"""
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',
    'insert1p', 'with_parts',
    'descendants', 'ancestors', 'parts', 'parents', 'children',
    'insert', 'insert1', 'update1', 'drop', 'drop_quick', 'delete', 'delete_quick'}


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 + ')'
