"""
Hosts the table tiers, user relations should be derived from.
"""

import datajoint as dj
from .table import Table
from .autopopulate import AutoPopulate
from .utils import from_camel_case, ClassProperty
from .errors import DataJointError

_base_regexp = r"[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*"

# attributes that trigger instantiation of user classes


supported_class_attrs = {
    "key_source",
    "describe",
    "alter",
    "heading",
    "populate",
    "progress",
    "primary_key",
    "proj",
    "aggr",
    "join",
    "fetch",
    "fetch1",
    "head",
    "tail",
    "descendants",
    "ancestors",
    "parts",
    "parents",
    "children",
    "insert",
    "insert1",
    "update1",
    "drop",
    "drop_quick",
    "delete",
    "delete_quick",
    "insert1p", 
    "with_parts",
}


class TableMeta(type):
    """
    TableMeta subclasses allow applying some instance methods and properties directly
    at class level. For example, this allows Table.fetch() instead of Table().fetch().
    """

    def __getattribute__(cls, name):
        # trigger instantiation for supported class attrs
        return (
            cls().__getattribute__(name)
            if name in supported_class_attrs
            else super().__getattribute__(name)
        )

    def __and__(cls, arg):
        return cls() & arg

    def __xor__(cls, arg):
        return cls() ^ arg

    def __sub__(cls, arg):
        return cls() - arg

    def __neg__(cls):
        return -cls()

    def __mul__(cls, arg):
        return cls() * arg

    def __matmul__(cls, arg):
        return cls() @ arg

    def __add__(cls, arg):
        return cls() + arg

    def __iter__(cls):
        return iter(cls())


class UserTable(Table, metaclass=TableMeta):
    """
    A subclass of UserTable is a dedicated class interfacing a base relation.
    UserTable is initialized by the decorator generated by schema().
    """

    # set by @schema
    _connection = None
    _heading = None
    _support = None

    # set by subclass
    tier_regexp = None
    _prefix = None

    @property
    def definition(self):
        """
        :return: a string containing the table definition using the DataJoint DDL.
        """
        raise NotImplementedError(
            'Subclasses of Table must implement the property "definition"'
        )

    @ClassProperty
    def name(cls):
        return cls.__name__

    @ClassProperty
    def connection(cls):
        return cls._connection

    @ClassProperty
    def table_name(cls):
        """
        :return: the table name of the table formatted for mysql.
        """
        if cls._prefix is None:
            raise AttributeError("Class prefix is not defined!")
        return cls._prefix + from_camel_case(cls.name)

    @ClassProperty
    def full_table_name(cls):
        if cls not in {
            dj.Manual, dj.Imported, dj.Lookup, dj.Computed, dj.Part,
            dj.AutoImported, dj.AutoComputed, dj.Settingstable,
            UserTable
        }:
            # for derived classes only
            if cls.database is None:
                raise DataJointError((
                    "Class {0} is not properly declared "
                    "(schema decorator not applied?)"
                ).format(cls.name))
            return r"`{0:s}`.`{1:s}`".format(cls.database, cls.table_name)


class Manual(UserTable):
    """
    Inherit from this class if the table's values are entered manually.
    """

    _prefix = r""
    tier_regexp = r"(?P<manual>" + _prefix + _base_regexp + ")"


class Lookup(UserTable):
    """
    Inherit from this class if the table's values are for lookup. This is
    currently equivalent to defining the table as Manual and serves semantic
    purposes only.
    """

    _prefix = "#"
    tier_regexp = (
        r"(?P<lookup>" + _prefix + _base_regexp.replace("TIER", "lookup") + ")"
    )


class Imported(UserTable, AutoPopulate):
    """
    Inherit from this class if the table's values are imported from external data sources.
    The inherited class must at least provide the function `_make_tuples`.
    """

    _prefix = "_"
    tier_regexp = r"(?P<imported>" + _prefix + _base_regexp + ")"


class Computed(UserTable, AutoPopulate):
    """
    Inherit from this class if the table's values are computed from other relations in the schema.
    The inherited class must at least provide the function `_make_tuples`.
    """
    _prefix = "__"
    tier_regexp = r"(?P<computed>" + _prefix + _base_regexp + ")"
