from __future__ import unicode_literals

import datetime
import xml.etree.ElementTree as et
from itertools import cycle
import tempfile

import sqlalchemy as sa
import sqlalchemy.orm
from pyramid.httpexceptions import HTTPFound

from clld.interfaces import IDataset, IMetadata, ILanguage, IIndex
from clld.web.adapters.base import Representation, Index
from clld.web.adapters.download import CsvDump, N3Dump
from clld.web.adapters.geojson import GeoJsonLanguages
from clld.web.adapters.md import BibTex
from clld.web.maps import GeoJsonSelectedLanguages, SelectedLanguagesMap
from clld.db.models.common import Language, LanguageIdentifier, Identifier
from clld.web.icon import ORDERED_ICONS
from clld.lib import bibtex

from glottolog3.models import Languoid, LanguoidLevel, Languoidcountry, Country
from glottolog3.interfaces import IProvider


class BibTexCitation(BibTex):

    def rec(self, ctx, req):
        url = '%s accessed %s' % (req.resource_url(ctx), datetime.date.today())
        return bibtex.Record('misc', req.dataset.id,
            author=[c.contributor.name for c in ctx.editors],
            title=getattr(ctx, 'citation_name', ctx.__unicode__()),
            url=url,
            address=req.dataset.publisher_place,
            howpublished=req.dataset.publisher_name,
            year=str(req.dataset.published.year))


class LanguoidCsvDump(CsvDump):

    def query(self, req):
        Family = sa.orm.aliased(Language, flat=True)
        Father = sa.orm.aliased(Language, flat=True)
        _Country = sa.orm.aliased(Country, name='_country')

        query = req.db.query(
                Languoid.id, Family.id.label('family_id'), Father.id.label('parent_id'),
                Languoid.name,
                Languoid.bookkeeping,
                sa.type_coerce(Languoid.level, sa.Text).label('level'),
                sa.type_coerce(Languoid.status, sa.Text).label('status'),
                Languoid.latitude, Languoid.longitude,
                sa.select([Identifier.name])
                    .where(LanguageIdentifier.identifier_pk == Identifier.pk)
                    .where(LanguageIdentifier.language_pk == Language.pk)
                    .where(Identifier.type == 'iso639-3')
                    .label('iso639P3code'),
                Languoid.description, Languoid.markup_description,
                Languoid.child_family_count, Languoid.child_language_count, Languoid.child_dialect_count,
                sa.select([sa.literal_column("string_agg(_country.id, ' ' ORDER BY _country.id)")])
                    .where(Languoidcountry.country_pk == _Country.pk)
                    .where(Languoidcountry.languoid_pk == Languoid.pk)
                    .label('country_ids'),
            ).select_from(Languoid).filter(Languoid.active)\
            .outerjoin(Family, Family.pk == Languoid.family_pk)\
            .outerjoin(Father, Father.pk == Languoid.father_pk)\
            .order_by(Languoid.id)
        return query

    def get_fields(self, req):
        if not self.fields:
            self.fields = [c.name for c in self.query(req).statement.columns]
        return self.fields

    def row(self, req, fd, item, index):
        return item


class LanguoidN3Dump(N3Dump):

    def query(self, req):
        return req.db.query(Language).options(sa.orm.joinedload_all(
            Language.languageidentifier, LanguageIdentifier.identifier))\
            .order_by(Language.pk)


class Redirect(Representation):
    mimetype = 'text/html'
    extension = 'html'

    def render(self, ctx, req):
        raise HTTPFound(
            req.route_url('providers', _anchor='provider-' + req.matchdict['id']))


class Bigmap(Representation):
    mimetype = 'text/vnd.clld.bigmap+html'
    send_mimetype = 'text/html'
    extension = 'bigmap.html'
    template = 'language/bigmap_html.mako'


class Treeview(Representation):
    """Classification tree rooted at the current languoid rendered as svg graphic in an
    HTML page.
    """
    name = 'SVG Classification Tree'
    mimetype = 'text/vnd.clld.treeview+html'
    send_mimetype = 'text/html'
    extension = 'treeview.html'
    template = 'language/treeview_html.mako'


class Newick(Representation):
    """Classification tree represented in Newick format.
    """
    name = 'Newick format'
    mimetype = 'text/vnd.clld.newick+plain'
    send_mimetype = 'text/plain'
    extension = 'newick.txt'

    def render(self, languoid, request):
        if languoid.family:
            languoid = languoid.family
        return languoid.newick


class PhyloXML(Representation):
    """Classification tree rooted at the current languoid represented in PhyloXML.
    """
    name = 'PhyloXML'
    mimetype = 'application/vnd.clld.phyloxml+xml'
    send_mimetype = 'application/xml'
    extension = 'phylo.xml'
    namespace = 'http://www.phyloxml.org'

    def render(self, root, req):
        self.depth_limit = 2 if root.child_language_count > 350 else 100
        et.register_namespace('', self.namespace)
        e = self.element('phyloxml')
        phylogeny = self.element('phylogeny', rooted="true")
        phylogeny.append(self.element('name', root.name))
        phylogeny.append(self.element('description', root.name))
        clade = self.clade(root, req)
        self.append_children(clade, root, req, 0)
        phylogeny.append(clade)
        e.append(phylogeny)
        tree = et.ElementTree(element=e)
        with tempfile.TemporaryFile() as fp:
            tree.write(fp, encoding='utf8', xml_declaration=True)
            fp.seek(0)
            return fp.read()

    def element(self, name, text=None, **kw):
        e = et.Element('{%s}%s' % (self.namespace, name), **kw)
        if text:
            e.text = text
        return e

    def clade(self, lang, req, level=0):
        e = self.element('clade', branch_length="0.2")
        if lang.level == LanguoidLevel.language or level == self.depth_limit:
            e.append(self.element('name', lang.name))
            ann = self.element('annotation')
            ann.append(self.element('desc', ' > '.join(reversed([l.name for l in lang.get_ancestors()]))))
            ann.append(self.element('uri', req.resource_url(lang)))
            e.append(ann)
        return e

    def append_children(self, clade, lang, req, level):
        if level > self.depth_limit:
            return
        children = [l for l in lang.children if
                    l.level in [LanguoidLevel.language, LanguoidLevel.family]]
        for child in sorted(children, key=lambda l: l.name):
            subclade = self.clade(child, req, level)
            if child.children:
                self.append_children(subclade, child, req, level + 1)
            clade.append(subclade)


class GlottologGeoJsonLanguages(GeoJsonLanguages):
    def feature_properties(self, ctx, req, feature):
        res = GeoJsonLanguages.feature_properties(self, ctx, req, feature)
        language = self.get_language(ctx, req, feature)
        res.update(language=language.__json__(req, core=True))
        return res


class _GeoJsonSelectedLanguages(GeoJsonSelectedLanguages):
    def feature_properties(self, ctx, req, feature):
        res = GeoJsonSelectedLanguages.feature_properties(self, ctx, req, feature)
        language = self.get_language(ctx, req, feature)
        res.update(
            icon=ctx[language.family_pk],
            language=language.__json__(req, core=True))
        return res


class _SelectedLanguagesMap(SelectedLanguagesMap):
    def __init__(self, req, languages, icon_map):
        SelectedLanguagesMap.__init__(
            self, icon_map, req, languages, geojson_impl=_GeoJsonSelectedLanguages)

    def get_options(self):
        opts = SelectedLanguagesMap.get_options(self)
        opts['max_zoom'] = 12
        return opts


def get_selected_languages_map(req, languages):
    icon_map = {}
    family_map = {}
    icons = cycle(ORDERED_ICONS)
    for l in languages:
        if l.family_pk not in icon_map:
            icon_map[l.family_pk] = next(icons).url(req)
            family_map[l.family_pk] = l.family
    return _SelectedLanguagesMap(req, languages, icon_map), icon_map, family_map


class MapView(Index):
    extension = str('map.html')
    mimetype = str('text/vnd.clld.map+html')
    send_mimetype = str('text/html')
    template = 'language/map_html.mako'

    def template_context(self, ctx, req):
        if 'country' in req.params:
            country = Country.get(req.params['country'], default=None)
        else:
            country = None

        if country:
            languages = country.languoids
        else:
            languages = list(ctx.get_query(limit=8000))
        map_, icon_map, family_map = get_selected_languages_map(req, languages)
        return {
            'map': map_,
            'country': country,
            'icon_map': icon_map,
            'family_map': family_map,
            'languages': languages}


def includeme(config):
    config.register_adapter(BibTexCitation, IDataset, IMetadata)
    config.register_adapter(Redirect, IProvider)
    config.register_adapter(Bigmap, ILanguage)
    config.register_adapter(PhyloXML, ILanguage)
    config.register_adapter(Newick, ILanguage)
    config.register_adapter(Treeview, ILanguage)
    config.register_adapter(GlottologGeoJsonLanguages, ILanguage, IIndex)
    config.register_adapter(MapView, ILanguage, IIndex)
