from itertools import chain, groupby
from collections import defaultdict

from zope.interface import implementer
from sqlalchemy import (
    Column,
    Unicode,
    Integer,
    ForeignKey,
    Date,
)
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.dialects.postgresql import TSVECTOR

from clld import interfaces
from clld.db.meta import Base, CustomModelMixin
from clld.db.models import common
from clld.web.util.htmllib import HTML
from clld.web.util.helpers import external_link
from clld_glottologfamily_plugin.models import HasFamilyMixin

from dictionaria.util import split


@implementer(interfaces.ILanguage)
class Variety(CustomModelMixin, common.Language, HasFamilyMixin):
    pk = Column(Integer, ForeignKey('language.pk'), primary_key=True)


@implementer(interfaces.IContribution)
class Dictionary(CustomModelMixin, common.Contribution):
    """Contributions in WOW are dictionaries which are always related to one language.
    """
    pk = Column(Integer, ForeignKey('contribution.pk'), primary_key=True)
    language_pk = Column(Integer, ForeignKey('language.pk'))
    language = relationship('Language', backref='dictionaries')
    number = Column(Integer)
    published = Column(Date)
    count_words = Column(Integer)
    count_audio = Column(Integer)
    count_example_audio = Column(Integer)
    count_image = Column(Integer)
    semantic_domains = Column(Unicode)
    toc = Column(Unicode)
    doi = Column(Unicode)

    def metalanguage_label(self, lang):
        style = self.jsondata['metalanguage_styles'].get(lang)
        style = "label label-{0}".format(style) if style else lang
        return HTML.span(lang, class_=style)

    def doi_link(self):
        if self.doi:
            return external_link(
                'https://doi.org/{0.doi}'.format(self), label='DOI: {0.doi}'.format(self))
        return ''


@implementer(interfaces.IParameter)
class ComparisonMeaning(CustomModelMixin, common.Parameter):
    pk = Column(Integer, ForeignKey('parameter.pk'), primary_key=True)
    concepticon_url = Column(Unicode)
    representation = Column(Integer)


class SourcesForDataMixin(object):
    @property
    def sourcedict(self):
        res = defaultdict(list)
        for ref in self.references:
            res[ref.description].append(ref.source)
        return res


@implementer(interfaces.IUnit)
class Word(CustomModelMixin, common.Unit, SourcesForDataMixin):
    """Words are units of a particular language, but are still considered part of a
    dictionary, i.e. part of a contribution.
    """
    pk = Column(Integer, ForeignKey('unit.pk'), primary_key=True)
    semantic_domain = Column(Unicode)
    fts = Column(TSVECTOR)
    serialized = Column(Unicode)

    # original ...?

    # the concatenated values for the UnitParameter part of speech is stored denormalized.
    pos = Column(Unicode)

    dictionary_pk = Column(Integer, ForeignKey('dictionary.pk'))
    dictionary = relationship(Dictionary, backref='words')
    number = Column(Integer, default=0)  # for disambiguation of words with the same name
    example_count = Column(Integer, default=0)

    custom_field1 = Column(Unicode)
    custom_field2 = Column(Unicode)
    second_tab1 = Column(Unicode)
    second_tab2 = Column(Unicode)
    second_tab3 = Column(Unicode)

    def iterfiles(self):
        for file in self._files:
            yield file
        for meaning in self.meanings:
            for file in meaning._files:
                yield file

    @property
    def label(self):
        args = [self.name]
        if self.number:
            args.append(HTML.sup('{0}'.format(self.number)))
        return HTML.span(*args, **{'class': 'lemma'})

    @property
    def iterrelations(self):
        # note: add 0/1 to tuples to sort references before back-references
        links_to = [
            (ta.description, 0, ta.ord, ta.target)
            for ta in self.target_assocs]
        target_ids = {t[3].id for t in links_to}

        links_from = [
            (
                RELATIONS.get(sa.description, sa.description),
                1,
                sa.ord,
                sa.source
            )
            for sa in self.source_assocs
            if sa.source.id not in target_ids]

        links = sorted(
            chain(links_to, links_from),
            key=lambda t: t[:3])
        for desc, tuples in groupby(links, lambda t: t[0]):
            yield desc, [t[3] for t in tuples]

    @property
    def description_list(self):
        return split(self.description)

    @property
    def semantic_domain_list(self):
        return split(self.semantic_domain)


class WordReference(Base, common.HasSourceMixin):
    word_pk = Column(Integer, ForeignKey('word.pk'))
    word = relationship(Word, backref="references")


RELATIONS = {
    'Main Entry': 'Subentry',
    'Synonym': '(Part of) Synonym (for)',
    'Antonym': '(Part of) Antonym (for)',
    'Contains': 'Is Part of',
}


class SeeAlso(Base):
    source_pk = Column(Integer, ForeignKey('word.pk'))
    target_pk = Column(Integer, ForeignKey('word.pk'))
    description = Column(Unicode())
    ord = Column(Integer, default=1)

    source = relationship(Word, foreign_keys=[source_pk], backref='target_assocs')
    target = relationship(Word, foreign_keys=[target_pk], backref='source_assocs')


class Meaning_files(Base, common.FilesMixin):
    pass


class Meaning_data(Base, common.DataMixin):
    pass


class Meaning(Base, common.HasFilesMixin, common.HasDataMixin, common.IdNameDescriptionMixin, SourcesForDataMixin):
    word_pk = Column(Integer, ForeignKey('word.pk'))
    ord = Column(Integer, default=1)
    gloss = Column(Unicode)
    language = Column(Unicode, default='en')
    semantic_domain = Column(Unicode)
    alt_translation1 = Column(Unicode)
    alt_translation_language1 = Column(Unicode)
    alt_translation2 = Column(Unicode)
    alt_translation_language2 = Column(Unicode)

    @declared_attr
    def word(cls):
        return relationship(Word, backref=backref('meanings', order_by=[cls.ord]))

    @property
    def semantic_domain_list(self):
        if self.semantic_domain:
            return split(self.semantic_domain)
        return []