#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
    script.skin.helper.service
    Helper service and scripts for Kodi skins
    skinsettings.py
    several helpers that allows skinners to have custom dialogs for their skin settings and constants
'''

import xbmc
import xbmcvfs
import xbmcgui
import xbmcaddon
from utils import ADDON_ID, try_decode, getCondVisibility
from dialogselect import DialogSelect
from xml.dom.minidom import parse
import xml.etree.ElementTree as xmltree
import os
import time

class SkinSettings:
    '''several helpers that allows skinners to have custom dialogs for their skin settings and constants'''
    params = {}
    skinsettings = {}

    def __init__(self):
        '''Initialization'''
        self.win = xbmcgui.Window(10000)
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.skinsettings = self.get_skin_settings()
        self.skin_constants, self.skin_variables = self.get_skin_constants()

    def __del__(self):
        '''Cleanup Kodi Cpython instances'''
        del self.win
        del self.addon

    def write_skin_constants(self, constants=None, variables=None):
        '''writes the list of all skin constants'''
        addonpath = xbmc.translatePath(os.path.join("special://skin/", 'addon.xml').encode("utf-8")).decode("utf-8")
        addon = xmltree.parse(addonpath)
        extensionpoints = addon.findall("extension")
        for extensionpoint in extensionpoints:
            if extensionpoint.attrib.get("point") == "xbmc.gui.skin":
                resolutions = extensionpoint.findall("res")
                for resolution in resolutions:
                    includes_file = xbmc.translatePath(
                        os.path.join(
                            "special://skin/",
                            try_decode(
                                resolution.attrib.get("folder")),
                            "script-skin_helper_service-includes.xml").encode("utf-8")).decode('utf-8')
                    tree = xmltree.ElementTree(xmltree.Element("includes"))
                    root = tree.getroot()
                    if constants:
                        for key, value in constants.iteritems():
                            if value:
                                child = xmltree.SubElement(root, "constant")
                                child.text = value
                                child.attrib["name"] = key
                                # also write to skin strings
                                xbmc.executebuiltin(
                                    "Skin.SetString(%s,%s)" %
                                    (key.encode("utf-8"), value.encode("utf-8")))
                    if variables:
                        for key, value in variables.iteritems():
                            if value:
                                child = xmltree.SubElement(root, "variable")
                                child.attrib["name"] = key
                                child2 = xmltree.SubElement(child, "value")
                                child2.text = value
                    self.indent_xml(tree.getroot())
                    xmlstring = xmltree.tostring(tree.getroot(), encoding="utf-8")
                    fileobj = xbmcvfs.File(includes_file, 'w')
                    fileobj.write(xmlstring)
                    fileobj.close()
        xbmc.executebuiltin("ReloadSkin()")

    @staticmethod
    def get_skin_constants():
        '''gets a list of all skin constants as set in the special xml file'''
        all_constants = {}
        all_variables = {}
        addonpath = xbmc.translatePath(os.path.join("special://skin/", 'addon.xml').encode("utf-8")).decode("utf-8")
        addon = xmltree.parse(addonpath)
        extensionpoints = addon.findall("extension")
        for extensionpoint in extensionpoints:
            if extensionpoint.attrib.get("point") == "xbmc.gui.skin":
                resolutions = extensionpoint.findall("res")
                for resolution in resolutions:
                    includes_file = xbmc.translatePath(
                        os.path.join(
                            "special://skin/",
                            try_decode(
                                resolution.attrib.get("folder")),
                            "script-skin_helper_service-includes.xml").encode("utf-8")).decode('utf-8')
                    if xbmcvfs.exists(includes_file):
                        doc = parse(includes_file)
                        listing = doc.documentElement.getElementsByTagName('constant')
                        # constants
                        for item in listing:
                            name = try_decode(item.attributes['name'].nodeValue)
                            value = try_decode(item.firstChild.nodeValue)
                            all_constants[name] = value
                        # variables
                        listing = doc.documentElement.getElementsByTagName('variable')
                        for item in listing:
                            name = try_decode(item.attributes['name'].nodeValue)
                            value_item = item.getElementsByTagName('value')[0]
                            value = try_decode(value_item.firstChild.nodeValue)
                            all_variables[name] = value
        return all_constants, all_variables

    def update_skin_constants(self, new_constants):
        '''update skin constants if needed'''
        update_needed = False
        if new_constants:
            for key, value in new_constants.iteritems():
                if key in self.skin_constants:
                    if self.skin_constants.get(key) != value:
                        update_needed = True
                        self.skin_constants[key] = value
                else:
                    update_needed = True
                    self.skin_constants[key] = value
        if update_needed:
            self.write_skin_constants(self.skin_constants, self.skin_variables)

    def set_skin_constant(self, setting="", window_header="", value=""):
        '''set a skin constant'''
        cur_values = self.skin_constants
        if not value:
            cur_value = cur_values.get(setting, "emptyconstant")
            value = self.set_skin_setting(setting, window_header, "", cur_value)[0]
        result = {setting: value}
        self.update_skin_constants(result)

    def set_skin_constants(self, settings, values):
        '''set multiple constants at once'''
        result = {}
        for count, setting in enumerate(settings):
            result[setting] = values[count]
        self.update_skin_constants(result)

    def set_skin_variable(self, key, value):
        '''set skin variable in constants file'''
        if self.skin_variables.get(key, "") != value:
            self.skin_variables[key] = value
            self.write_skin_constants(self.skin_constants, self.skin_variables)

    @staticmethod
    def get_skin_settings():
        '''get the complete list of all settings defined in the special skinsettings file'''
        all_skinsettings = {}
        settings_file = xbmc.translatePath('special://skin/extras/skinsettings.xml').decode("utf-8")
        if xbmcvfs.exists(settings_file):
            doc = parse(settings_file)
            listing = doc.documentElement.getElementsByTagName('setting')
            for item in listing:
                skinsetting_id = item.attributes["id"].nodeValue.decode("utf-8")
                if "$" in skinsetting_id:
                    skinsetting_id = xbmc.getInfoLabel(skinsetting_id).decode("utf-8")
                if all_skinsettings.get(skinsetting_id):
                    skinsetting_values = all_skinsettings[skinsetting_id]
                else:
                    skinsetting_values = []
                skinsettingvalue = {}
                skinsettingvalue["value"] = item.attributes["value"].nodeValue.decode("utf-8")
                # optional attributes
                for key in ["label", "condition", "description", "default", "icon", "constantdefault"]:
                    value = ""
                    try:
                        value = item.attributes[key].nodeValue
                        if "$" in value:
                            value = xbmc.getInfoLabel(value).decode("utf-8")
                        else:
                            value = value.decode("utf-8")
                    except Exception:
                        pass
                    skinsettingvalue[key] = value

                # optional onselect actions for this skinsetting value
                onselectactions = []
                for action in item.getElementsByTagName('onselect'):
                    selectaction = {}
                    selectaction["condition"] = action.attributes['condition'].nodeValue.decode("utf-8")
                    command = action.firstChild.nodeValue
                    if "$" in command:
                        command = xbmc.getInfoLabel(command).decode("utf-8")
                    else:
                        command = command.decode("utf-8")
                    selectaction["command"] = command
                    onselectactions.append(selectaction)
                skinsettingvalue["onselectactions"] = onselectactions

                # optional multiselect options for this skinsetting value
                settingoptions = []
                for option in item.getElementsByTagName('option'):
                    settingoption = {}
                    for key in ["id", "label", "condition", "description", "default", "icon", "value"]:
                        value = ""
                        try:
                            value = option.attributes[key].nodeValue
                            if value.startswith("$"):
                                value = xbmc.getInfoLabel(value).decode("utf-8")
                            else:
                                value = value.decode("utf-8")
                        except Exception:
                            pass
                        settingoption[key] = value
                    settingoptions.append(settingoption)
                skinsettingvalue["settingoptions"] = settingoptions

                skinsetting_values.append(skinsettingvalue)
                all_skinsettings[skinsetting_id] = skinsetting_values
        return all_skinsettings

    def set_skin_setting(self, setting="", window_header="", sublevel="",
                         cur_value_label="", skip_skin_string=False, original_id="", cur_value=""):
        '''allows the skinner to use a select dialog to set all kind of skin settings'''
        if not cur_value_label:
            cur_value_label = xbmc.getInfoLabel("Skin.String(%s.label)" % setting).decode("utf-8")
        if not cur_value:
            cur_value = xbmc.getInfoLabel("Skin.String(%s)" % setting).decode("utf-8")
        rich_layout = False
        listitems = []
        if sublevel:
            listitem = xbmcgui.ListItem(label="..", iconImage="DefaultFolderBack.png")
            listitem.setProperty("icon", "DefaultFolderBack.png")
            listitem.setProperty("value", "||BACK||")
            listitems.append(listitem)
            all_values = self.skinsettings.get(sublevel, [])
        elif original_id:
            all_values = self.skinsettings.get(original_id, [])
        else:
            all_values = self.skinsettings.get(setting, [])
        for item in all_values:
            if not item["condition"] or getCondVisibility(item["condition"]):
                value = item["value"]
                icon = item["icon"]
                if icon:
                    rich_layout = True
                label = item["label"]
                if "%" in label:
                    label = label % value
                if value == "||MULTISELECT||" or item["settingoptions"]:
                    return self.multi_select(item["settingoptions"], window_header)
                listitem = xbmcgui.ListItem(label, iconImage=icon, label2=item["description"])
                listitem.setProperty("value", value)
                listitem.setProperty("icon", icon)
                listitem.setProperty("description", item["description"])
                listitem.setProperty("onselectactions", repr(item["onselectactions"]))
                listitems.append(listitem)

        # show select dialog
        dialog = DialogSelect("DialogSelect.xml", "", listing=listitems, windowtitle=window_header,
                              richlayout=rich_layout, autofocuslabel=cur_value_label)
        dialog.doModal()
        selected_item = dialog.result
        del dialog
        # process the results
        if selected_item:
            value = selected_item.getProperty("value").decode("utf-8")
            label = selected_item.getLabel().decode("utf-8")
            if value.startswith("||SUBLEVEL||"):
                sublevel = value.replace("||SUBLEVEL||", "")
                self.set_skin_setting(setting, window_header, sublevel)
            elif value == "||BACK||":
                self.set_skin_setting(setting, window_header)
            else:
                if value == "||BROWSEIMAGE||":
                    value = self.save_skin_image(setting, True, label)
                if value == "||BROWSESINGLEIMAGE||":
                    value = self.save_skin_image(setting, False, label)
                if value == "||BROWSEMULTIIMAGE||":
                    value = self.save_skin_image(setting, True, label)
                if value == "||PROMPTNUMERIC||":
                    value = xbmcgui.Dialog().input(label, cur_value, 1).decode("utf-8")
                if value == "||PROMPTSTRING||":
                    value = xbmcgui.Dialog().input(label, cur_value, 0).decode("utf-8")
                if value == "||PROMPTSTRINGASNUMERIC||":
                    validinput = False
                    while not validinput:
                        try:
                            value = xbmcgui.Dialog().input(label, cur_value, 0).decode("utf-8")
                            valueint = int(value)
                            validinput = True
                            del valueint
                        except Exception:
                            value = xbmcgui.Dialog().notification("Invalid input", "Please enter a number...")

                # write skin strings
                if not skip_skin_string and value != "||SKIPSTRING||":
                    xbmc.executebuiltin("Skin.SetString(%s,%s)" %
                                        (setting.encode("utf-8"), value.encode("utf-8")))
                    xbmc.executebuiltin("Skin.SetString(%s.label,%s)" %
                                        (setting.encode("utf-8"), label.encode("utf-8")))
                # process additional actions
                onselectactions = selected_item.getProperty("onselectactions")
                if onselectactions:
                    for action in eval(onselectactions):
                        if not action["condition"] or getCondVisibility(action["condition"]):
                            xbmc.executebuiltin(action["command"])
                return (value, label)
        else:
            return (None, None)

    def correct_skin_settings(self):
        '''correct any special skin settings'''
        skinconstants = {}
        for settingid, settingvalues in self.skinsettings.iteritems():
            curvalue = xbmc.getInfoLabel("Skin.String(%s)" % settingid).decode("utf-8")
            curlabel = xbmc.getInfoLabel("Skin.String(%s.label)" % settingid).decode("utf-8")
            # first check if we have a sublevel
            if settingvalues and settingvalues[0]["value"].startswith("||SUBLEVEL||"):
                sublevel = settingvalues[0]["value"].replace("||SUBLEVEL||", "")
                settingvalues = self.skinsettings.get(sublevel)
            for settingvalue in settingvalues:
                value = settingvalue["value"]
                label = settingvalue["label"]
                if "%" in label:
                    label = label % value

                # only correct the label if value already set
                if value and value == curvalue:
                    xbmc.executebuiltin(
                        "Skin.SetString(%s.label,%s)" %
                        (settingid.encode("utf-8"), label.encode("utf-8")))

                # set the default value if current value is empty
                if not (curvalue or curlabel):
                    if settingvalue["default"] and getCondVisibility(settingvalue["default"]):
                        xbmc.executebuiltin(
                            "Skin.SetString(%s.label,%s)" %
                            (settingid.encode("utf-8"), label.encode("utf-8")))
                        xbmc.executebuiltin(
                            "Skin.SetString(%s,%s)" %
                            (settingid.encode("utf-8"), value.encode("utf-8")))
                        # additional onselect actions
                        for action in settingvalue["onselectactions"]:
                            if action["condition"] and getCondVisibility(action["condition"]):
                                command = action["command"]
                                if "$" in command:
                                    command = xbmc.getInfoLabel(command)
                                xbmc.executebuiltin(command.encode("utf-8"))

                # process any multiselects
                for option in settingvalue["settingoptions"]:
                    settingid = option["id"]
                    if (not xbmc.getInfoLabel("Skin.String(defaultset_%s)" % settingid) and option["default"] and
                            getCondVisibility(option["default"])):
                        xbmc.executebuiltin("Skin.SetBool(%s)" % settingid)
                    xbmc.executebuiltin("Skin.SetString(defaultset_%s,defaultset)" % settingid)

                # set the default constant value if current value is empty
                if (not curvalue and settingvalue["constantdefault"] and
                        getCondVisibility(settingvalue["constantdefault"])):
                    skinconstants[settingid] = value

        # update skin constants if needed only
        if skinconstants:
            self.update_skin_constants(skinconstants)

    def save_skin_image(self, skinstring="", multi_image=False, header=""):
        '''let the user select an image and save it to addon_data for easy backup'''
        cur_value = xbmc.getInfoLabel("Skin.String(%s)" % skinstring).decode("utf-8")
        cur_value_org = xbmc.getInfoLabel("Skin.String(%s.org)" % skinstring).decode("utf-8")

        if not multi_image:
            # single image (allow copy to addon_data)
            value = xbmcgui.Dialog().browse(2, header, 'files', '', True, True, cur_value_org).decode("utf-8")
            if value:
                ext = value.split(".")[-1]
                newfile = (u"special://profile/addon_data/%s/custom_images/%s.%s"
                           % (xbmc.getSkinDir(), skinstring + time.strftime("%Y%m%d%H%M%S", time.gmtime()), ext))
                if "special://profile/addon_data/%s/custom_images/" % xbmc.getSkinDir() in cur_value:
                    xbmcvfs.delete(cur_value)
                xbmcvfs.copy(value, newfile)
                xbmc.executebuiltin("Skin.SetString(%s.org,%s)" % (skinstring.encode("utf-8"), value.encode("utf-8")))
                value = newfile
        else:
            # multi image
            if not cur_value_org.startswith("$"):
                delim = "\\" if "\\" in cur_value_org else "/"
                curdir = cur_value_org.rsplit(delim, 1)[0] + delim
            else:
                curdir = ""
            value = xbmcgui.Dialog().browse(0, self.addon.getLocalizedString(32005),
                                            'files', '', True, True, curdir).decode("utf-8")
        return value

    def set_skinshortcuts_property(self, setting="", window_header="", property_name=""):
        '''allows the user to make a setting for skinshortcuts using the special skinsettings dialogs'''
        cur_value = xbmc.getInfoLabel(
            "$INFO[Container(211).ListItem.Property(%s)]" %
            property_name).decode("utf-8")
        cur_value_label = xbmc.getInfoLabel(
            "$INFO[Container(211).ListItem.Property(%s.name)]" %
            property_name).decode("utf-8")
        if setting == "||IMAGE||":
            # select image
            label, value = self.select_image(setting, allow_multi=True, windowheader=windowheader)
        if setting:
            # use skin settings select dialog
            value, label = self.set_skin_setting(
                setting, window_header=window_header, sublevel="", cur_value_label=cur_value_label,
                skip_skin_string=True, cur_value=cur_value)
        else:
            # manually input string
            if not cur_value:
                cur_value = "None"
            value = xbmcgui.Dialog().input(window_header, cur_value, type=xbmcgui.INPUT_ALPHANUM).decode("utf-8")
            label = value
        if label:
            from skinshortcuts import set_skinshortcuts_property
            set_skinshortcuts_property(property_name, value, label)

    def select_image(self, skinstring, allow_multi=True, windowheader="",
                     resource_addon="", skinhelper_backgrounds=False, current_value=""):
        '''helper which lets the user select an image or imagepath from resourceaddons or custom path'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        images = []
        if not windowheader:
            windowheader = self.addon.getLocalizedString(32020)
        if not current_value:
            current_value = xbmc.getInfoLabel("Skin.String(%s.label)" % skinstring).decode("utf-8")

        # none option
        images.append((self.addon.getLocalizedString(32001), "", "", "DefaultAddonNone.png"))
        # custom single
        images.append((self.addon.getLocalizedString(32004), "", "", "DefaultAddonPicture.png"))
        # custom multi
        if allow_multi:
            images.append((self.addon.getLocalizedString(32005), "", "", "DefaultFolder.png"))

        # backgrounds supplied in our special skinsettings.xml file
        skinimages = self.skinsettings
        if skinimages.get(skinstring):
            for item in skinimages[skinstring]:
                if not item["condition"] or getCondVisibility(item["condition"]):
                    images.append((item["label"], item["value"], item["description"], item["icon"]))

        # backgrounds provided by skinhelper
        if skinhelper_backgrounds:
            from skinshortcuts import get_skinhelper_backgrounds
            for label, image in get_skinhelper_backgrounds():
                images.append((label, image, "Skin Helper Backgrounds", xbmc.getInfoLabel(image)))

        # resource addon images
        if resource_addon:
            from resourceaddons import get_resourceimages
            images += get_resourceimages(resource_addon)

        # create listitems
        listitems = []
        for label, imagepath, label2, icon in images:
            listitem = xbmcgui.ListItem(label=label, label2=label2, iconImage=icon)
            listitem.setPath(imagepath)
            listitems.append(listitem)

        # show select dialog with choices
        dialog = DialogSelect("DialogSelect.xml", "", listing=listitems, windowtitle=windowheader, richlayout=True,
                              getmorebutton=resource_addon, autofocuslabel=current_value)
        xbmc.executebuiltin("Dialog.Close(busydialog)")
        dialog.doModal()
        result = dialog.result
        del dialog
        if isinstance(result, bool):
            if result:
                # refresh listing requested by getmore button
                return self.select_image(skinstring, allow_multi, windowheader,
                                         resource_addon, skinhelper_backgrounds, current_value)
        elif result:
            label = result.getLabel().decode("utf-8")
            if label == self.addon.getLocalizedString(32004):
                # browse for single image
                custom_image = SkinSettings().save_skin_image(skinstring, False, self.addon.getLocalizedString(32004))
                if custom_image:
                    result.setPath(custom_image)
                else:
                    return self.selectimage()
            elif label == self.addon.getLocalizedString(32005):
                # browse for image path
                custom_image = SkinSettings().save_skin_image(skinstring, True, self.addon.getLocalizedString(32005))
                if custom_image:
                    result.setPath(custom_image)
                else:
                    return self.selectimage()
            # return values
            return (result.getLabel().decode("utf-8"), result.getfilename().decode("utf-8"))
        # return empty values
        return ("", "")

    @staticmethod
    def multi_select(options, window_header=""):
        '''allows the user to choose from multiple options'''
        listitems = []
        for option in options:
            if not option["condition"] or getCondVisibility(option["condition"]):
                listitem = xbmcgui.ListItem(label=option["label"], label2=option["description"])
                listitem.setProperty("id", option["id"])
                if getCondVisibility("Skin.HasSetting(%s)" % option["id"]) or (not xbmc.getInfoLabel(
                        "Skin.String(defaultset_%s)" % option["id"]) and getCondVisibility(option["default"])):
                    listitem.select(selected=True)
                listitems.append(listitem)
        # show select dialog
        dialog = DialogSelect("DialogSelect.xml", "", listing=listitems, windowtitle=window_header, multiselect=True)
        dialog.doModal()
        result = dialog.result
        if result:
            for item in result:
                if item.isSelected():
                    # option is enabled
                    xbmc.executebuiltin("Skin.SetBool(%s)" % item.getProperty("id"))
                else:
                    # option is disabled
                    xbmc.executebuiltin("Skin.Reset(%s)" % item.getProperty("id"))
            # always set additional prop to define the defaults
            xbmc.executebuiltin("Skin.SetString(defaultset_%s,defaultset)" % item.getProperty("id"))
        del dialog

    def indent_xml(self, elem, level=0):
        '''helper to properly indent xml strings to file'''
        text_i = "\n" + level * "\t"
        if len(elem):
            if not elem.text or not elem.text.strip():
                elem.text = text_i + "\t"
            if not elem.tail or not elem.tail.strip():
                elem.tail = text_i
            for elem in elem:
                self.indent_xml(elem, level + 1)
            if not elem.tail or not elem.tail.strip():
                elem.tail = text_i
        else:
            if level and (not elem.tail or not elem.tail.strip()):
                elem.tail = text_i