import enum
import itertools
import functools
import collections

from . import types
from . import utils


__all__ = ('New', 'Key', 'field')


class New:

    """
    Convenience source for new :class:`~.types.Structure` classes.

    .. code-block::

        >>> new = New()
        >>> new.Animal
        '<Animal>'
    """

    __slots__ = ('__base', '__meta', '__dict__')

    __space = {'__slots__': ()}

    def __init__(self, base = types.Structure, meta = type):

        self.__base = base

        self.__meta = meta

    def __getattr__(self, key):

        value = self.__meta(key, (self.__base,), self.__space)

        self.__dict__[key] = value

        return value


class Key(str):

    """
    Key(level, value)

    Signifies a table key.

    :param int level:
        Determines importance. For example, setting it to ``0`` would imply
        this is the primary key, ``1`` could be used for recognising, and ``2+``
        for more trivial reasons.
    :param str value:
        The name of the key.
    """

    __slots__ = ('_level',)

    def __new__(cls, /, level, value):

        self = super().__new__(cls, value)

        self._level = level

        return self

    @property
    def level(self):

        return self._level


class Table(dict):

    """
    Concentrates instructions on how to handle :class:`Structure`\s.
    """

    __slots__ = ('_groups', '_extra')

    def __init__(self, data, extra = {}):

        check = lambda key: key.level

        groups = collections.defaultdict(list)

        for (level, keys) in itertools.groupby(data, key = check):

            groups[level].extend(keys)

        self._groups = groups

        self.update(data)

        self._extra = extra

    @property
    def groups(self):

        return self._groups

    @property
    def extra(self):

        return self._extra


@functools.lru_cache(None)
def field(cls, *args, **kwargs):

    """
    Signifies a table field.

    .. code-block::

        Table(
            {
                Key(0, 'id'): field(str),
                Key(1, 'users'): field(Mapping, new.User)
            }
        )
    """

    return functools.partial(cls, *args, **kwargs)
