# /*##########################################################################
#
# Copyright (c) 2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""Sphinx extension creating a link to an archive.

This Sphinx extension adds the possibility to create a zip archive and
include it in the generated HTML documentation.

This extension provides an *archive* directive.

Usage:

.. archive:: <relative_path/to/directory/to/compress>
   :filename: <name_of_the_archive_file, default: last_folder_of_directory.zip>
   :filter: <Space-separated list of included file patterns, default *.*>
   :basedir: <Name of the base directory in the archive, default: filename>

Examples:

To create a example.zip archive of the ../../example/ folder:

.. archive:: ../../examples/

To get more control on the name of the archive and its content:

.. archive:: ../../examples/
   :filename: myproject_examples.zip
   :filter: *.py *.png
   :basedir: myproject_examples

WARNING: The content of this directory is not checked for outdated documents.
"""
__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "01/09/2017"


import fnmatch
import logging
import os
import os.path
import posixpath
import shutil
import tempfile

from docutils.parsers.rst import directives, Directive
import docutils.nodes


_logger = logging.getLogger(__name__)


# docutils directive

class ArchiveDirective(Directive):
    """Add a link to download an archive

    This directive add an :class:`archive` node to the document tree.
    """

    has_content = False
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = {
        'filename': directives.unchanged,
        'filter': directives.unchanged,
        'basedir': directives.unchanged,
    }

    def run(self):
        node = archive('')

        # Get rst source file containing this directive
        source_file = self.state_machine.get_source_and_line()[0]
        if source_file is None:
            raise RuntimeError('Cannot get rst source file path')

        # Build input path from rst source file and directive argument
        input_path = self.arguments[0]
        if not input_path.startswith('/'):  # Argument is a relative path
            input_path = os.path.abspath(
                os.path.join(os.path.dirname(source_file), input_path))
        node['input_path'] = input_path

        default_basedir = os.path.basename(input_path)
        node['basedir'] = self.options.get('basedir', default_basedir)
        node['filename'] = self.options.get('filename',
                                            '.'.join((default_basedir, 'zip')))

        node['filter'] = self.options.get('filter', '*.*')

        return [node]


# archive doctuils node

class archive(docutils.nodes.General, docutils.nodes.Element, docutils.nodes.Inline):
    """archive node created by :class:`ArchiveDirective`"""
    pass


def visit_archive_html(self, node):
    """Node visitor to translate :class:`archive` nodes to HTML.

    :param self: Sphinx HTML Writter
    :param node: The :class:`archive` node to translate to HTML
    :raise: SkipNode as depart is not implemented
    """
    filename = node['filename']
    input_path = node['input_path']

    # Create a temporary folder to create archive content
    tmp_dir = tempfile.mkdtemp()

    # Copy selected content to temporary folder
    base_dir = os.path.join(tmp_dir, node['basedir'])

    def ignore(src, names):
        patterns = node['filter'].split()
        ignored_names = []
        for name in names:
            for pattern in patterns:
                if fnmatch.fnmatch(name, pattern):
                    break
            else:
                ignored_names.append(name)
        return ignored_names

    shutil.copytree(input_path, base_dir, ignore=ignore)

    # Compress temporary folder to zip
    output_filename = os.path.join(
        self.builder.outdir, '_downloads', filename)
    root, ext = os.path.splitext(output_filename)
    assert ext == '.zip'
    shutil.make_archive(root, 'zip', tmp_dir, node['basedir'])

    # Clean-up temporary folder
    shutil.rmtree(tmp_dir)

    # Generate HTML
    relative_path = posixpath.join(self.builder.dlpath, filename)
    self.body.append('<a href="%s">%s</a>' % (relative_path, filename))
    raise docutils.nodes.SkipNode


def visit_skip(self, node):
    """No-op node visitor"""
    raise docutils.nodes.SkipNode


# Extension setup

def setup(app):
    """Sphinx extension registration"""
    app.add_node(archive,
                 html=(visit_archive_html, None),
                 latex=(visit_skip, None))

    app.add_directive('archive', ArchiveDirective)

    return {'version': '0.1'}
