# 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 RuleFunctions():
    def __init__(self):
        self.nodeRules = 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 translateRule( self, rule ):
        # Load the rules
        tree = self._load_rules()
        hasValue = True
        elems = tree.getroot().find( "matches" ).findall( "match" )
        for elem in elems:
            if elem.attrib.get( "name" ) == rule[ 0 ]:
                match = xbmc.getLocalizedString( int( elem.find( "label" ).text ) )
                group = elem.find( "operator" ).text
        elems = tree.getroot().find( "operators" ).findall( "group" )
        operator = None
        defaultOperator = None
        defaultOperatorValue = None
        for elem in elems:
            if elem.attrib.get( "name" ) == group:
                for operators in elem.findall( "operator" ):
                    if operators.text == rule[ 1 ]:
                        operator = xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) )
                    if defaultOperator is None:
                        defaultOperator = xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) )
                        defaultOperatorValue = operators.text
                if "option" in elem.attrib:
                    hasValue = False
        # If we didn't match an operator, set it to the default
        if operator is None:
            operator = defaultOperator
            rule[ 1 ] = defaultOperatorValue
        if hasValue == False:
            return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ "|NONE|", "<No value>" ] ]
        if len( rule ) == 2 or rule[ 2 ] == "" or rule[ 2 ] is None:
            return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ "", "<No value>" ] ]
        return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ rule[ 2 ], rule[ 2 ] ] ]

    def displayRule( self, actionPath, path, ruleNum ):
        if actionPath.endswith( "index.xml" ):
            # If this is a parent node, call alternative function
            self.displayNodeRule( actionPath, ruleNum )
            return
        try:
            # Load the xml file
            tree = xmltree.parse( actionPath )
            root = tree.getroot()
            actionPath = actionPath
            # Get the content type
            content = root.find( "content" )
            if content is not None:
                content = content.text
            else:
                content = "NONE"
            # Get all the rules
            ruleCount = 0
            rules = root.findall( "rule" )
            if len( rules ) == int( ruleNum ):
                # This rule doesn't exist - create it
                self.newRule( tree, urllib.unquote( actionPath ) )
                rules = root.findall( "rule" )
            if rules is not None:
                for rule in rules:
                    if str( ruleCount ) == ruleNum:
                        value = rule.find( "value" )
                        if value is None:
                            value = ""
                        else:
                            value = value.text
                        translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value ] )
                        # Rule to change match
                        listitem = xbmcgui.ListItem( label="%s" % ( translated[ 0 ][ 0 ] ) )
                        action = "plugin://plugin.library.node.editor?ltype=%s&type=editMatch&actionPath=" % ltype + actionPath + "&content=" + content + "&default=" + translated[ 0 ][ 1 ] + "&rule=" + str( ruleCount )
                        xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
                        listitem = xbmcgui.ListItem( label="%s" % ( translated[ 1 ][ 0 ] ) )
                        action = "plugin://plugin.library.node.editor?ltype=%s&type=editOperator&actionPath=" % ltype + actionPath + "&group=" + translated[ 1 ][ 1 ] + "&default=" + translated[ 1 ][ 2 ] + "&rule=" + str( ruleCount )
                        xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
                        if not ( translated[ 2 ][ 0 ] ) == "|NONE|":
                            listitem = xbmcgui.ListItem( label="%s" % ( translated[ 2 ][ 1 ] ) )
                            action = "plugin://plugin.library.node.editor?ltype=%s&type=editValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount )
                            xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
                            # Check if this match type can be browsed
                            if self.canBrowse( translated[ 0 ][ 1 ], content ):
                                #listitem.addContextMenuItems( [(LANGUAGE(30107), "XBMC.RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content + ")" )], replaceItems = True )
                                listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
                                action = "plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content
                                xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
                            #self.browse( translated[ 0 ][ 1 ], content )
                        xbmcplugin.setContent(int(sys.argv[1]), 'files')
                        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
                        return
                    ruleCount = ruleCount + 1
        except:
            print_exc()

    def editMatch( self, actionPath, ruleNum, content, default ):
        # Load all operator groups
        tree = self._load_rules().getroot()
        elems = tree.find( "matches" ).findall( "match" )
        selectName = []
        selectValue = []
        # Find the matches for the content we've been passed
        for elem in elems:
            if content != "NONE":
                contentMatch = elem.find( content )
                if contentMatch is not None:
                    selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
                    selectValue.append( elem.attrib.get( "name" ) )
                else:
                    pass
            else:
                selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
                selectValue.append( elem.attrib.get( "name" ) )
        # Let the user select an operator
        selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30305 ), selectName )
        # If the user selected no operator...
        if selectedOperator == -1:
            return
        self.writeUpdatedRule( actionPath, ruleNum, match = selectValue[ selectedOperator ] )

    def editOperator( self, actionPath, ruleNum, group, default ):
        # Load all operator groups
        tree = self._load_rules().getroot()
        elems = tree.find( "operators" ).findall( "group" )
        selectName = []
        selectValue = []
        # Find the group we've been passed and load its operators
        for elem in elems:
            if elem.attrib.get( "name" ) == group:
                for operators in elem.findall( "operator" ):
                    selectName.append( xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) ) )
                    selectValue.append( operators.text )
        # Let the user select an operator
        selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30306 ), selectName )
        # If the user selected no operator...
        if selectedOperator == -1:
            return
        self.writeUpdatedRule( actionPath, ruleNum, operator = selectValue[ selectedOperator ] )

    def editValue( self, actionPath, ruleNum ):
        # This function is the entry point for editing the value of a rule
        # Because we can't always pass the current value through the uri, we first need
        # to retrieve it, and the operator data type
        try:
            if actionPath.endswith( "index.xml" ):
                ( filePath, fileName ) = os.path.split( actionPath )
                # Load the rules file
                if ltype.startswith('video'):
                    tree = xmltree.parse( os.path.join( DATAPATH, "videorules.xml" ) )
                else:
                    tree = xmltree.parse( os.path.join( DATAPATH, "musicrules.xml" ) )
                root = tree.getroot()
                nodes = root.findall( "node" )
                for node in nodes:
                    if node.attrib.get( "name" ) == filePath:
                        rules = node.findall( "rule" )
                        ruleCount = 0
                        for rule in rules:
                            if ruleCount == int( ruleNum ):
                                # This is the rule we'll be updating
                                # Get the current value
                                curValue = rule.find( "value" )
                                if curValue is None:
                                    curValue = ""
                                else:
                                    curValue = curValue.text
                                match = rule.attrib.get( "field" )
                                operator = rule.attrib.get( "operator" )
                            ruleCount += 1
            else:
                # Load the xml file
                tree = xmltree.parse( actionPath )
                root = tree.getroot()
                # Get all the rules
                ruleCount = 0
                rules = root.findall( "rule" )
                if rules is not None:
                    for rule in rules:
                        if str( ruleCount ) == ruleNum:
                            # This is the rule we'll be updating
                            # Get the current value
                            curValue = rule.find( "value" )
                            if curValue is None:
                                curValue = ""
                            else:
                                curValue = curValue.text
                            match = rule.attrib.get( "field" )
                            operator = rule.attrib.get( "operator" )
                        ruleCount = ruleCount + 1
            # Now, use the match value to get the group of operators this
            # comes from (this will tell us the data type in all types
            # but "date")
            tree = self._load_rules().getroot()
            elems = tree.find( "matches" ).findall( "match" )
            for elem in elems:
                if elem.attrib.get( "name" ) == match:
                    group = elem.find( "operator" ).text
            if group == "date":
                # We probably should go through the tree again, but we'll just check
                # for string ending in "inthelast", and switch the type to numeric
                if operator.endswith( "inthelast" ):
                    group = "numeric"
            # Set the type of text entry dialog to be used
            if group == "string":
                type = xbmcgui.INPUT_ALPHANUM
            if group == "numeric":
                type = xbmcgui.INPUT_NUMERIC
            if group == "time":
                type = xbmcgui.INPUT_TIME
            if group == "date":
                type = xbmcgui.INPUT_DATE
            if group == "isornot":
                type = xbmcgui.INPUT_ALPHANUM
            returnVal = xbmcgui.Dialog().input( LANGUAGE( 30307 ), curValue, type=type )
            if returnVal != "":
                self.writeUpdatedRule( actionPath, ruleNum, value=returnVal.decode( "utf-8" ) )
        except:
            print_exc()

    def writeUpdatedRule( self, actionPath, ruleNum, match = None, operator = None, value = None ):
        # This function writes an updated match, operator or value to a rule
        if actionPath.endswith( "index.xml" ):
            # This is a parent node rule, so call the relevant function
            #( filePath, fileName ) = os.path.split( actionPath )
            #self.editNodeRule( filePath, originalRule, translated )
            self.editNodeRule( actionPath, ruleNum, match, operator, value )
            return
        try:
            # Load the xml file
            tree = xmltree.parse( actionPath )
            root = tree.getroot()
            # Get all the rules
            ruleCount = 0
            rules = root.findall( "rule" )
            if rules is not None:
                for rule in rules:
                    if str( ruleCount ) == ruleNum:
                        # This is the rule we're updating
                        valueElem = rule.find( "value" )
                        if match is None:
                            match = rule.attrib.get( "field" )
                        if operator is None:
                            operator = rule.attrib.get( "operator" )
                        if value is None:
                            if valueElem is None:
                                value = ""
                            else:
                                value = valueElem.text
                        if value is None:
                            value = ""
                        translated = self.translateRule( [ match, operator, value ] )
                        # Update the rule
                        rule.set( "field", translated[ 0 ][ 1 ] )
                        rule.set( "operator", translated[ 1 ][ 2 ] )
                        if len( translated ) == 3:
                            if rule.find( "value" ) == None:
                                # Create a new rule node
                                xmltree.SubElement( rule, "value" ).text = translated[ 2 ][ 0 ]
                            else:
                                rule.find( "value" ).text = translated[ 2 ][ 0 ]
                    ruleCount = ruleCount + 1
            # Save the file
            self.indent( root )
            tree.write( actionPath, encoding="UTF-8" )
        except:
            print_exc()

    def newRule( self, tree, actionPath ):
        # This function adds a new rule, with default match and operator, no value
        try:
            # Load the xml file
            # tree = xmltree.parse( actionPath )
            root = tree.getroot()
            # Get the content type
            content = root.find( "content" )
            # Find the default match for this content type
            ruleTree = self._load_rules().getroot()
            elems = ruleTree.find( "matches" ).findall( "match" )
            match = "title"
            for elem in elems:
                if content is not None:
                    contentCheck = elem.find( content.text )
                    if contentCheck is not None:
                        # We've found the first match for this type
                        match = elem.attrib.get( "name" )
                        operator = elem.find( "operator" ).text
                        break
                else:
                    # We've found the first match for this type
                    match = elem.attrib.get( "name" )
                    operator = elem.find( "operator" ).text
                    break
            # Find the default operator for this match
            elems = ruleTree.find( "operators" ).findall( "group" )
            for elem in elems:
                if elem.attrib.get( "name" ) == operator:
                    operator = elem.find( "operator" ).text
                    break
            # Write the new rule
            newRule = xmltree.SubElement( root, "rule" )
            newRule.set( "field", match )
            newRule.set( "operator", operator )
            xmltree.SubElement( newRule, "value" )
            # Save the file
            self.indent( root )
            tree.write( actionPath, encoding="UTF-8" )
            if actionPath.endswith( "index.xml" ):
                ( filePath, fileName ) = os.path.split( actionPath )
                newRule = self.translateRule( [ match, operator, "" ] )
                self.addNodeRule( filePath, newRule )
        except:
            print_exc()

    def deleteRule( self, actionPath, ruleNum ):
        # This function deletes a rule
        result = xbmcgui.Dialog().yesno(ADDONNAME, LANGUAGE( 30405 ) )
        if not result:
            return
        if actionPath.endswith( "index.xml" ):
            # This is a parent node rule, so call the relevant function
            #( filePath, fileName ) = os.path.split( actionPath )
            #self.deleteNodeRule( filePath, originalRule )
            self.deleteNodeRule( actionPath, ruleNum )
        try:
            # Load the xml file
            tree = xmltree.parse( actionPath )
            root = tree.getroot()
            # Get all the rules
            ruleCount = 0
            rules = root.findall( "rule" )
            if rules is not None:
                for rule in rules:
                    if str( ruleCount ) == ruleNum:
                        # This is the rule we want to delete
                        if actionPath.endswith( "index.xml" ):
                            # Translate the rule, so we can delete it from the views
                            valueElem = rule.find( "value" )
                            origMatch = rule.attrib.get( "field" )
                            origOperator = rule.attrib.get( "operator" )
                            if valueElem is None:
                                origValue = ""
                            else:
                                origValue = valueElem.text
                            originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
                        # Delete the rule
                        root.remove( rule )
                        break
                    ruleCount = ruleCount + 1
            # Save the file
            self.indent( root )
            tree.write( actionPath, encoding="UTF-8" )
        except:
            print_exc()

    # Functions for managing rules in all views
    def displayNodeRule( self, actionPath, ruleNum ):
        # This function will load and display a parent node rule
        # (and create one, if the ruleNum specified doesn't exist)
        # Split the actionPath, to make things easier
        ( filePath, fileName ) = os.path.split( actionPath )
        try:
            # Load the rules file
            if ltype.startswith('video'):
                tree = xmltree.parse( os.path.join( DATAPATH, "videorules.xml" ) )
            else:
                tree = xmltree.parse( os.path.join( DATAPATH, "musicrules.xml" ) )
            root = tree.getroot()
            # Find the relevant node
            nodes = root.findall( "node" )
            if nodes is None:
                self.newNodeRule( actionPath, ruleNum )
                return
            ruleNode = None
            for node in nodes:
                if node.attrib.get( "name" ) == filePath:
                    ruleNode = node
                    break
            if ruleNode == None:
                self.newNodeRule( actionPath, ruleNum )
                return
            # Find the relevant rule
            rules = node.findall( "rule" )
            if rules is None or len( rules ) == int( ruleNum ):
                self.newNodeRule( actionPath, ruleNum )
                return
            ruleCount = 0
            for rule in rules:
                if ruleCount == int( ruleNum ):
                    value = rule.find( "value" )
                    if value is None:
                        value = ""
                    else:
                        value = value.text
                    translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value ] )
                    actionPath = urllib.quote( actionPath )
                    # Rule to change match
                    listitem = xbmcgui.ListItem( label="%s" % ( translated[ 0 ][ 0 ] ) )
                    action = "plugin://plugin.library.node.editor?ltype=%s&type=editMatch&actionPath=" % ltype + actionPath + "&default=" + translated[ 0 ][ 1 ] + "&rule=" + str( ruleCount ) + "&content=NONE"
                    xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )

                    listitem = xbmcgui.ListItem( label="%s" % ( translated[ 1 ][ 0 ] ) )
                    action = "plugin://plugin.library.node.editor?ltype=%s&type=editOperator&actionPath=" % ltype + actionPath + "&group=" + translated[ 1 ][ 1 ] + "&default=" + translated[ 1 ][ 2 ] + "&rule=" + str( ruleCount )
                    xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
                    if not ( translated[ 2 ][ 0 ] ) == "|NONE|":
                        listitem = xbmcgui.ListItem( label="%s" % ( translated[ 2 ][ 1 ] ) )
                        action = "plugin://plugin.library.node.editor?ltype=%s&type=editValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount )
                        xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
                        # Check if this match type can be browsed
                        if self.canBrowse( translated[ 0 ][ 1 ] ):
                            #listitem.addContextMenuItems( [(LANGUAGE(30107), "XBMC.RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content + ")" )], replaceItems = True )
                            listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
                            action = "plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=NONE"
                            xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
                        #self.browse( translated[ 0 ][ 1 ], content )
                    xbmcplugin.setContent(int(sys.argv[1]), 'files')
                    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
                    return
                ruleCount += 1
        except:
            print_exc()
            self.newNodeRule( actionPath, ruleNum )
            return

    def newNodeRule( self, actionPath, ruleNum ):
        # This function creates a new node rule, then re-calls the displayNodeRule function
        # Split the actionPath, to make things easier
        ( filePath, fileName ) = os.path.split( actionPath )
        # Open the rules file if it exists, else create it
        if ltype.startswith('video'):
            rulesfile = 'videorules.xml'
        else:
            rulesfile = 'musicrules.xml'
        if os.path.exists( os.path.join( DATAPATH, rulesfile ) ):
            tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
            root = tree.getroot()
        else:
            tree = xmltree.ElementTree( xmltree.Element( "rules" ) )
            root = tree.getroot()
        # See if we already have a element for the node we're parsing
        nodes = root.findall( "node" )
        ruleNode = None
        if nodes is not None:
            for node in nodes:
                if node.attrib.get( "name" ) == filePath:
                    ruleNode = node
                    break
        if ruleNode is None:
            # We couldn't find an existing element for the node we're parsing - so create one
            ruleNode = xmltree.SubElement( root, "node" )
            ruleNode.set( "name", filePath )
        # Create a new rule
        ruleTree = self._load_rules().getroot()
        elems = ruleTree.find( "matches" ).findall( "match" )
        match = "title"
        for elem in elems:
            # We've found the first match for this type
            match = elem.attrib.get( "name" )
            operator = elem.find( "operator" ).text
            break
        # Find the default operator for this match
        elems = ruleTree.find( "operators" ).findall( "group" )
        for elem in elems:
            if elem.attrib.get( "name" ) == operator:
                operator = elem.find( "operator" ).text
                break
        # Write the new rule
        newRule = xmltree.SubElement( ruleNode, "rule" )
        newRule.set( "field", match )
        newRule.set( "operator", operator )
        xmltree.SubElement( newRule, "value" )
        # Save the file
        self.indent( root )
        tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
        # Now add the rule to all views within the node
        dirs, files = xbmcvfs.listdir( filePath )
        for file in files:
            if file == "index.xml":
                continue
            elif file.endswith( ".xml" ):
                filename = os.path.join( filePath, file )
                try:
                    # Load the xml file
                    tree = xmltree.parse( filename )
                    root = tree.getroot()
                    rule = xmltree.SubElement( root, "rule" )
                    rule.set( "field", match )
                    rule.set( "operator", operator )
                    xmltree.SubElement( rule, "value" )
                    # Save the file
                    self.indent( root )
                    tree.write( filename, encoding="UTF-8" )
                except:
                    print_exc()
        # Re-call the displayNodeRule function
        self.displayNodeRule( actionPath, ruleNum )

    #def editNodeRule( self, actionPath, originalRule, newRule ):
    def editNodeRule( self, actionPath, ruleNum, match, operator, value ):
        ( filePath, fileName ) = os.path.split( actionPath )
        # Update the rule in the rules file
        if ltype.startswith('video'):
            rulesfile = 'videorules.xml'
        else:
            rulesfile = 'musicrules.xml'
        try:
            tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
            root = tree.getroot()
            nodes = root.findall( "node" )
            for node in nodes:
                if node.attrib.get( "name" ) == filePath:
                    ruleCount = 0
                    rules = node.findall( "rule" )
                    for rule in rules:
                        if ruleCount == int( ruleNum ):
                            # This is the rule we want to update
                            valueElem = rule.find( "value" )
                            if match is None:
                                match = rule.attrib.get( "field" )
                            if operator is None:
                                operator = rule.attrib.get( "operator" )
                            if value is None:
                                if valueElem is None:
                                    value = ""
                                else:
                                    value = valueElem.text
                            if value is None:
                                value = ""
                            newRule = self.translateRule( [ match, operator, value ] )
                            # Get the original rule
                            origMatch = rule.attrib.get( "field" )
                            origOperator = rule.attrib.get( "operator" )
                            if valueElem is None:
                                origValue = ""
                            else:
                                origValue = valueElem.text
                            originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
                            # Update the rule
                            rule.set( "field", newRule[ 0 ][ 1 ] )
                            rule.set( "operator", newRule[ 1 ][ 2 ] )
                            if len( newRule ) == 3:
                                if rule.find( "value" ) == None:
                                    # Create a new rule node
                                    xmltree.SubElement( rule, "value" ).text = newRule[ 2 ][ 0 ]
                                else:
                                    rule.find( "value" ).text = newRule[ 2 ][ 0 ]
                        ruleCount += 1
                        # Save the file
            self.indent( root )
            tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
        except:
            print_exc()
            return
        # Now update the views with the new rule
        dirs, files = xbmcvfs.listdir( filePath )
        for file in files:
            if file == "index.xml":
                continue
            elif file.endswith( ".xml" ):
                filename = os.path.join( filePath, file )
                # List the rules
                try:
                    # Load the xml file
                    tree = xmltree.parse( filename )
                    root = tree.getroot()
                    # Look for any rules
                    rules = root.findall( "rule" )
                    if rules is not None:
                        for rule in rules:
                            value = rule.find( "value" )
                            if value is not None and value.text is not None:
                                translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
                            else:
                                translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
                            if originalRule[ 0 ][ 1 ] == translated[ 0 ][ 1 ] and originalRule[ 1 ][ 2 ] == translated[ 1 ][ 2 ] and originalRule[ 2 ][ 0 ] == translated[ 2 ][ 0 ]:
                                # This is the right rule, update it
                                rule.set( "field", newRule[ 0 ][ 1 ] )
                                rule.set( "operator", newRule[ 1 ][ 2 ] )
                                if value is not None:
                                    value.text = newRule[ 2 ][ 0 ]
                                else:
                                    xmltree.SubElement( rule, "value" ).text = newRule[ 2 ][ 0 ]
                                break
                    # Save the file
                    self.indent( root )
                    tree.write( filename, encoding="UTF-8" )
                except:
                    print_exc()

    #def deleteNodeRule( self, actionPath, originalRule ):
    def deleteNodeRule( self, actionPath, ruleNum ):
        ( filePath, fileName ) = os.path.split( actionPath )
        # Delete the rule from the rules file
        if ltype.startswith('video'):
            rulesfile = 'videorules.xml'
        else:
            rulesfile = 'musicrules.xml'
        try:
            tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
            root = tree.getroot()
            nodes = root.findall( "node" )
            for node in nodes:
                if node.attrib.get( "name" ) == filePath:
                    ruleCount = 0
                    rules = node.findall( "rule" )
                    for rule in rules:
                        if ruleCount == int( ruleNum ):
                            # This is the rule we want to delete
                            # Translate the rule, so we can delete it from the views
                            valueElem = rule.find( "value" )
                            origMatch = rule.attrib.get( "field" )
                            origOperator = rule.attrib.get( "operator" )
                            if valueElem is None:
                                origValue = ""
                            else:
                                origValue = valueElem.text
                            originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
                            node.remove( rule )
                        ruleCount += 1
            # Save the file
            self.indent( root )
            tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
        except:
            print_exc()
            return
        # Now delete the rule from all the views
        dirs, files = xbmcvfs.listdir( filePath )
        for file in files:
            if file == "index.xml":
                continue
            elif file.endswith( ".xml" ):
                filename = os.path.join( filePath, file )
                # List the rules
                try:
                    # Load the xml file
                    tree = xmltree.parse( filename )
                    root = tree.getroot()
                    # Look for any rules
                    rules = root.findall( "rule" )
                    if rules is not None:
                        for rule in rules:
                            value = rule.find( "value" )
                            if value is not None and value.text is not None:
                                translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
                            else:
                                translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )

                            if originalRule[ 0 ][ 1 ] == translated[ 0 ][ 1 ] and originalRule[ 1 ][ 2 ] == translated[ 1 ][ 2 ] and originalRule[ 2 ][ 0 ] == translated[ 2 ][ 0 ]:
                                # This is the right rule, delete it
                                root.remove( rule )
                                break
                    # Save the file
                    self.indent( root )
                    tree.write( filename, encoding="UTF-8" )
                except:
                    print_exc()

    def deleteAllNodeRules( self, actionPath ):
        if ltype.startswith('video'):
            rulesfile = 'videorules.xml'
        else:
            rulesfile = 'musicrules.xml'
        try:
            # Remove all rules for this parent node from the rules file
            tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
            root = tree.getroot()
            nodes = root.findall( "node" )
            for node in nodes:
                if node.attrib.get( "name" ) == actionPath:
                    root.remove( node )
            # Write the updated file
            self.indent( root )
            tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
        except:
            print_exc()

    def isNodeRule( self, viewRule, actionPath ):
        if actionPath.endswith( "index.xml" ):
            return False
        if self.nodeRules is None:
            self.nodeRules = []
            # Load all the node rules for current directory
            ( filePath, fileName ) = os.path.split( actionPath )
            self.loadNodeRules( filePath )
        # If there are no node rules, return False
        if len( self.nodeRules ) == 0:
            return False

        # Compare the passed in rule with those in self.nodeRules
        count = 0
        for nodeRule in self.nodeRules:
            if nodeRule[0] == viewRule[0][1] and nodeRule[1] == viewRule[1][2] and nodeRule[2] == viewRule[2][0]:
                # Rule matches
                self.nodeRules.pop( count )
                return True
            count += 1
        return False

    def addAllNodeRules( self, actionPath, root ):
        if self.nodeRules is None:
            self.loadNodeRules( actionPath )
        if len( self.nodeRules ) == 0:
            return
        for nodeRule in self.nodeRules:
            rule = xmltree.SubElement( root, "rule" )
            rule.set( "field", nodeRule[ 0 ] )
            rule.set( "operator", nodeRule[ 1 ] )
            xmltree.SubElement( rule, "value" ).text = nodeRule[ 2 ]

    def getNodeRules( self, actionPath ):
        ( filePath, fileName ) = os.path.split( actionPath )
        if self.nodeRules is None:
            self.loadNodeRules( filePath )
        if len( self.nodeRules ) == 0:
            return None
        else:
            return self.nodeRules

    def loadNodeRules( self, actionPath ):
        self.nodeRules = []
        # Load all the node rules for current directory
        #actionPath = os.path.join( actionPath, "index.xml" )
        if ltype.startswith('video'):
            filename = os.path.join( DATAPATH, "videorules.xml" )
        else:
            filename = os.path.join( DATAPATH, "musicrules.xml" )
        if os.path.exists( filename ):
            try:
                # Load the xml file
                tree = xmltree.parse( filename )
                root = tree.getroot()
                # Find the node element for this path
                nodes = root.findall( "node" )
                if nodes is None:
                    return
                ruleNode = None
                for node in nodes:
                    if node.attrib.get( "name" ) == actionPath:
                        ruleNode = node
                        break
                if ruleNode is None:
                    # There don't appear to be any rules
                    return
                # Look for any rules
                rules = ruleNode.findall( "rule" )
                if rules is not None:
                    for rule in rules:
                        value = rule.find( "value" )
                        if value is not None and value.text is not None:
                            translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
                        else:
                            translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
                        # Save the rule
                        self.nodeRules.append( [ translated[0][1], translated[1][2], translated[2][0] ] )
            except:
                print_exc()

    def moveNodeRuleToAppdata( self, path, actionPath ):
        #BETA2 ONLY CODE
        # This function will move any parent node rules out of the index.xml, and into the rules file in the plugins appdata folder
        # Open the rules file if it exists, else create it
        if ltype.startswith('video'):
            rulesfile = 'videorules.xml'
        else:
            rulesfile = 'musicrules.xml'
        if os.path.exists( os.path.join( DATAPATH, rulesfile ) ):
            ruleTree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
            ruleRoot = ruleTree.getroot()
        else:
            ruleTree = xmltree.ElementTree( xmltree.Element( "rules" ) )
            ruleRoot = ruleTree.getroot()
        # See if we already have a element for the node we're parsing
        nodes = ruleRoot.findall( "node" )
        ruleNode = None
        if nodes is not None:
            for node in nodes:
                if node.attrib.get( "name" ) == path:
                    ruleNode = node
                    break
        if ruleNode is None:
            # We couldn't find an existing element for the node we're parsing - so create one
            ruleNode = xmltree.SubElement( ruleRoot, "node" )
            ruleNode.set( "name", path )
        try:
            tree = xmltree.parse( actionPath )
            root = tree.getroot()
            rules = root.findall( "rule" )
            if rules is not None:
                for rule in rules:
                    # Create a new rule in the ruleTree
                    newRule = xmltree.SubElement( ruleNode, "rule" )
                    newRule.set( "field", rule.attrib.get( "field" ) )
                    newRule.set( "operator", rule.attrib.get( "operator" ) )
                    value = rule.find( "value" )
                    if value is not None:
                        xmltree.SubElement( newRule, "value" ).text = value.text
                    # Delete the rule from the tree
                    root.remove( rule )
            # Write both files
            self.indent( root )
            tree.write( actionPath, encoding="UTF-8" )
            self.indent( ruleRoot )
            ruleTree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
        except:
            print_exc()
        #/BETA2 ONLY CODE

    # Functions for browsing for value
    def canBrowse( self, match, content = None ):
        # Check whether the match type allows browsing
        if content == "NONE":
            content = None
        # Load the rules
        tree = self._load_rules()
        elems = tree.getroot().find( "matches" ).findall( "match" )
        for elem in elems:
            if elem.attrib.get( "name" ) == match:
                canBrowse = elem.find( "browse" )
                if canBrowse is None:
                    # This match type is marked as non-browsable
                    return False
                if content is None:
                    # If we haven't been passed a content type, allow to browse all
                    return True
                canBrowse = elem.find( content )
                if canBrowse is None:
                    # We can't browse for this content type
                    return False
                # We can browse this content type
                return True
        return False

    def browse( self, actionPath, ruleNum, match, content = None ):
        # This function launches the browser for the given match and content type
        if content is None or content == "" or content == "NONE":
            if match != "path" and match != "playlist":
                # No content parameter passed, so check what contents are valid for
                # this type
                tree = self._load_rules()
                elems = tree.getroot().find( "matches" ).findall( "match" )
                matches = {}
                for elem in elems:
                    if elem.attrib.get( "name" ) == match:
                        if ltype.startswith('video'):
                            matches["movies"] = elem.find( "movies" )
                            matches["tvshows"] = elem.find( "tvshows" )
                            matches["episodes"] = elem.find( "episodes" )
                            matches["musicvideos"] = elem.find( "musicvideos" )
                        else:
                            matches["artists"] = elem.find( "artists" )
                            matches["albums"] = elem.find( "albums" )
                            matches["songs"] = elem.find( "songs" )
                        break
                matchesList = []
                matchesValue = []
                # Generate a list of the available content types
                elems = tree.getroot().find( "content" ).findall( "type" )
                for elem in elems:
                    if matches[ elem.text ] is not None:
                        matchesList.append( xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) ) )
                        matchesValue.append( elem.text )
                if len( matchesList ) == 0:
                    return
                if len( matchesList ) == 1:
                    # Only one returned, no point offering a choice of content type
                    content = matchesValue[ 0 ]
                else:
                    # Display a select dialog so user can choose their content
                    selectedContent = xbmcgui.Dialog().select( LANGUAGE( 30308 ), matchesList )
                    # If the user selected nothing...
                    if selectedContent == -1:
                        return
                    content = matchesValue[ selectedContent ]
        if match == "title":
            self.createBrowseNode( content, None )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "tvshow":
            if content == "episodes":
                content = "tvshows"
            self.createBrowseNode( content, None )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "genre":
            self.createBrowseNode( content, "genres" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "album":
            self.createBrowseNode( content, "none" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "country":
            self.createBrowseNode( content, "countries" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "year":
            if content == "episodes":
                content = "tvshows"
            self.createBrowseNode( content, "years" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "artist":
            self.createBrowseNode( content, "artists" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "director":
            self.createBrowseNode( content, "directors" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "actor":
            if content == "episodes":
                content = "tvshows"
            self.createBrowseNode( content, "actors" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "studio":
            self.createBrowseNode( content, "studios" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "path":
            returnVal = xbmcgui.Dialog().browse(0, self.niceMatchName( match ), ltype )
        elif match == "set":
            self.createBrowseNode( content, "sets" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "tag":
            self.createBrowseNode( content, "tags" )
            returnVal = self.browser( self.niceMatchName( match ) )
        elif match == "playlist":
            returnVal = self.browserPlaylist( self.niceMatchName( match ) )
        elif match == "virtualfolder":
            returnVal = self.browserPlaylist( self.niceMatchName( match ) )
        elif match == "albumartist":
            self.createBrowseNode( content, "artists" )
            returnVal = self.browser( self.niceMatchName( match ) )
        try:
            # Delete any fake node
            xbmcvfs.delete( os.path.join( xbmc.translatePath( "special://profile".decode('utf-8') ), "library", ltype, "plugin.library.node.editor", "temp.xml" ) )
        except:
            print_exc()
        if returnVal is not None:
            returnVal = returnVal.decode( "utf-8" )
        self.writeUpdatedRule( actionPath, ruleNum, value = returnVal )

    def niceMatchName( self, match ):
        # This function retrieves the translated label for a given match
        tree = self._load_rules()
        elems = tree.getroot().find( "matches" ).findall( "match" )
        matches = {}
        for elem in elems:
            if elem.attrib.get( "name" ) == match:
                return xbmc.getLocalizedString( int( elem.find( "label" ).text ) )

    def createBrowseNode( self, content, grouping = None ):
        # This function creates a fake node which we'll use for browsing
        targetDir = os.path.join( xbmc.translatePath( "special://profile".decode('utf-8') ), "library", ltype, "plugin.library.node.editor" )
        if not os.path.exists( targetDir ):
            xbmcvfs.mkdirs( targetDir )
        # Create a new etree
        tree = xmltree.ElementTree(xmltree.Element( "node" ) )
        root = tree.getroot()
        root.set( "type", "filter" )
        xmltree.SubElement( root, "label" ).text = "Fake node used for browsing"
        xmltree.SubElement( root, "content" ).text = content
        if grouping is not None:
            xmltree.SubElement( root, "group" ).text = grouping
        else:
            order = xmltree.SubElement( root, "order" )
            order.text = "sorttitle"
            order.set( "direction", "ascending" )
        self.indent( root )
        tree.write( os.path.join( targetDir, "temp.xml" ), encoding="UTF-8" )

    def browser( self, title ):
        # Browser instance used by majority of browses
        json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "thumbnail"], "directory": "library://%s/plugin.library.node.editor/temp.xml", "media": "files" } }' % ltype)
        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 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 )
                values.append( item[ "label" ] )
        # 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
        return values[ selectedItem ]

    def browserPlaylist( self, title ):
        # Browser instance used by playlists
        json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "thumbnail"], "directory": "special://%splaylists/", "media": "files" } }' % ltype)
        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 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 )
                values.append( item[ "label" ] )
        # 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
        return values[ selectedItem ]

    # 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
