# -*- coding: utf-8 -*-
"""
    sphinx.builders.websupport
    ~~~~~~~~~~~~~~~~~~~~~~~~~~

    Builder for the web support package.

    :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

from os import path
import posixpath
import shutil

from docutils.io import StringOutput

from sphinx import version_info as sphinx_version
from sphinx.jinja2glue import BuiltinTemplateLoader
from sphinx.util.osutil import os_path, relative_uri, ensuredir, copyfile
from sphinx.builders.html import PickleHTMLBuilder

from . import package_dir
from .writer import WebSupportTranslator
from .utils import is_commentable


if False:
    # For type annotation
    from typing import Any, Dict, Iterable, Tuple  # NOQA
    from docutils import nodes  # NOQA
    from sphinx.application import Sphinx  # NOQA

RESOURCES = [
    'ajax-loader.gif',
    'comment-bright.png',
    'comment-close.png',
    'comment.png',
    'down-pressed.png',
    'down.png',
    'up-pressed.png',
    'up.png',
    'websupport.js',
]


class WebSupportBuilder(PickleHTMLBuilder):
    """
    Builds documents for the web support package.
    """
    name = 'websupport'
    default_translator_class = WebSupportTranslator
    versioning_compare = True  # for commentable node's uuid stability.

    def init(self):
        # type: () -> None
        PickleHTMLBuilder.init(self)
        # templates are needed for this builder, but the serializing
        # builder does not initialize them
        self.init_templates()
        if not isinstance(self.templates, BuiltinTemplateLoader):
            raise RuntimeError('websupport builder must be used with '
                               'the builtin templates')
        # add our custom JS
        self.script_files.append('_static/websupport.js')

    @property
    def versioning_method(self):
        if sphinx_version < (2, 0):
            return 'commentable'
        else:
            return is_commentable

    def set_webinfo(self, staticdir, virtual_staticdir, search, storage):
        # type: (str, str, Any, str) -> None
        self.staticdir = staticdir
        self.virtual_staticdir = virtual_staticdir
        self.search = search
        self.storage = storage

    def prepare_writing(self, docnames):
        # type: (Iterable[str]) -> None
        PickleHTMLBuilder.prepare_writing(self, docnames)
        self.globalcontext['no_search_suffix'] = True

    def write_doc(self, docname, doctree):
        # type: (str, nodes.Node) -> None
        destination = StringOutput(encoding='utf-8')
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.fignumbers = self.env.toc_fignumbers.get(docname, {})
        self.imgpath = '/' + posixpath.join(self.virtual_staticdir, self.imagedir)
        self.dlpath = '/' + posixpath.join(self.virtual_staticdir, '_downloads')
        self.current_docname = docname
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts['fragment']
        metatags = self.docwriter.clean_meta

        ctx = self.get_doc_context(docname, body, metatags)
        self.handle_page(docname, ctx, event_arg=doctree)

    def write_doc_serialized(self, docname, doctree):
        # type: (str, nodes.Node) -> None
        self.imgpath = '/' + posixpath.join(self.virtual_staticdir, self.imagedir)
        self.post_process_images(doctree)
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        self.index_page(docname, doctree, title)

    def load_indexer(self, docnames):
        # type: (Iterable[str]) -> None
        self.indexer = self.search  # type: ignore
        self.indexer.init_indexing(changed=docnames)  # type: ignore

    def _render_page(self, pagename, addctx, templatename, event_arg=None):
        # type: (str, Dict, str, str) -> Tuple[Dict, Dict]
        # This is mostly copied from StandaloneHTMLBuilder. However, instead
        # of rendering the template and saving the html, create a context
        # dict and pickle it.
        ctx = self.globalcontext.copy()
        ctx['pagename'] = pagename

        def pathto(otheruri, resource=False,
                   baseuri=self.get_target_uri(pagename)):
            # type: (str, bool, str) -> str
            if resource and '://' in otheruri:
                return otheruri
            elif not resource:
                otheruri = self.get_target_uri(otheruri)
                return relative_uri(baseuri, otheruri) or '#'
            else:
                return '/' + posixpath.join(self.virtual_staticdir, otheruri)
        ctx['pathto'] = pathto
        ctx['hasdoc'] = lambda name: name in self.env.all_docs
        ctx['encoding'] = self.config.html_output_encoding
        ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw)
        self.add_sidebars(pagename, ctx)
        ctx.update(addctx)

        newtmpl = self.app.emit_firstresult('html-page-context', pagename,
                                            templatename, ctx, event_arg)
        if newtmpl:
            templatename = newtmpl

        # create a dict that will be pickled and used by webapps
        doc_ctx = {
            'body': ctx.get('body', ''),
            'title': ctx.get('title', ''),
            'css': ctx.get('css', ''),
            'script': ctx.get('script', ''),
        }
        # partially render the html template to get at interesting macros
        template = self.templates.environment.get_template(templatename)
        template_module = template.make_module(ctx)
        for item in ['sidebar', 'relbar', 'script', 'css']:
            if hasattr(template_module, item):
                doc_ctx[item] = getattr(template_module, item)()

        return ctx, doc_ctx

    def handle_page(self, pagename, addctx, templatename='page.html',
                    outfilename=None, event_arg=None):
        # type: (str, Dict, str, str, str) -> None
        ctx, doc_ctx = self._render_page(pagename, addctx,
                                         templatename, event_arg)

        if not outfilename:
            outfilename = path.join(self.outdir, 'pickles',
                                    os_path(pagename) + self.out_suffix)
        ensuredir(path.dirname(outfilename))
        self.dump_context(doc_ctx, outfilename)

        # if there is a source file, copy the source file for the
        # "show source" link
        if ctx.get('sourcename'):
            source_name = path.join(self.staticdir,
                                    '_sources', os_path(ctx['sourcename']))
            ensuredir(path.dirname(source_name))
            copyfile(self.env.doc2path(pagename), source_name)

    def handle_finish(self):
        # type: () -> None
        # get global values for css and script files
        _, doc_ctx = self._render_page('tmp', {}, 'page.html')
        self.globalcontext['css'] = doc_ctx['css']
        self.globalcontext['script'] = doc_ctx['script']

        PickleHTMLBuilder.handle_finish(self)

        # move static stuff over to separate directory
        directories = [self.imagedir, '_static']
        for directory in directories:
            src = path.join(self.outdir, directory)
            dst = path.join(self.staticdir, directory)
            if path.isdir(src):
                if path.isdir(dst):
                    shutil.rmtree(dst)
                shutil.move(src, dst)
        self.copy_resources()

    def copy_resources(self):
        # type: () -> None
        # copy resource files to static dir
        dst = path.join(self.staticdir, '_static')

        if path.isdir(dst):
            for resource in RESOURCES:
                src = path.join(package_dir, 'files', resource)
                shutil.copy(src, dst)

    def dump_search_index(self):
        # type: () -> None
        self.indexer.finish_indexing()  # type: ignore


def setup(app):
    # type: (Sphinx) -> Dict[str, Any]
    if sphinx_version >= (2, 0):
        app.add_builder(WebSupportBuilder)

    return {
        'version': 'builtin',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }
