"""Module containing a preprocessor that converts outputs in the notebook from
one format to another.
"""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import base64
import os
import subprocess
import sys
from shutil import which
from tempfile import TemporaryDirectory

from traitlets import List, Unicode, Union, default

from nbconvert.utils.io import FormatSafeDict

from .convertfigures import ConvertFiguresPreprocessor

# inkscape path for darwin (macOS)
INKSCAPE_APP = "/Applications/Inkscape.app/Contents/Resources/bin/inkscape"
# Recent versions of Inkscape (v1.0) moved the executable from
# Resources/bin/inkscape to MacOS/inkscape
INKSCAPE_APP_v1 = "/Applications/Inkscape.app/Contents/MacOS/inkscape"

if sys.platform == "win32":
    try:
        import winreg
    except ImportError:
        import _winreg as winreg


class SVG2PDFPreprocessor(ConvertFiguresPreprocessor):
    """
    Converts all of the outputs in a notebook from SVG to PDF.
    """

    @default("from_format")
    def _from_format_default(self):
        return "image/svg+xml"

    @default("to_format")
    def _to_format_default(self):
        return "application/pdf"

    inkscape_version = Unicode(
        help="""The version of inkscape being used.

        This affects how the conversion command is run.
        """
    ).tag(config=True)

    @default("inkscape_version")
    def _inkscape_version_default(self):
        p = subprocess.Popen(
            [self.inkscape, "--version"],  # noqa:S603
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        output, _ = p.communicate()
        if p.returncode != 0:
            msg = "Unable to find inkscape executable --version"
            raise RuntimeError(msg)
        return output.decode("utf-8").split(" ")[1]

    # FIXME: Deprecate passing a string here
    command = Union(
        [Unicode(), List()],
        help="""
        The command to use for converting SVG to PDF

        This traitlet is a template, which will be formatted with the keys
        to_filename and from_filename.

        The conversion call must read the SVG from {from_filename},
        and write a PDF to {to_filename}.

        It could be a List (recommended) or a String. If string, it will
        be passed to a shell for execution.
        """,
    ).tag(config=True)

    @default("command")
    def _command_default(self):
        major_version = self.inkscape_version.split(".")[0]
        command = [self.inkscape]

        if int(major_version) < 1:
            # --without-gui is only needed for inkscape 0.x
            command.append("--without-gui")
            # --export-pdf is old name for --export-filename
            command.append("--export-pdf={to_filename}")
        else:
            command.append("--export-filename={to_filename}")

        command.append("{from_filename}")
        return command

    inkscape = Unicode(help="The path to Inkscape, if necessary").tag(config=True)

    @default("inkscape")
    def _inkscape_default(self):
        inkscape_path = which("inkscape")
        if inkscape_path is not None:
            return inkscape_path
        if sys.platform == "darwin":
            if os.path.isfile(INKSCAPE_APP_v1):
                return INKSCAPE_APP_v1
            # Order is important. If INKSCAPE_APP exists, prefer it over
            # the executable in the MacOS directory.
            if os.path.isfile(INKSCAPE_APP):
                return INKSCAPE_APP
        if sys.platform == "win32":
            wr_handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
            try:
                rkey = winreg.OpenKey(wr_handle, "SOFTWARE\\Classes\\inkscape.svg\\DefaultIcon")
                inkscape = winreg.QueryValueEx(rkey, "")[0]
            except FileNotFoundError:
                msg = "Inkscape executable not found"
                raise FileNotFoundError(msg) from None
            return inkscape
        return "inkscape"

    def convert_figure(self, data_format, data):
        """
        Convert a single SVG figure to PDF.  Returns converted data.
        """

        # Work in a temporary directory
        with TemporaryDirectory() as tmpdir:
            # Write fig to temp file
            input_filename = os.path.join(tmpdir, "figure.svg")
            # SVG data is unicode text
            with open(input_filename, "w", encoding="utf8") as f:
                f.write(data)

            # Call conversion application
            output_filename = os.path.join(tmpdir, "figure.pdf")

            template_vars = {"from_filename": input_filename, "to_filename": output_filename}
            if isinstance(self.command, list):
                full_cmd = [s.format_map(FormatSafeDict(**template_vars)) for s in self.command]
            else:
                # For backwards compatibility with specifying strings
                # Okay-ish, since the string is trusted
                full_cmd = self.command.format(*template_vars)
            subprocess.call(full_cmd, shell=isinstance(full_cmd, str))  # noqa: S603

            # Read output from drive
            # return value expects a filename
            if os.path.isfile(output_filename):
                with open(output_filename, "rb") as f:
                    # PDF is a nb supported binary, data type, so base64 encode.
                    return base64.encodebytes(f.read()).decode("utf-8")
            else:
                msg = "Inkscape svg to pdf conversion failed"
                raise TypeError(msg)
