import json

from maya import cmds

from ngSkinTools2 import signal
from ngSkinTools2.api.log import getLogger
from ngSkinTools2.api.python_compatibility import Object, is_string
from ngSkinTools2.observableValue import ObservableValue

log = getLogger("plugin")


class Value(Object):
    def __init__(self, value=None):
        self.value = value

    def get(self):
        return self.value

    def set(self, value):
        self.value = value

    def getInt(self):
        try:
            return int(self.get())
        except:
            return 0


class PersistentValue(Value):
    """
    persistent value can store itself into Maya's "option vars" array
    """

    def __init__(self, name, default_value=None, prefix=None):
        Value.__init__(self)

        if prefix is None:
            prefix = VAR_OPTION_PREFIX
        self.name = prefix + name
        self.default_value = default_value
        self.value = load_option(self.name, self.default_value)

    def set(self, value):
        Value.set(self, value)
        save_option(self.name, self.value)


class PersistentDict(Object):
    def __init__(self, name, default_values=None):
        if default_values is None:
            default_values = {}
        self.persistence = PersistentValue(name=name, default_value=json.dumps(default_values))

    def __get_values(self):
        # type: () -> dict
        return json.loads(self.persistence.get())

    def __getitem__(self, item):
        return self.__get_values().get(item, None)

    def __setitem__(self, key, value):
        v = self.__get_values()
        v[key] = value
        self.persistence.set(json.dumps(v))


def load_option(var_name, default_value):
    """
    loads value from optionVar
    """

    from ngSkinTools2 import BATCH_MODE

    if BATCH_MODE:
        return default_value

    if cmds.optionVar(exists=var_name):
        return cmds.optionVar(q=var_name)

    return default_value


def save_option(varName, value):
    """
    saves option via optionVar
    """
    from ngSkinTools2 import BATCH_MODE

    if BATCH_MODE:
        return

    # variable does not exist, attempt to save it
    key = None
    if isinstance(value, float):
        key = 'fv'
    elif isinstance(value, int):
        key = 'iv'
    elif is_string(value):
        key = 'sv'
    else:
        raise ValueError("could not save option %s: invalid value %r" % (varName, value))

    kvargs = {key: (varName, value)}
    log.info("saving optionvar: %r", kvargs)
    cmds.optionVar(**kvargs)


VAR_OPTION_PREFIX = 'ngSkinTools2_'


def delete_custom_options():
    for varName in cmds.optionVar(list=True):
        if varName.startswith(VAR_OPTION_PREFIX):
            cmds.optionVar(remove=varName)

    cmds.windowPref('MirrorWeightsWindow', ra=True)


def build_config_property(name, default_value, doc=''):
    return property(lambda self: self.__get_value__(name, default_value), lambda self, val: self.__set_value__(name, val), doc=doc)


class Config(Object):
    """
    Maya-wide settings for ngSkinTools2
    """

    mirrorInfluencesDefaults = build_config_property('mirrorInfluencesDefaults', "{}")  # type: string

    InfluencesSortUnsorted = 'unsorted'
    InfluencesSortDescending = 'descending'

    def __init__(self):
        from ngSkinTools2.api.mirror import MirrorOptions

        self.__storage__ = PersistentValue("config", "{}")
        self.__state__ = self.load()

        self.unique_client_id = PersistentValue('updateCheckUniqueClientId')

        self.checkForUpdatesAtStartup = self.build_observable_value('checkForUpdatesAtStartup', True)
        self.influences_show_used_influences_only = self.build_observable_value("influencesViewShowUsedInfluencesOnly", False)

        # influences sort is not a simple "true/false" flag to allow different sorting methods in the future.
        self.influences_sort = self.build_observable_value("influencesSort", Config.InfluencesSortUnsorted)

        default_mirror_options = MirrorOptions()
        self.mirror_direction = self.build_observable_value("mirrorDirection", default_mirror_options.direction)
        self.mirror_dq = self.build_observable_value("mirrorDq", default_mirror_options.mirrorDq)
        self.mirror_mask = self.build_observable_value("mirrorMask", default_mirror_options.mirrorMask)
        self.mirror_weights = self.build_observable_value("mirrorWeights", default_mirror_options.mirrorWeights)

    def __get_value__(self, name, default_value):
        result = self.__state__.get(name, default_value)
        log.info("config: return %s=%r", name, result)
        return result

    def __set_value__(self, name, value):
        log.info("config: save %s=%r", name, value)
        self.__state__[name] = value
        self.save()

    def build_observable_value(self, name, default_value):
        """
        builds ObservableValue that is loaded and persisted into config when changed
        :type name: str
        :rtype: ngSkinTools2.observableValue.ObservableValue
        """
        result = ObservableValue(self.__get_value__(name=name, default_value=default_value))

        @signal.on(result.changed)
        def save():
            self.__set_value__(name, result())

        return result

    def load(self):
        # noinspection PyBroadException
        try:
            return json.loads(self.__storage__.get())
        except:
            return {}

    def save(self):
        self.__storage__.set(json.dumps(self.__state__))


config = Config()


def bind_checkbox(cb, option):
    from ngSkinTools2.ui import qt

    cb.setChecked(option())

    @qt.on(cb.toggled)
    def update():
        option.set(cb.isChecked())

    return cb
