"""
svn repo installer plugin

Nuka1195
"""

# main imports
import sys
import os
import xbmc
import xbmcgui
import xbmcplugin
import urllib
import re
from xml.sax.saxutils import unescape
#from pprint import pprint
from xbmcplugin_lib import *

# Script constants
__date__ = '15-10-2009'
log("Module: %s Dated: %s loaded!" % (__name__, __date__))

class Parser:
	""" Parser Class: grabs all tag versions and urls """
	# regexpressions
	revision_regex = re.compile( '<h2>.+?Revision ([0-9]*): ([^<]*)</h2>' )
	asset_regex = re.compile( '<li><a href="([^"]*)">([^"]*)</a></li>' )

	def __init__( self, htmlSource ):
		log("%s __init__!" % (self.__class__))
		# set our initial status
		self.dict = { "status": "fail", "revision": 0, "assets": [], "url": "" }
		# fetch revision number
		self._fetch_revision( htmlSource )
		# if we were successful, fetch assets
		if ( self.dict[ "revision" ] != 0 ):
			self._fetch_assets( htmlSource )

	def _fetch_revision( self, htmlSource ):
		try:
			# parse revision and current dir level
			revision, url = self.revision_regex.findall( htmlSource )[ 0 ]
			# we succeeded :), set our info
			self.dict[ "url" ] = url
			self.dict[ "revision" ] = int( revision )
		except:
			pass

	def _fetch_assets( self, htmlSource ):
		try:
			assets = self.asset_regex.findall( htmlSource )
			if ( len( assets ) ):
				for asset in assets:
					if ( asset[ 0 ] != "../" ):
						self.dict[ "assets" ] += [ unescape( asset[ 0 ] ) ]
				self.dict[ "status" ] = "ok"
		except:
			pass


class Main:
	# base path
	BASE_CACHE_PATH = os.path.join( xbmc.translatePath( "special://profile/" ), "Thumbnails", "Pictures" )
#	INSTALLED_ITEMS_FILENAME = os.path.join( os.getcwd(), "installed_items.dat" )
	INSTALLED_ITEMS_FILENAME = os.path.join( HOME_DIR, "installed_items.dat" )

	def __init__( self ):
		log( "%s init!" % self.__class__ )
		ok = False
		# parse sys.argv for our current url
		self._parse_argv()
		# if this is first run list all the repos
		if ( sys.argv[ 2 ] == "" ):
			ok = self._get_repos()
		else:
			# get the repository info
			repo_info = get_repo_info( self.args.repo )
			if repo_info:
				self.REPO_URL, self.REPO_ROOT, self.REPO_STRUCTURES = repo_info
				# if category is root, set our repo root
				if ( self.args.category == "root" ):
					self.args.category = self.REPO_ROOT

				# get XBMC revision
				self.XBMC_REVISION = get_xbmc_revision()
				# get the list
				ok = self._show_categories()
		# send notification we're finished, successfully or unsuccessfully
		xbmcplugin.endOfDirectory( handle=int( sys.argv[ 1 ] ), succeeded=ok )

	def _clear_log( self, repo ):
#		base_path = os.path.join( xbmc.translatePath( "special://profile/" ), "plugin_data", "programs", os.path.basename( os.getcwd() ) )
		base_path = os.path.join( xbmc.translatePath( "special://profile/" ), "plugin_data", "programs", os.path.basename( HOME_DIR ) )
		for page in range( 3 ):
			path = os.path.join( base_path, "%s%d.txt" % ( repo, page, ) )
			# remove log file
			if ( os.path.isfile( path ) ):
				os.remove( path )

	def _parse_argv( self ):
		# if first run set title to blank
		if ( sys.argv[ 2 ] == "" ):
			self.args = Info( title="" )
		else:
			# call _Info() with our formatted argv to create the self.args object
			exec "self.args = Info(%s)" % ( urllib.unquote_plus( sys.argv[ 2 ][ 1 : ].replace( "&", ", " ) ), )

	def _get_repos( self ):
		try:
			# we fetch the log here only at start of plugin
			#import xbmcplugin_logviewer
			# add the check for updates item to the media list
			url = "%s?category='updates'" % ( sys.argv[ 0 ], )
			# set the default icon
			icon = "DefaultFolder.png"
			# set thumbnail
#			thumbnail = os.path.join( os.getcwd(), "resources", "media", "update_checker.png" )
			thumbnail = os.path.join( HOME_DIR, "resources", "media", "update_checker.png" )
			# create our listitem, fixing title
			listitem = xbmcgui.ListItem( xbmc.getLocalizedString( 30500 ), iconImage=icon, thumbnailImage=thumbnail )
			# set the title
			listitem.setInfo( type="Video", infoLabels={ "Title": xbmc.getLocalizedString( 30500 ) } )
			cm = [ ( xbmc.getLocalizedString( 30610 ), "XBMC.RunPlugin(%s?showreadme=True&repo=None&readme=None)" % ( sys.argv[ 0 ], ), ) ]
			listitem.addContextMenuItems( cm, replaceItems=True )
			# add our item
			ok = xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=url, listitem=listitem, isFolder=True )

			# now add all the repos
			repos = load_repos()
			for repo in repos:
				cm = []
				# create the url
				url = "%s?category='root'&repo=%s&title=%s" % ( sys.argv[ 0 ], repr( urllib.quote_plus( repo ) ), repr( urllib.quote_plus( repo ) ), )
				# set thumbnail
#				thumbnail = os.path.join( os.getcwd(), "resources", "media", "svn_repo.png" )
				thumbnail = os.path.join( HOME_DIR, "resources", "media", "svn_repo.png" )
				# create our listitem, fixing title
				listitem = xbmcgui.ListItem( repo, iconImage=icon, thumbnailImage=thumbnail )
				# set the title
				listitem.setInfo( type="Video", infoLabels={ "Title": repo } )
				# grab the log for this repo
				if ( "(tagged)" not in repo ):
					#parser = xbmcplugin_logviewer.ChangelogParser( repo, parse=False )
					#parser.fetch_changelog()
					# clear logs on first run
					self._clear_log( repo )
					# add view log context menu item
					cm += [ ( xbmc.getLocalizedString( 30600 ), "XBMC.RunPlugin(%s?showlog=True&repo=%s&category=None&revision=None&parse=True)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( repo ) ), ), ) ]
				# add view readme context menu item
				cm += [ ( xbmc.getLocalizedString( 30610 ), "XBMC.RunPlugin(%s?showreadme=True&repo=None&readme=None)" % ( sys.argv[ 0 ], ), ) ]
				# add context menu items
				listitem.addContextMenuItems( cm, replaceItems=True )
				# add the item to the media list
				ok = xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=url, listitem=listitem, isFolder=True )
				# if user cancels, call raise to exit loop
				if ( not ok ): raise
		except:
			# user cancelled dialog or an error occurred
			logError()
			ok = False
		if ( ok ):
			xbmcplugin.addSortMethod( handle=int( sys.argv[ 1 ] ), sortMethod=xbmcplugin.SORT_METHOD_LABEL )
		return ok

	def _show_categories( self ):
		ok = False
		# fetch the html source
		items = self._get_items()
		# if successful
		if ( items and items[ "status" ] == "ok" ):
			# if there are assets, we have categories
			ok = self._fill_list( items[ "url" ], items[ "revision" ], items[ "assets" ] )
		return ok

	def _fill_list( self, repo_url, revision, assets ):
		log("_fill_list()")
		try:
			ok = False
			# enumerate through the list of categories and add the item to the media list
			all_addons = []
			for item in assets:
				info={}
				cm = []
				isFolder = True
				for name, noffset, install, ioffset, voffset in self.REPO_STRUCTURES:
					try:
						if ( repo_url.split( "/" )[ int( noffset ) ].lower() == name.lower() ):
							isFolder = False
							break
					except:
						pass

				if ( isFolder ):
					heading = "category"
					thumbnail = ""
					label2 = ""
					version = ""
					path = ""
				else:
					heading = "download_url"
					thumbnail = "%s%s/%sdefault.tbn" % ( self.REPO_URL, repo_url.replace( " ", "%20" ), item.replace( " ", "%20" ), )
					info['thumb'] = thumbnail
					svn_url = "%s%s/%s" % ( self.REPO_URL, repo_url.replace( " ", "%20" ), item.replace( " ", "%20" ), )
					svn_tagInfo, installed_version, label2, path = self._check_compatible( svn_url, self.REPO_URL, install, int( ioffset ), int( voffset ) )
					if svn_tagInfo:
						info.update(svn_tagInfo)
						info['svn_ver'] = svn_tagInfo.get('version')
					info['svn_url'] = svn_url
					info['version'] = installed_version
					info['filepath'] = path
					if not info.get('title'):
						info['title'] = urllib.unquote_plus( item[ : -1 ] )
					info['install'] = install
					info['ioffset'] = ioffset
					info['voffset'] = voffset
					info['repo'] = self.args.repo
					info['category'] = parseCategory(path)
					version = info.get('svn_ver')
					if not version:
						version = "?"
					version = " (v%s)" % version

					# discover if a readme exists, save url
					readme = check_readme( svn_url )
					info['readme'] = readme

				if heading == "category":
					url = '%s?%s="%s/%s"&repo=%s&install="%s"&ioffset=%s&voffset=%s&title=%s' % \
						( sys.argv[ 0 ], heading, urllib.quote_plus( repo_url ), urllib.quote_plus( item ), repr( urllib.quote_plus( self.args.repo ) ), \
						install, ioffset, voffset, repr( urllib.quote_plus( self.args.repo ) ), )
				elif heading == "download_url":
					# dont add install url to info dict if incompatible
					if xbmc.getLocalizedString( 30015 ) not in label2:
						dl = "%s/%s" % ( urllib.quote_plus( repo_url ), urllib.quote_plus( item ) )
						info['download_url'] = "download_url=%s&repo=%s&install=%s&ioffset=%s&voffset=%s" % \
							( repr( dl ), repr( urllib.quote_plus( self.args.repo ) ), repr(install), ioffset, voffset )

					url_args = "show_info=%s" % urllib.quote_plus( repr(info['filepath']) )
					url = '%s?%s' % ( sys.argv[ 0 ], url_args, )
					
				# add uninstall item
				if ( not isFolder and not "SVN%20Repo%20Installer" in item and os.path.isfile( path ) ):
					cm +=  [ ( xbmc.getLocalizedString( 30022 ), "XBMC.RunPlugin(%s?delete=%s&title=%s&delete_from_list=True)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( os.path.dirname( path ) ) ), repr( urllib.quote_plus( item[ : -1 ] ) ), ), ) ]
					
				# set the default icon
				if isFolder:
					icon = "DefaultFolder.png"
				else:
					icon = "DefaultFile.png"
				# create our listitem, fixing title
				label1 = "%s%s" % ( urllib.unquote_plus( item[ : -1 ] ), version, )
				listitem = xbmcgui.ListItem( label1, label2=label2, iconImage=icon, thumbnailImage=thumbnail )
				# set the title
				listitem.setInfo( type="Video", infoLabels={ "Title": label1, "Genre": label2 } )
				if ( not isFolder ):
					cm += [ ( xbmc.getLocalizedString( 30600 ), "XBMC.RunPlugin(%s?showlog=True&repo=%s&category=%s&revision=None&parse=True)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( self.args.repo ) ), urllib.quote_plus( repr( item[ : -1 ].replace( "%20", " " )  )  ), ), ) ]
					# add context menu items
					if ( readme ):
						cm += [ ( xbmc.getLocalizedString( 30610 ), "XBMC.RunPlugin(%s?showreadme=True&repo=None&readme=%s)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( readme ) ), ), ) ]
				listitem.addContextMenuItems( cm, replaceItems=True )

				all_addons.append(info)

				# add the item to the media list
				ok = xbmcplugin.addDirectoryItem( handle=int( sys.argv[ 1 ] ), url=url, listitem=listitem, isFolder=isFolder, totalItems=len( assets ) )
				# if user cancels, call raise to exit loop
				if ( not ok ): raise

			# save info to file
#			pprint (all_addons)
			saveFileObj(self.INSTALLED_ITEMS_FILENAME, all_addons)
		except:
			# user cancelled dialog or an error occurred
			logError()
			ok = False
		if ( ok ):
			# set our plugin category
			xbmcplugin.setPluginCategory( handle=int( sys.argv[ 1 ] ), category=self.args.title )
			# sort by genre so all update status' are grouped
			xbmcplugin.addSortMethod( handle=int( sys.argv[ 1 ] ), sortMethod=xbmcplugin.SORT_METHOD_GENRE )

		return ok

	def _check_compatible( self, url, repo_url, install, ioffset, voffset ):
		log("> _check_compatible() url=%s" % (url, ) )

		# get svn info
		# 1st try description.xml
		doc = readURL( os.path.join(url, "description.xml") )
		svn_tagInfo = parseDescriptionXML( doc )
		if not svn_tagInfo:
			# 2nd try defult.py
			doc = readURL( os.path.join(url, "default.py") )
			svn_tagInfo = parseAllDocTags( doc )

		if not svn_tagInfo:
			log("unable to find any svn tags")
			ok = False	# no description.xml or default.py - prevent installation
		else:
			# parse source for revision and version
			svn_version = svn_tagInfo.get('version','')
			svn_xbmc_rev = svn_tagInfo.get('XBMC_Revision',0)
			# compatible - 0 == unknown, so allow it
			ok = bool((not self.XBMC_REVISION) or (not svn_xbmc_rev) or (self.XBMC_REVISION >= svn_xbmc_rev))

		# create path
		items = url.replace( repo_url, "" ).split( "/" )
		# base path
		drive = xbmc.translatePath( "/".join( [ "special://home", install ] ) )
		if ( voffset != 0 ):
			items[ voffset - 1 ] = "%s - %s" % ( items[ voffset - 1 ].replace( "%20", " " ), items[ voffset ], )
			del items[ voffset ]
		path = os.path.join( drive, os.path.sep.join( items[ ioffset : ] ).replace( "%20", " " ) )

		# discover installed version
		version = ""
		isInstalled = os.path.isdir(path)
		log("isInstalled %s" % isInstalled)
		if isInstalled:
			# 1st use description.xml
			doc = readFile(os.path.join(path, "description.xml"))
			version = parseXMLTag(doc, "version")
			if not version:
				# parse from .py
				doc = readFile(os.path.join(path, "default.py"))
				version = parseDocTag( doc, "version" )

		if ( not ok ): 
			verState = xbmc.getLocalizedString( 30015 )							# Incompatible
		elif not isInstalled:
			verState = xbmc.getLocalizedString( 30021 )							# install
		else:
			# determine if svn update; always NEW if installed ver unknown
			if (not version and svn_version) or (svn_version and svn_version > version):
				verState = xbmc.getLocalizedString( 30014 )    					# New
			else:
				verState = xbmc.getLocalizedString( 30011 )						# OK

		# make label2 according to state
		label2 = makeLabel2( verState )
		log("< _check_compatible() installed_ver=%s label2=%s path=%s" % (version, label2, path))
		return svn_tagInfo, version, label2, path

	def _get_items( self ):
		try:
			# open url
			url = self.REPO_URL + self.args.category
			htmlSource = readURL( url )
			# parse source and return a dictionary
			return Parser( htmlSource ).dict
		except:
			# oops print error message
			print "ERROR: %s::%s (%d) - %s" % ( self.__class__.__name__, sys.exc_info()[ 2 ].tb_frame.f_code.co_name, sys.exc_info()[ 2 ].tb_lineno, sys.exc_info()[ 1 ], )
			return {}
