# Copyright (c) 2016 Civic Knowledge. This file is licensed under the terms of the
# Revised BSD License, included in this distribution as LICENSE

"""
Create Markdown and HTML of datasets.
"""

import datetime
from rowgenerators import parse_app_url
from markdown import markdown as convert_markdown
from nameparser import HumanName
from pybtex import PybtexEngine
from pybtex.backends.html import Backend as HtmlBackend
from pybtex.style.formatting import toplevel
from pybtex.style.formatting.plain import Style
from pybtex.style.template import (
    words, together, field, sentence, optional_field, href, optional
)
from yaml import safe_dump

from metatab.doc import MetatabDoc

dl_templ = "{}\n:   {}\n\n"

def ns(v):
    """Return empty str if bool false"""
    return str(v) if bool(v) else ''


def linkify(v, description=None, cwd_url=None):


    if not v:
        return None

    u = parse_app_url(v)

    if u.scheme in ('http', 'https', 'mailto'):

        if description is None:
            description = v
        return '[{desc}]({url})'.format(url=v, desc=description)

    elif u.scheme == 'file':

        return '[{desc}]({url})'.format(url=u.path, desc=description)

    else:
        return v


def resource(r, fields=None):
    fields = fields or (
        ('Header', 'header'),
        ('Datatype', 'datatype'),
        ('Description', 'description')
    )

    headers = [f[0] for f in fields]
    keys = [f[1] for f in fields]

    rows = [''.join(["<td>{}</td>".format(e.replace("\n", "<br/>\n")) for e in [c.get(k, '') for k in keys]])
            for c in r.columns()]

    return ("### {name} \n [{url}]({url})\n\n".format(name=r.name, url=r.resolved_url)) + \
           "{}\n".format(ns(r.description)) + \
           "<table class=\"table table-striped\">\n" + \
           ("<tr>{}</tr>".format(''.join("<th>{}</th>".format(e) for e in headers))) + \
           "\n".join("<tr>{}</tr>".format(row) for row in rows) + \
           '</table>\n'


def ckan_resource_markdown(r, fields=None):
    fields = fields or (
        ('Header', 'header'),
        ('Datatype', 'datatype'),
        ('Description', 'description')
    )

    headers = [f[0] for f in fields]
    keys = [f[1] for f in fields]

    def col_line(c):
        return "* **{}** ({}): {}\n".format(
            c.get('header'),
            c.get('datatype'),
            "*{}*".format(c.get('description')) if c.get('description') else '')

    return "### {}. {} Columns. \n\n{}".format(
        ns(r.description),
        len(list(r.columns())),
        ''.join([col_line(c) for c in r.columns()])
    )


def resource_block(doc, fields=None):
    if fields is None:
        fields = [
            ('Header', 'header'),
            ('Datatype', 'datatype'),
            ('Description', 'description')
        ]

        for h in doc['Schema'].args:

            if h.lower() in ('note', 'notes', 'coding'):
                fields.append((h, h.lower()))

    return "".join(resource(r, fields) for r in doc['Resources'].find('Root.Resource'))


def resource_ref(r):
    return dl_templ.format(r.name, "_{}_<br/>{}".format(r.resolved_url, ns(r.description)))


def resource_ref_block(doc):
    return "".join(resource_ref(r) for r in doc['Resources'].find('Root.Resource'))


def contact(r):
    pass


class MetatabStyle(Style):
    # Minnesota Population Center. IPUMS Higher Ed: Version 1.0 [dataset]
    # Minneapolis, MN: University of Minnesota, 2016. http://doi.org/10.18128/D100.V1.0.

    def format_url(self, e):
        return words[
            href[
                field('url', raw=True),
                field('url', raw=True)
            ]
        ]

    def format_accessed(self, e):
        from dateutil.parser import parser

        return words[
            'Accessed',
            field('accessdate', raw=True, apply_func=lambda v: str(parser().parse(v).strftime("%d %b %Y")))
        ]

    def format_dataset(self, e):

        template = toplevel[
            optional[sentence[field('origin')]],
            self.format_btitle(e, 'title'),
            optional[sentence[together['Version', field('version')]]],
            optional[sentence[field('publisher') ]],
            optional[self.format_author_or_editor(e)],
            optional[words[optional_field('month'), field('year')]],
            self.format_web_refs(e),
            self.format_accessed(e)
        ]

        return template.format_data(e)


class MetatabHtmlBackend(HtmlBackend):
    def write_prologue(self):
        pass
        # super().write_prologue()

    def write_epilogue(self):
        pass
        # super().write_epilogue()

    def write_entry(self, key, label, text):
        self.output("<div class='citation'><a name=\"{key}\"><b>[{key}]</b></a> {text} </div>"
                    .format(key=key, text=text))


def make_citation_dict(td):
    """
    Update a citation dictionary by editing the Author field
    :param td: A BixTex format citation dict or a term
    :return:
    """

    from datetime import datetime

    if isinstance(td, dict):
        d = td
        name = d['name_link']
    else:

        d = td.as_dict()
        d['_term'] = td

        try:
            d['name_link'] = td.name
        except AttributeError:
            d['name_link'] = td['name_link'].value

    if 'author' in d and isinstance(d['author'], str):
        authors = []
        for e in d['author'].split(';'):
            author_d = HumanName(e).as_dict(include_empty=False)
            if 'suffix' in author_d:
                author_d['lineage'] = author_d['suffix']
                del author_d['suffix']
            authors.append(author_d)
        d['author'] = authors

    if not 'type' in d:

        if '_term' in d:
            t = d['_term']

            if t.term_is('Root.Reference') or t.term_is('Root.Resource'):
                d['type'] = 'dataset'

            elif t.term_is('Root.Citation'):
                d['type'] = 'article'
            else:
                d['type'] = 'article'

    if d['type'] == 'dataset':

        if not 'editor' in d:
            d['editor'] = [HumanName('Missing Editor').as_dict(include_empty=False)]

        if not 'accessdate' in d:
            d['accessdate'] = datetime.now().strftime('%Y-%m-%d')

    if not 'author' in d:
        d['author'] = [HumanName('Missing Author').as_dict(include_empty=False)]

    if not 'title' in d:
        d['title'] = d.get('description', '<Missing Title>')

    if not 'journal' in d:
        d['journal'] = '<Missing Journal>'

    if not 'year' in d:
        d['year'] = '<Missing Year>'

    if '_term' in d:
        del d['_term']

    return d


def make_metatab_citation_dict(t):
    """
    Return a dict with BibText key/values for metatab data
    :param t:
    :return:
    """

    try:

        if parse_app_url(t.url).proto == 'metatab':

            try:
                url = parse_app_url(str(t.resolved_url)).resource_url
                doc = t.row_generator.generator.package

            except AttributeError as e:
                return False  # It isn't a resource or reference

            creator = doc.find_first('Root.Creator')
            author_key='author'

            if not creator:
                creator = doc.find_first('Root.Wrangler')
                author_key = 'editor'

            if not creator:
                creator = doc.find_first('Root.Origin')
                author_key = 'editor'

            try:
                origin = doc['Contacts'].get('Root.Origin').get('organization').value
            except AttributeError:
                try:
                    origin = doc.get_value('Root.Origin', doc.get_value('Name.Origin')).title()
                except:
                    origin = None

            d = {
                'type': 'dataset',
                'name_link': t.name,
                author_key: [HumanName(creator.value).as_dict(include_empty=False)],
                'publisher': creator.properties.get('organization'),
                'origin': origin,
                'journal': '2010 - 2015 American Community Survey',
                'title': doc['Root'].find_first_value('Root.Title') + '; ' + t.name,
                'year': doc.get_value('Root.Year'),
                'accessDate': '{}'.format(datetime.datetime.now().strftime('%Y-%m-%d')),
                'url': url,
                'version': doc.get_value('Root.Version', doc.get_value('Name.Version') ),
            }

            d =  { k:v for k, v in d.items() if v is not None}


            return d


        else:
            return False

    except (AttributeError, KeyError) as e:
        raise
        return False


def _bibliography(doc, terms, converters=[], format='html'):
    """
    Render citations, from a document or a doct of dicts

    If the input is a dict, each key is the name of the citation, and the value is a BibTex
    formatted dict

    :param doc: A MetatabDoc, or a dict of BibTex dicts
    :return:
    """

    output_backend = 'latex' if format == 'latex' else MetatabHtmlBackend

    def mk_cite(v):
        for c in converters:
            r = c(v)

            if r is not False:
                return r

        return make_citation_dict(v)

    if isinstance(doc, MetatabDoc):
        # This doesn't work for LaTex, b/c the formatter adds the prologue and epilogue to eery entry

        d = [mk_cite(t) for t in terms]

        cd = {e['name_link']: e for e in d}

    else:

        cd = {k: mk_cite(v, i) for i, (k, v) in enumerate(doc.items())}

    # for k, v in cd.items():
    #    print (k, v)

    return PybtexEngine().format_from_string(safe_dump({'entries': cd}),
                                             style=MetatabStyle,
                                             output_backend=output_backend,
                                             bib_format='yaml')


def bibliography(doc, converters=[], format='html'):
    terms = list(doc.get_section('Bibliography', []))

    return _bibliography(doc, terms, converters, format)


def data_sources(doc, converters=[], format='html'):
    terms = list(doc.references()) + list(doc.resources())

    return _bibliography(doc, terms, converters, format)


def identity_block(doc):
    from collections import OrderedDict

    name = doc.find_first('Root.Name')

    d = OrderedDict(
        name=name.value,
        identifier=doc.find_first_value('Root.Identifier'),
    )

    for k, v in name.props.items():
        d[k] = v

    try:
        del d['@value']
    except KeyError:
        pass

    return "".join(dl_templ.format(k, v) for k, v in d.items())


def modtime_str(doc):
    from dateutil.parser import parse

    modtime = doc.find_first_value('Root.Modified') or doc.find_first_value('Root.Issued')

    if modtime:
        try:
            modtime_str = "Last Modified: " + parse(modtime).strftime("%Y-%m-%d %H:%M")
        except ValueError:
            modtime_str = ''

    else:

        modtime_str = ''

    return modtime_str



def process_contact(d):

    email = d.get('email',None)
    org = d.get('organization', None)
    url = d.get('url',None)
    name = d.get('name', None)

    if name and email and org and url:
        return {
            'parts':["[{}]({})".format(name,'mailto:'+email),"[{}]({})".format(org,url)]
        }

    elif name and email and org:
        return {
            'parts':["[{}]({})".format(name, 'mailto:' + email),"{}".format(org)]
        }
    elif name and email and url:
        return {
            'parts': ["[{}]({})".format(name, 'mailto:' + email), "{}".format(url)]
        }

    elif name and org and url:
        return {
            'parts':["{}".format(name),"[{}]({})".format(org, url)]
        }

    elif name and org:
        return {
            'parts': ["{}".format(name),"{}".format(org)]
        }
    elif name and url:
        return {
            'parts': ["{}".format(name),"[{}]({})".format(url, url)]
        }
    elif name:
        return {
            'parts': ["{}".format(name)]
        }
    elif org and email and url:
        return {
            'parts': ["[{}]({})".format(org,url),"[{}]({})".format(email,'mailto:'+email)]
        }
    elif org and email:
        return {
            'parts':["{}".format(org),"[{}]({})".format(email, 'mailto:' + email)]
        }
    elif org and url:
        return {
            'parts':[ "[{}]({})".format(org, url)]
        }
    elif url and email:
        return {
            'parts':["[{}]({})".format(url, url), "[{}]({})".format(email, 'mailto:' + email)]
        }

    elif org:
        return {
            'parts': ["{}".format(org)]
        }
    elif url:
        return {
            'parts': ["[{}]({})".format(url, url)]
        }
    elif email:
        return {
            'parts': ["[{}]({})".format(email, 'mailto:' + email)]
        }
    else:

        return {
            'parts': []
        }

def display_context(doc):
    """Create a Jinja context for display"""
    from rowgenerators.exceptions import DownloadError

    context = {s.name.lower(): s.as_dict() for s in doc if s.name.lower() != 'schema'}

    #import json
    #print(json.dumps(context, indent=4))

    mandatory_sections = ['documentation', 'contacts']

    # Remove section names
    deletes = []
    for k,v in context.items():
        try:
            del v['@value']
        except KeyError:
            pass # Doesn't have the value
        except TypeError:
            # Is actually completely empty, and has a scalar value. Delete and re-create
            deletes.append(k)

        if isinstance(v, str): # Shouldn't ever happen, but who knows ?
            deletes.append(k)

    for d in deletes:
        try:
            del context[d]
        except KeyError:
            # Fails in TravisCI, no idea why.
            pass

    for ms in mandatory_sections:
        if not ms in context:
            context[ms] = {}

    # Load inline documentation
    inline = ''

    for d in context.get('documentation',{}).get('documentation',[]):

        u = parse_app_url(d['url'])

        if u.target_format == 'md':  # The README.md file
            inline = ''
            if u.proto == 'file':
                # File really ought to be relative
                t = doc.package_url.join_target(u).get_resource().get_target()
            else:
                try:
                    t = u.get_resource().get_target()
                except DownloadError as e:
                    raise e


            try:
                with open(t.fspath) as f:
                    inline += f.read()

            except FileNotFoundError:
                pass

            del d['title'] # Will cause it to be ignored in next section

    # Strip off the leading title, if it exists, because it will be re-applied
    # by the templates
    import re

    lines = inline.strip().splitlines()
    if lines and lines[0].startswith('# '):
        lines = lines[1:]

    context['inline_doc'] = '\n'.join(lines)

    # Convert doc section
    doc_links = {}
    images = {}
    for term_name, terms in context['documentation'].items():
        if term_name == 'note':
            context['notes'] = terms
        else:
            for i, term in enumerate(terms):
                try:
                    if term_name == 'image':
                        images[term['title']] = term
                    else:
                        doc_links[term['title']] = term
                except AttributeError: # A scalar
                    pass # There should not be any scalars in the documentation section
                except KeyError:
                    pass # ignore entries without titles
                except TypeError:
                    pass # Also probably a ascalar

    context['doc_links'] = doc_links
    context['images'] = images

    del context['documentation']

    #
    # Update contacts

    origin = None
    for term_name, terms in context['contacts'].items():
        if isinstance(terms, dict):
            origin = terms # Origin is a scalar in roort, must be converted to sequence here
        else:
            for t in terms:
                try:
                    t.update(process_contact(t))
                except AttributeError:
                    pass # Probably got a scalar
    if origin:
        origin.update(process_contact(origin))
        context['contacts']['origin'] = [origin]


    # For resources and references, convert scalars into lists of dicts, which are the
    # default for Datafiles and References.

    for section in ('references', 'resources'):
        for term_key, term_vals in context.get(section,{}).items():
            if isinstance(term_vals, dict):
                if '@value' in term_vals:
                    term_vals['url'] = term_vals['@value']
                    del term_vals['@value']
                new_term_vals = [term_vals]
            elif isinstance(term_vals, list):
                new_term_vals = None
            else:
                new_term_vals = [ {'url': term_vals, 'name': term_vals}]

            if new_term_vals:
                context[section][term_key] = new_term_vals

    context['distributions'] = {}
    for dist in doc.find('Root.Distribution'):
        context['distributions'][dist.type] = dist.value

    if doc.find('Root.Giturl'):
        context['distributions']['source'] = doc.get_value('Root.Giturl')

    return context

def markdown(doc, title=True, template='short_documentation.md'):
    """Markdown, specifically for the Notes field in a CKAN dataset"""

    from jinja2 import Environment, PackageLoader, select_autoescape
    env = Environment(
        loader=PackageLoader('metapack', 'support/templates')
        #autoescape=select_autoescape(['html', 'xml'])
    )

    context = display_context(doc)

    return env.get_template(template).render(**context)


def html(doc, template='short_documentation.md'):
    extensions = [
        'markdown.extensions.extra',
        'markdown.extensions.admonition'
    ]

    return """
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

    <style>
    .narrow-dl .dl-horizontal dd {{
        margin-left: 100px;
    }}
    
    img[alt=doc_img] {{ width: 300px; }}

    .narrow-dl .dl-horizontal dt {{
        width: 80px;
    }}

    </style>
    <title>{title}</title>
</head>
<body>
    <div class="container">
        <div class="row">
            {body}
        </div>
    </div>
</body>
</html>
    """.format(
        title=doc.find_first_value('Root.Title'),
        body=convert_markdown(markdown(doc, template=template), extensions)
    )
