"""
Project related model.
"""
from string import capwords

from zope.interface import implementer

from sqlalchemy import (
    Column,
    String,
    Boolean,
    Unicode,
    Integer,
    ForeignKey,
    desc,
    UniqueConstraint,
    and_,
    cast,
    Text,
    Index,
)
from sqlalchemy.orm import relationship, backref, joinedload
from sqlalchemy.dialects.postgresql import TSVECTOR
from sqlalchemy.sql.expression import func

from clld.interfaces import ISource, ILanguage
from clld.db.meta import DBSession, Base, CustomModelMixin
from clld.db.models.common import (
    Language, Source, IdNameDescriptionMixin, IdentifierType, Identifier, Parameter,
)
from clld.util import DeclEnum

from glottolog3.interfaces import IProvider
from glottolog3.config import github


def get_parameter(pid):
    return DBSession.query(Parameter)\
        .filter(Parameter.id == pid)\
        .options(joinedload(Parameter.domain))\
        .one()


def get_source(key):
    return DBSession.query(Ref).join(Refprovider).filter(Refprovider.id == key).one()


@implementer(IProvider)
class Provider(Base, IdNameDescriptionMixin):
    """A provider of bibliographical data, i.e. entries in Ref.

    name -> id
    description -> name
    comment -> description
    """
    abbr = Column(Unicode)
    url = Column(Unicode)

    # if refurl is given, we can construct direct links to the provider's catalog ...
    refurl = Column(Unicode)
    # ... using the content of the bibfield attribute of a Ref instance.
    bibfield = Column(Unicode)

    @property
    def github_url(self):
        return github('references/bibtex/{0}.bib'.format(self.id))


class Doctype(Base, IdNameDescriptionMixin):
    """
    id -> pk
    id -> id
    abbr -> abbr
    name -> name
    """
    abbr = Column(Unicode)

    ord = Column(Integer)

    def __str__(self):
        return capwords(self.name.replace('_', ' '))


class Refdoctype(Base):
    __table_args__ = (UniqueConstraint('ref_pk', 'doctype_pk'),)
    doctype_pk = Column(Integer, ForeignKey('doctype.pk'), nullable=False)
    ref_pk = Column(Integer, ForeignKey('ref.pk'), nullable=False)


class Refprovider(Base):
    __table_args__ = (UniqueConstraint('ref_pk', 'provider_pk', 'id'),)
    provider_pk = Column(Integer, ForeignKey('provider.pk'), nullable=False)
    ref_pk = Column(Integer, ForeignKey('ref.pk'), nullable=False)
    id = Column(Unicode, unique=True, nullable=False)
    provider = relationship(Provider)

    @classmethod
    def get_stats(cls):
        return {
            row[0]: row[1] for row in
            DBSession.query(Provider.pk, func.count(cls.ref_pk).label('c'))
                .filter(Provider.pk == cls.provider_pk)
                .group_by(Provider.pk)
                .order_by(desc('c'))
                .all()}


#-----------------------------------------------------------------------------
# specialized common mapper classes
#-----------------------------------------------------------------------------
class LanguoidLevel(DeclEnum):
    family = 'family', 'family'
    language = 'language', 'language'
    dialect = 'dialect', 'dialect'


@implementer(ILanguage)
class Languoid(CustomModelMixin, Language):
    """
    id -> pk
    alnumcode -> id
    primaryname -> name
    names, codes -> languageidentifier
    refs -> languagesource
    """

    GLOTTOLOG_NAME = u'glottolog'

    pk = Column(Integer, ForeignKey('language.pk'), primary_key=True)

    # hid is the id of a language in Harald's classification. I.e. if hid is None, the
    # languoid is not a H-Language.
    hid = Column(Unicode, unique=True)

    father_pk = Column(Integer, ForeignKey('languoid.pk'))
    family_pk = Column(Integer, ForeignKey('languoid.pk'))

    level = Column(LanguoidLevel.db_type())
    bookkeeping = Column(Boolean, default=False)
    category = Column(Unicode)
    newick = Column(Unicode)

    child_family_count = Column(Integer)
    child_language_count = Column(Integer)
    child_dialect_count = Column(Integer)
    macroareas = Column(Unicode)

    descendants = relationship(
        'Languoid',
        order_by='Languoid.name, Languoid.id',
        foreign_keys=[family_pk],
        backref=backref('family', remote_side=[pk]))
    children = relationship(
        'Languoid',
        order_by='Languoid.name, Languoid.id',
        foreign_keys=[father_pk],
        backref=backref('father', remote_side=[pk]))

    def get_identifier_objs(self, type_):
        if getattr(type_, 'value', type_) == IdentifierType.glottolog.value:
            return [
                Identifier(name=self.id, type=IdentifierType.glottolog.value)]
        return Language.get_identifier_objs(self, type_)

    def get_ancestors(self, session=None):
        """
        :return: Iterable of ancestors of self back to the top-level family.
        """
        session = session or DBSession
        # retrieve the ancestors ordered by distance, i.e. from direct parent
        # to top-level family:
        return session.query(Languoid)\
            .join(TreeClosureTable, and_(
                TreeClosureTable.parent_pk == Languoid.pk,
                TreeClosureTable.depth > 0))\
            .filter(TreeClosureTable.child_pk == self.pk)\
            .order_by(TreeClosureTable.depth)

    @property
    def github_url(self):
        path = [self.id]
        for l in self.get_ancestors():
            path.append(l.id)
        return github('languoids/tree/{0}/md.ini'.format('/'.join(reversed(path))))

    def __json__(self, req=None, core=False):
        def ancestor(l):
            r = {"name": l.name, "id": l.id}
            if req:
                r['url'] = req.resource_url(l)
            return r
        res = super(Languoid, self).__json__(req)
        if not core:
            res['classification'] = [ancestor(l) for l in reversed(list(self.get_ancestors()))]
            if self.iso_code:
                res[IdentifierType.iso.value] = self.iso_code
            res['macroareas'] = {}#{ma.id: ma.name for ma in self.macroareas}
        return res

    def get_geocoords(self):
        """
        :return: sqlalchemy Query selecting quadrupels \
        (lid, primaryname, longitude, latitude) where lid is the Languoidbase.id of one\
        of the children of self.

        .. note::

            This method does not return the geo coordinates of the Languoid self, but of
            its descendants.
        """
        
        child_pks = DBSession.query(Languoid.pk)\
            .filter(Languoid.father_pk == self.pk).subquery()
        return DBSession.query(
            TreeClosureTable.parent_pk,
            Language.name,
            Language.longitude,
            Language.latitude,
            Language.id)\
            .filter(Language.pk == TreeClosureTable.child_pk)\
            .filter(TreeClosureTable.parent_pk.in_(child_pks))\
            .filter(Language.latitude != None)

    @property
    def valueset_dict(self):
        return {vs.parameter.id: vs for vs in self.valuesets}

    def classification(self, type_):
        assert type_ in ['fc', 'sc']
        return self.valueset_dict.get(type_)

    @property
    def fc(self):
        c = self.classification('fc')
        if c and c.description:
            return c

    @property
    def sc(self):
        c = self.classification('sc')
        if c and c.description:
            return c

    def _crefs(self, t):
        c = self.classification(t)
        if c:
            return list(c.references)