# -*- coding: utf-8 -*-

from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

from .logging       import *

import hashlib



class Arguments(dict):
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

# https://stackoverflow.com/questions/4984647/accessing-dict-keys-like-an-attribute-in-python
# class Arguments(dict):
#     def __init__(self, *args, **kwargs):
#         super(Arguments, self).__init__(*args, **kwargs)
#         self.__dict__ = self



class ArgumentsManagerMeta(type):

    __instance_cache = {}

    def __call__(cls, *args, **kwargs):
        _args = {}

        ########################################################################
        # Compose and check args
        ###
        if args:
            # TODO: Change to other type of exception
            raise ValueError("Must provide named arguments for Tasks")

        if kwargs:
            # Check required args
            for required_arg_name, required_arg_type in get_managed_args(cls).iteritems():
                try:
                    if required_arg_type \
                            and not isinstance(kwargs[required_arg_name],
                                required_arg_type):
                        raise TypeError("'{}' type must be: {}".format(
                            required_arg_name, required_arg_type.__name__))
                except KeyError as e:
                    raise ValueError(
                        "Must provide attribute '{}' to '{}'".format(
                            e.message, cls.__name__))

            # Copy the rest
            _args = kwargs

        ########################################################################
        # Calculate key
        ###
        _key = tuple([cls] + _args.items())
        _id = hash(_key)

        ########################################################################
        # Cache?
        ###
        cache = None
        for c in cls.__mro__:
            try:
                cache = c.__dict__["__arguments_manager_cache__"]
                break
            except KeyError:
                continue
        if cache is None:
            raise TypeError("{} must inherit from {}".format(cls,
                ArgumentsManagerMixin.__name__))

        ########################################################################
        # Return
        ###
        if cache and _id in ArgumentsManagerMeta.__instance_cache:
            return ArgumentsManagerMeta.__instance_cache[_id]
        else:
            # 1. Create object
            obj = cls.__new__(cls)

            # 2. Add items to make them available on __init__ time
            #obj.__dict__.update(_args)
            obj.__dict__["args"] = Arguments(_args)

            # 3. Init the object
            obj.__init__()

            ArgumentsManagerMeta.__instance_cache[_id] = obj
            return obj



class ArgumentsManagerMixin(object):
    __arguments_manager_cache__ = True

    def _key(self):
        return tuple(
            [self.__class__] + [(k, self.args[k])
                for k in get_managed_args(self.__class__).iterkeys()])

    def _persistent_key(self):
        fields = ["=".join(["cls", self.__class__.__name__])]
        fields += ["=".join([str(k), str(self.args[k])])
            for k in get_managed_args(self.__class__).iterkeys()]
        return ":".join(fields)

    def persistent_id(self):
        k = self._persistent_key()
        assert isinstance(k, basestring)
        return hashlib.sha1(k).hexdigest()

    def __hash__(self):
        k = self._key()
        assert isinstance(k, tuple)
        return hash(k)

    def __eq__(self, other):
        return self._key() == other._key()

    def __ne__(self, other):
        return not self.__eq__(other)



def iter_managed_classes(globals):
    assert(globals)

    if isinstance(globals, dict):
        globals = globals.itervalues()

    for g in globals:
        if hasattr(g, "args_types"):
            yield g



def get_managed_args(cls):
    assert(cls)

    args = {}
    for c in cls.__mro__:
        try:
            args.update(c.args_types)
        except AttributeError:
            continue
    return args


def get_all_managed_args(globals):
    assert(globals)

    args = {}
    for cls in iter_managed_classes(globals):
        args.update(get_managed_args(cls))
    return args
