# -*- coding: utf-8 -*-
"""
    sphinx.environment.collectors.asset
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    The image collector for sphinx.environment.

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

import os
from os import path
from glob import glob

from six import iteritems, itervalues

from docutils import nodes
from docutils.utils import relative_path

from sphinx import addnodes
from sphinx.environment.collectors import EnvironmentCollector
from sphinx.util import logging
from sphinx.util.i18n import get_image_filename_for_language, search_image_for_language
from sphinx.util.images import guess_mimetype

if False:
    # For type annotation
    from typing import Dict, List, Set, Tuple  # NOQA
    from docutils import nodes  # NOQA
    from sphinx.sphinx import Sphinx  # NOQA
    from sphinx.environment import BuildEnvironment  # NOQA

logger = logging.getLogger(__name__)


class ImageCollector(EnvironmentCollector):
    """Image files collector for sphinx.environment."""

    def clear_doc(self, app, env, docname):
        # type: (Sphinx, BuildEnvironment, unicode) -> None
        env.images.purge_doc(docname)

    def merge_other(self, app, env, docnames, other):
        # type: (Sphinx, BuildEnvironment, Set[unicode], BuildEnvironment) -> None
        env.images.merge_other(docnames, other.images)

    def process_doc(self, app, doctree):
        # type: (Sphinx, nodes.Node) -> None
        """Process and rewrite image URIs."""
        docname = app.env.docname

        for node in doctree.traverse(nodes.image):
            # Map the mimetype to the corresponding image.  The writer may
            # choose the best image from these candidates.  The special key * is
            # set if there is only single candidate to be used by a writer.
            # The special key ? is set for nonlocal URIs.
            candidates = {}  # type: Dict[unicode, unicode]
            node['candidates'] = candidates
            imguri = node['uri']
            if imguri.startswith('data:'):
                candidates['?'] = imguri
                continue
            elif imguri.find('://') != -1:
                candidates['?'] = imguri
                continue
            rel_imgpath, full_imgpath = app.env.relfn2path(imguri, docname)
            if app.config.language:
                # substitute figures (ex. foo.png -> foo.en.png)
                i18n_full_imgpath = search_image_for_language(full_imgpath, app.env)
                if i18n_full_imgpath != full_imgpath:
                    full_imgpath = i18n_full_imgpath
                    rel_imgpath = relative_path(path.join(app.srcdir, 'dummy'),
                                                i18n_full_imgpath)
            # set imgpath as default URI
            node['uri'] = rel_imgpath
            if rel_imgpath.endswith(os.extsep + '*'):
                if app.config.language:
                    # Search language-specific figures at first
                    i18n_imguri = get_image_filename_for_language(imguri, app.env)
                    _, full_i18n_imgpath = app.env.relfn2path(i18n_imguri, docname)
                    self.collect_candidates(app.env, full_i18n_imgpath, candidates, node)

                self.collect_candidates(app.env, full_imgpath, candidates, node)
            else:
                candidates['*'] = rel_imgpath

            # map image paths to unique image names (so that they can be put
            # into a single directory)
            for imgpath in itervalues(candidates):
                app.env.dependencies[docname].add(imgpath)
                if not os.access(path.join(app.srcdir, imgpath), os.R_OK):
                    logger.warning('image file not readable: %s' % imgpath,
                                   location=node, type='image', subtype='not_readable')
                    continue
                app.env.images.add_file(docname, imgpath)

    def collect_candidates(self, env, imgpath, candidates, node):
        # type: (BuildEnvironment, unicode, Dict[unicode, unicode], nodes.Node) -> None
        globbed = {}  # type: Dict[unicode, List[unicode]]
        for filename in glob(imgpath):
            new_imgpath = relative_path(path.join(env.srcdir, 'dummy'),
                                        filename)
            try:
                mimetype = guess_mimetype(filename)
                if mimetype not in candidates:
                    globbed.setdefault(mimetype, []).append(new_imgpath)
            except (OSError, IOError) as err:
                logger.warning('image file %s not readable: %s' % (filename, err),
                               location=node, type='image', subtype='not_readable')
        for key, files in iteritems(globbed):
            candidates[key] = sorted(files, key=len)[0]  # select by similarity


class DownloadFileCollector(EnvironmentCollector):
    """Download files collector for sphinx.environment."""

    def clear_doc(self, app, env, docname):
        # type: (Sphinx, BuildEnvironment, unicode) -> None
        env.dlfiles.purge_doc(docname)

    def merge_other(self, app, env, docnames, other):
        # type: (Sphinx, BuildEnvironment, Set[unicode], BuildEnvironment) -> None
        env.dlfiles.merge_other(docnames, other.dlfiles)

    def process_doc(self, app, doctree):
        # type: (Sphinx, nodes.Node) -> None
        """Process downloadable file paths. """
        for node in doctree.traverse(addnodes.download_reference):
            targetname = node['reftarget']
            rel_filename, filename = app.env.relfn2path(targetname, app.env.docname)
            app.env.dependencies[app.env.docname].add(rel_filename)
            if not os.access(filename, os.R_OK):
                logger.warning('download file not readable: %s' % filename,
                               location=node, type='download', subtype='not_readable')
                continue
            node['filename'] = app.env.dlfiles.add_file(app.env.docname, filename)


def setup(app):
    # type: (Sphinx) -> Dict
    app.add_env_collector(ImageCollector)
    app.add_env_collector(DownloadFileCollector)

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