# coding=utf-8
import os, sys, shutil
import xbmc, xbmcaddon, xbmcplugin, xbmcgui, xbmcvfs
import xml.etree.ElementTree as xmltree
import urllib
from traceback import print_exc
import json

ADDON        = xbmcaddon.Addon()
ADDONID      = ADDON.getAddonInfo('id').decode( 'utf-8' )
ADDONVERSION = ADDON.getAddonInfo('version')
ADDONNAME    = ADDON.getAddonInfo('name').decode("utf-8")
LANGUAGE     = ADDON.getLocalizedString
CWD          = ADDON.getAddonInfo('path').decode("utf-8")
DEFAULTPATH  = xbmc.translatePath( os.path.join( CWD, 'resources' ).encode("utf-8") ).decode("utf-8")
DATAPATH     = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('profile')).decode('utf-8')
ltype        = sys.modules[ '__main__' ].ltype

def log(txt):
    if isinstance (txt,str):
        txt = txt.decode('utf-8')
    message = u'%s: %s' % (ADDONID, txt)
    xbmc.log(msg=message.encode('utf-8'), level=xbmc.LOGDEBUG)

class PathRuleFunctions():
    def __init__(self):
        self.nodeRules = None
        self.ATTRIB = None

    def _load_rules( self ):
        if ltype.startswith('video'):
            overridepath = os.path.join( DEFAULTPATH , "videorules.xml" )
        else:
            overridepath = os.path.join( DEFAULTPATH , "musicrules.xml" )
        try:
            tree = xmltree.parse( overridepath )
            return tree
        except:
            return None

    def translateComponent( self, component, splitPath ):
        if component[ 0 ] is None:
            return splitPath[ 0 ]

        if unicode( component[ 0 ], "utf-8" ).isnumeric():
            string = LANGUAGE( int( component[ 0 ] ) )
            if string != "": return string
            return xbmc.getLocalizedString( int( component[ 0 ] ) )
        else:
            return component[ 0 ]

    def translateValue( self, rule, splitPath, ruleNum ):
        if splitPath[ ruleNum ][ 1 ] == "":
            return "<No value>"

        if rule[ 1 ] == "year" or rule[ 2 ] != "integer" or rule[ 3 ] is None:
            return splitPath[ ruleNum ][ 1 ]

        try:
            value = int( splitPath[ ruleNum ][ 1 ] )
        except:
            return splitPath[ ruleNum ][ 1 ]

        json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title"], "directory": "%s", "media": "files" } }' % rule[ 3 ] )
        json_query = unicode(json_query, 'utf-8', errors='ignore')
        json_response = json.loads(json_query)
        listings = []
        values = []
        # Add all directories returned by the json query
        if json_response.has_key('result') and json_response['result'].has_key('files') and json_response['result']['files'] is not None:
            for item in json_response['result']['files']:
                if "id" in item and item[ "id" ] == value:
                    return "(%d) %s" %( value, item[ "label" ] )

        # Didn't find a match
        return "%s (%s)" %( splitPath[ ruleNum ][ 1 ], xbmc.getLocalizedString(13205) )

    def displayRule( self, actionPath, ruleNum ):
        try:
            # Load the xml file
            tree = xmltree.parse( actionPath )
            root = tree.getroot()
            # Get the path
            path = root.find( "path" ).text
            # Split the path element
            splitPath = self.ATTRIB.splitPath( path )
            # Get the rules
            rules = self.getRulesForPath( splitPath[ 0 ] )
            if len( splitPath ) == int( ruleNum ):
                # This rule doesn't exist - create it
                self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, rules[ 0 ][ 1 ], "" ) )
                rule = rules[ 0 ]
                translatedValue = "<No value>"
            else:
                # Find the matching rule
                rule = self.getMatchingRule( splitPath[ ruleNum ], rules )
                translatedValue = self.translateValue( rule, splitPath, ruleNum )

            #Component
            listitem = xbmcgui.ListItem( label="%s" % ( self.translateComponent( rule, splitPath[ ruleNum ] ) ) )
            action = "plugin://plugin.library.node.editor?ltype=%s&type=editPathMatch&actionPath=%s&rule=%d" %( ltype, actionPath, ruleNum )
            xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
            # Value
            listitem = xbmcgui.ListItem( label="%s" % ( translatedValue ) )
            action = "plugin://plugin.library.node.editor?ltype=%s&type=editPathValue&actionPath=%s&rule=%s" %( ltype, actionPath, ruleNum )
            xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
            # Browse
            if rule[ 3 ] is not None:
                listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
                action = "plugin://plugin.library.node.editor?ltype=%s&type=browsePathValue&actionPath=%s&rule=%s" %( ltype, actionPath, ruleNum )
                xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )

            xbmcplugin.setContent(int(sys.argv[1]), 'files')
            xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
            return
        except:
            log( "Failed" )
            print_exc()

    def getRulesForPath( self, path ):
        # This function gets all valid rules for a given path

        # Load the rules
        tree = self._load_rules()
        subSearch = None
        content = []
        elems = tree.getroot().find( "paths" ).findall( "type" )
        for elem in elems:
            if elem.attrib.get( "name" ) == path[ 0 ]:
                for contentType in elem.findall( "content" ):
                    content.append( contentType.text )
                subSearch = elem
                break

        if path[ 1 ] and subSearch is not None:
            for elem in subSearch.findall( "type" ):
                if elem.attrib.get( "name" ) == path[ 1 ]:
                    if elem.find( "content" ):
                        content = []
                        for contentType in elem.findall( "content" ):
                            content.append( contentType.text )
                    break

        rules = []
        for rule in tree.getroot().find( "pathRules" ).findall( "rule" ):
            # Can it be browsed
            if rule.find( "browse" ) is not None:
                browse = rule.find( "browse" ).text
            else:
                browse = None
            if len( content ) == 0:
                rules.append( ( rule.attrib.get( "label" ), rule.attrib.get( "name" ), rule.find( "value" ).text, browse ) )
            else:
                for contentType in rule.findall( "content" ):
                    if contentType.text in content:
                        # If the root of the browse is changed dependant on what we're looking at, replace
                        # it now with the correct content
                        if browse is not None and "::root::" in browse:
                            browse = browse.replace( "::root::", content[ 0 ] )
                        rules.append( ( rule.attrib.get( "label" ), rule.attrib.get( "name" ), rule.find( "value" ).text, browse ) )

        if len( rules ) == 0:
            return [ ( None, "property", "string", None ) ]
        return rules

    def getMatchingRule( self, component, rules ):
        # This function matches a component to its rule
        for rule in rules:
            if rule[ 1 ] == component[ 0 ]:
                return rule

        # No rule matched, return an empty one
        return ( None, "property", "string", None )

    def editMatch( self, actionPath, ruleNum ):
        # Change the match element of a path component

        # Load the xml file
        tree = xmltree.parse( actionPath )
        root = tree.getroot()
        # Get the path
        path = root.find( "path" ).text
        # Split the path element
        splitPath = self.ATTRIB.splitPath( path )
        # Get the rules and current value
        rules = self.getRulesForPath( splitPath[ 0 ] )
        currentValue = splitPath[ ruleNum ][ 1 ]

        if rules[ 0 ][ 0 ] is None:
            # There are no available choices
            self.manuallyEditMatch( actionPath, ruleNum, splitPath[ ruleNum ][ 0 ], currentValue )
            return

        # Build list of rules to choose from
        selectName = []
        for rule in rules:
            selectName.append( self.translateComponent( rule, None ) )

        # Add a manual option
        selectName.append( LANGUAGE( 30408 ) )

        # Let the user select an operator
        selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30305 ), selectName )
        # If the user selected no operator...
        if selectedOperator == -1:
            return
        elif selectedOperator == len( rules ):
            # User choose custom property
            self.manuallyEditMatch( actionPath, ruleNum, splitPath[ ruleNum ][ 0 ], currentValue )
            return            
        else:
            self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, rules[ selectedOperator ][ 1 ], currentValue ) )

    def manuallyEditMatch( self, actionPath, ruleNum, currentName, currentValue ):
        type = xbmcgui.INPUT_ALPHANUM
        returnVal = xbmcgui.Dialog().input( LANGUAGE( 30318 ), currentName, type=type )
        if returnVal != "":
            self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, returnVal.decode( "utf-8" ), currentValue ) )       

    def editValue( self, actionPath, ruleNum ):
        # Let the user edit the value of a path component

        # Load the xml file
        tree = xmltree.parse( actionPath )
        root = tree.getroot()
        # Get the path
        path = root.find( "path" ).text
        # Split the path element
        splitPath = self.ATTRIB.splitPath( path )
        # Get the rules and current value
        rules = self.getRulesForPath( splitPath[ 0 ] )
        rule = self.getMatchingRule( splitPath[ ruleNum ], rules )

        if rule[ 2 ] == "boolean":
            # Let the user select a boolean
            selectedBool = xbmcgui.Dialog().select( LANGUAGE( 30307 ), [ xbmc.getLocalizedString(20122), xbmc.getLocalizedString(20424) ] )
            # If the user selected nothing...
            if selectedBool == -1:
                return
            value = "true"
            if selectedBool == 1:
                value = "false"
            self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, splitPath[ ruleNum][ 0 ], value ) )
        else:
            type = xbmcgui.INPUT_ALPHANUM
            if rule[ 2 ] == "integer":
                type = xbmcgui.INPUT_NUMERIC
            returnVal = xbmcgui.Dialog().input( LANGUAGE( 30307 ), splitPath[ ruleNum ][ 1 ], type=type )
            if returnVal != "":
                self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, splitPath[ ruleNum ][ 0 ], returnVal.decode( "utf-8" ) ) )


    def browse( self, actionPath, ruleNum ):
        # This function launches the browser for the given property type

        pDialog = xbmcgui.DialogProgress()
        pDialog.create( ADDONNAME, LANGUAGE( 30317 ) )

        # Load the xml file
        tree = xmltree.parse( actionPath )
        root = tree.getroot()
        # Get the path
        path = root.find( "path" ).text
        # Split the path element
        splitPath = self.ATTRIB.splitPath( path )
        # Get the rules and current value
        rules = self.getRulesForPath( splitPath[ 0 ] )
        rule = self.getMatchingRule( splitPath[ ruleNum ], rules )
        title = self.translateComponent( rule, splitPath[ ruleNum ] )

        # Get the path we'll be browsing
        browsePath = self.getBrowsePath( splitPath, rule[ 3 ], ruleNum )

        json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "thumbnail"], "directory": "%s", "media": "files" } }' % browsePath )
        json_query = unicode(json_query, 'utf-8', errors='ignore')
        json_response = json.loads(json_query)
        listings = []
        values = []
        # Add all directories returned by the json query
        if json_response.has_key('result') and json_response['result'].has_key('files') and json_response['result']['files'] is not None:
            total = len( json_response[ 'result' ][ 'files' ] )
            for item in json_response['result']['files']:
                if item[ "label" ] == "..":
                    continue
                thumb = None
                if item[ "thumbnail" ] is not "":
                    thumb = item[ "thumbnail" ]
                listitem = xbmcgui.ListItem(label=item[ "label" ], iconImage=thumb )
                listitem.setProperty( "thumbnail", thumb )
                listings.append( listitem )
                if rule[ 2 ] == "integer" and "id" in item:
                    values.append( str( item[ "id" ] ) )
                else:
                    values.append( item[ "label" ] )

        pDialog.close()

        if len( listings ) == 0:
            # No browsable options found
            xbmcgui.Dialog().ok( ADDONNAME, LANGUAGE( 30409 ) )
            return

        # Show dialog
        w = ShowDialog( "DialogSelect.xml", CWD, listing=listings, windowtitle=title )
        w.doModal()
        selectedItem = w.result
        del w
        if selectedItem == "" or selectedItem == -1:
            return None

        self.ATTRIB.writeUpdatedPath( actionPath, ( ruleNum, splitPath[ ruleNum ][ 0 ], values[ selectedItem ] ) )

    def getBrowsePath( self, splitPath, newBase, rule ):
        # This function replaces the base path with the browse path, and removes the current
        # component

        returnText = ""

        if "::root::" in newBase:
            newBase = newBase.replace( "::root::", splitPath[ 0 ][ 0 ] )

        # Enumarate through everything in the existing path
        addedQ = False
        for x, component in enumerate( splitPath ):
            if x != rule:
                # Transfer this component to the new path
                if x == 0:
                    returnText = newBase
                elif not addedQ:
                    returnText += "?%s=%s" %( component[ 0 ], urllib.quote( component[ 1 ].encode( "utf-8" ) ).decode( "utf-8" ) )
                    addedQ = True
                else:
                    returnText += "&%s=%s" %( component[ 0 ], urllib.quote( component[ 1 ].encode( "utf-8" ) ).decode( "utf-8" ) )
        return returnText

    # in-place prettyprint formatter
    def indent( self, elem, level=0 ):
        i = "\n" + level*"\t"
        if len(elem):
            if not elem.text or not elem.text.strip():
                elem.text = i + "\t"
            if not elem.tail or not elem.tail.strip():
                elem.tail = i
            for elem in elem:
                self.indent(elem, level+1)
            if not elem.tail or not elem.tail.strip():
                elem.tail = i
        else:
            if level and (not elem.tail or not elem.tail.strip()):
                elem.tail = i

# ============================
# === PRETTY SELECT DIALOG ===
# ============================
class ShowDialog( xbmcgui.WindowXMLDialog ):
    def __init__( self, *args, **kwargs ):
        xbmcgui.WindowXMLDialog.__init__( self )
        self.listing = kwargs.get( "listing" )
        self.windowtitle = kwargs.get( "windowtitle" )
        self.result = -1

    def onInit(self):
        try:
            self.fav_list = self.getControl(6)
            self.getControl(3).setVisible(False)
        except:
            print_exc()
            self.fav_list = self.getControl(3)
        self.getControl(5).setVisible(False)
        self.getControl(1).setLabel(self.windowtitle)
        for item in self.listing :
            listitem = xbmcgui.ListItem(label=item.getLabel(), label2=item.getLabel2(), iconImage=item.getProperty( "icon" ), thumbnailImage=item.getProperty( "thumbnail" ))
            listitem.setProperty( "Addon.Summary", item.getLabel2() )
            self.fav_list.addItem( listitem )
        self.setFocus(self.fav_list)

    def onAction(self, action):
        if action.getId() in ( 9, 10, 92, 216, 247, 257, 275, 61467, 61448, ):
            self.result = -1
            self.close()

    def onClick(self, controlID):
        if controlID == 6 or controlID == 3:
            num = self.fav_list.getSelectedPosition()
            self.result = num
        else:
            self.result = -1
        self.close()

    def onFocus(self, controlID):
        pass
