# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
import ulib.base.i_need_py23

__all__ = ('date_validator', 'list_validator',
           'contexts_list_validator', 'projects_list_validator',
           'default_merger', 'list_merger',
           'contexts_list_merger', 'projects_list_merger',
           'make_prop', 'make_prop_defaults',
           'make_uprop', 'make_uprop_defaults',
           'make_sprop', 'make_suprop',
           'PropsMixin',
           )

from ulib.base.base import isseq, norm_nl
from ulib.base.base import make_prop as _make_prop
from ulib.base.uio import _s, _u
from ulib.base.dates import ensure_date

def date_validator(value):
    if value is None: return None
    else: return ensure_date(value)
def list_validator(value):
    if value is None: return []
    elif not isseq(value): return [value]
    else: return list(value)
def contexts_list_validator(value):
    def ensure_context(context):
        if context[:1] != '@':
            context = '@' + context
        return context
    values = list_validator(value)
    return [ensure_context(value) for value in values]
def projects_list_validator(value):
    def ensure_project(project):
        if project[:1] not in ('-', '+'):
            project = '+' + project
        return project
    values = list_validator(value)
    return [ensure_project(value) for value in values]

def default_merger(dvalue, svalue):
    if svalue is not None: return svalue
    else: return dvalue
def list_merger(dvalue, svalue):
    dlist = list_validator(dvalue)
    slist = [value for value in list_validator(svalue) if value not in dlist]
    return dlist + slist
def contexts_list_merger(dvalue, svalue):
    values = list_merger(dvalue, svalue)
    mcontexts = [context for context in values if context[:2] == '@-']
    for mcontext in mcontexts:
        context = '@' + mcontext[2:]
        while context in values: values.remove(context)
        values.remove(mcontext)
    return values
def projects_list_merger(dvalue, svalue):
    values = list_merger(dvalue, svalue)
    mprojects = [project for project in values if project[:1] == '-']
    for mproject in mprojects:
        project = '+' + mproject[1:]
        while project in values: values.remove(project)
        values.remove(mproject)
    return values

###############################################################################
# Gestion des propriétés pour les tâches
# Ces propriétés donnent la possibilité de lire leur valeur dans l'objet local
# et éventuellement dans le store associé accessible par la propriété _store

def make_getter(name, merger=None):
    def getter(self):
        svalue = getattr(self, name)
        if merger is None:
            return svalue
        else:
            dvalue = getattr(self._store, name, None)
            return merger(dvalue, svalue)
    return getter

NOT_SET = []
def make_setter(name, validator=None, set_modified=True):
    def setter(self, value):
        if validator is not None: value = validator(value)
        old_value = getattr(self, name, NOT_SET)
        setattr(self, name, value)
        if set_modified and (old_value is NOT_SET or old_value != value):
            # n'est modifié que si la valeur est différente
            self._modified = True
    return setter

def make_deleter(name, set_None=True, set_modified=True):
    def deleter(self):
        old_value = getattr(self, name, NOT_SET)
        if set_None: setattr(self, name, None)
        else: delattr(self, name)
        if set_modified and (not set_None or old_value is not None):
            # si set_None, n'est modifié que si la valeur était différente de
            # None
            self._modified = True
    return deleter

def make_prop(name, value=None, getter=True, setter=True, deleter=True,
              validator=None, merger=None,
              set_None=True, set_modified=True):
    if getter is True: getter = make_getter(name, merger)
    if setter is True: setter = make_setter(name, validator, set_modified)
    if deleter is True: deleter = make_deleter(name, set_None, set_modified)
    return _make_prop(name, value, getter, setter, deleter, validator=validator)

def make_prop_defaults(name, value=None, getter=True, setter=True, deleter=True,
                       validator=None, merger=default_merger,
                       set_None=True, set_modified=True):
    return make_prop(name, value, getter, setter, deleter, validator, merger, set_None, set_modified)

def make_usetter(name, set_modified=True):
    def setter(self, value):
        value = norm_nl(self._store.uio.u(value))
        setattr(self, name, value)
        if set_modified: self._modified = True
    return setter

def make_uprop(name, value=None, getter=True, setter=True, deleter=True,
               merger=None, set_None=True, set_modified=True):
    if setter is True: setter = make_usetter(name, set_modified)
    return make_prop(name, value, getter, setter, deleter, None, merger, set_None, set_modified)

def make_uprop_defaults(name, value=None, getter=True, setter=True, deleter=True,
                        merger=default_merger, set_None=True, set_modified=True):
    return make_uprop(name, value, getter, setter, deleter, merger, set_None, set_modified)

###############################################################################
# Gestion des propriétés pour les stores
# Ces propriétés ne donnent que la possibilité de lire leur valeur dans l'objet
# local.

def make_sgetter(name):
    def getter(self):
        return getattr(self, name)
    return getter

def make_ssetter(name, validator=None):
    def setter(self, value):
        if validator is not None: value = validator(value)
        setattr(self, name, value)
    return setter

def make_sdeleter(name, set_None=True):
    def deleter(self):
        if set_None: setattr(self, name, None)
        else: delattr(self, name)
    return deleter

def make_sprop(name, value=None, getter=True, setter=True, deleter=True, validator=None, set_None=True):
    if getter is True: getter = make_sgetter(name)
    if setter is True: setter = make_ssetter(name, validator)
    if deleter is True: deleter = make_sdeleter(name, set_None)
    return _make_prop(name, value, getter, setter, deleter, validator=validator)

def make_susetter(name):
    def setter(self, value):
        setattr(self, name, self._uio.u(value))
    return setter

def make_suprop(name, value=None, getter=True, setter=True, deleter=True, set_None=True):
    if setter is True: setter = make_susetter(name)
    return make_sprop(name, value, getter, setter, deleter, None, set_None)

###############################################################################
# Mixin pour la gestion des propriétés pour les stores et les tasks

class PropsMixin(object):
    u"""Gestion d'un ensemble de propriétés

    GENERIC_PROPERTIES est la liste des propriétés à traiter de façon générique pour
    l'enregistrement dans un store par exemple.

    SPECIAL_PROPERTIES est la liste des propriétés à traiter de façon spéciales.

    L'ensemble GENERIC_PROPERTIES + SPECIAL_PROPERTIES est la liste de toutes
    les propriétés valides qui peuvent être modifiées dans cet objet.
    """

    def is_valid_prop(cls, name):
        return name in cls.GENERIC_PROPERTIES or name in cls.SPECIAL_PROPERTIES
    is_valid_prop = classmethod(is_valid_prop)

    def all_properties(cls):
        return cls.GENERIC_PROPERTIES + cls.SPECIAL_PROPERTIES
    all_properties = classmethod(all_properties)

    def __get_props(self, valid_props, raw_values=False, all_values=False):
        props = {}
        for name in valid_props:
            if raw_values: vname = '_' + name
            else: vname = name
            value = getattr(self, vname, None)
            if all_values or value is not None:
                props[name] = value
        return props

    def get_all_props(self, all_values=False):
        u"""Retourner la liste des propriétés modifiables

        @rtype: dict
        """
        return self.__get_props(self.all_properties(), all_values=all_values)

    def get_props(self, raw_values=False, all_values=False):
        u"""Retourner la liste des propriétés modifiables à traiter de façon
        générique.

        @rtype: dict
        """
        return self.__get_props(self.GENERIC_PROPERTIES,
                                raw_values=raw_values,
                                all_values=all_values)

    def set_props(self, raw_values=False, **props):
        u"""Initialiser les propriétés modifiables de cet objet.

        @raise ValueError: si une propriété non valide est fournie.
        """
        all_properties = self.all_properties()
        for name, value in props.items():
            if name in all_properties:
                if raw_values: vname = '_' + name
                else: vname = name
                setattr(self, vname, value)
            else:
                raise ValueError("Invalid property: %s" % _s(name))
