"""matplatex: export matplotlib figures as pdf and text separately for
use in LaTeX.

Copyright (C) 2023 Johannes Sørby Heines

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

import sys

from .__about__ import __version__
from .string_replacements import invalid_latex


def as_latex_command(string: str, /):
    """Make sure the string begins with exactly one backslash."""
    return '\\' + string.strip('\\')

class LaTeXinput:
    """Text to be input into a LaTeX document to include a figure.

    Instance variables:
    latexcode       The text in question as a string
    open_graphics   Track whether a figure environment is currently
                    open in latexcode
    boxname         Name of the box defined in the LaTeX preamble which
                    will be used to size the figure.
    widthcommand    The LaTeX length command which will be used to
                    define the width of the figure.

    Public methods:
    __init__            Constructor.
    includegraphics     Open a figure environment and include a figure.
    add_text            Draw a text box.
    endgraphics         Close a figure environment.
    addline             Add a single line of code to latexcode.
    write               Write latexcode to a file.
    """

    # only works for single characters
    # translatex = str.maketrans(invalid_latex)

    def __init__(self, *, widthcommand: str):
        """Constructor for the LaTeXinput class.

        Keyword only arguments:
        boxname -- name of the box defined in the LaTeX preamble which
            will be used to size the figure.
        widthcommand -- the LaTeX length command which will be used to
            define the width of the figure.
        """
        self.open_graphics = False
        self.widthcommand = as_latex_command(widthcommand)

        self.latexcode = trim(
            rf"""% This file was automatically generated by matpLaTeX {__version__}.
            % The source code is at https://github.com/johashei/matplatex.
            %
            % Requires tikz
            %
            % Usage:
            %
            % Requires the tikz package.
            %
            % Set the desired with of the figure using
            %   \setlength{{{self.widthcommand}}}{{<your desired width>}}
            %
            % You may need to first define the length with
            %   \newlength{{{self.widthcommand}}} (only necessary once)
            %
            % Include the figure with
            %   \input{{<file name>.pdf_tex}}
            % or
            %   \import{{<path>}}{{<file name>.pdf_tex}}
            %
            %
            """
            )

    def includegraphics(self, graphics_filename, height_to_width: float):
        """ Start a tikzpicture and include the graphics."""
        if self.open_graphics:
            self.endgraphics()
        self.addline('')
        self.latexcode += trim(
            rf"""\begingroup

            \newlength{{\unitheight}}
            \setlength{{\unitheight}}{{{height_to_width}{self.widthcommand}}}

            \hspace{{-\parindent}}
            \begin{{tikzpicture}}[x={self.widthcommand}, y=\unitheight]
              \node[inner sep=0pt, above right] (graphics) at (0,0) {{
                \includegraphics[width={self.widthcommand}]{{{graphics_filename}}}}};
            """)
        self.open_graphics = True

    def add_text(self, text, position, *,
                 rotation=0, color=(0, 0, 0), alpha=1, anchor='center'):
        if len(color)==4:
            alpha = color[3]
        self.addline(rf"  \node [inner sep=0pt, "
                     rf"text={{rgb,1:red,{color[0]}; "
                                  rf"green,{color[1]}; "
                                  rf"blue,{color[2]}}}, "
                     rf"rotate={rotation}, "
                     rf"anchor={anchor}, "
                     rf"opacity={alpha}] "
                     rf"at ({position[0]}, {position[1]}) "
                     rf"{{{text}}};")

    def endgraphics(self):
        self.addline(r"\end{tikzpicture}")
        self.addline(r"\endgroup")
        self.open_graphics = False

    def addline(self, text):
        self.latexcode += f"\n{text}"

    def write(self, filename):
        if self.open_graphics:
            self.endgraphics()
        self.addline(rf"\global\let{self.widthcommand}\undefined%")
#        self.latexcode.replace('  ', '\t')
        self.latexcode = replace_multiple(self.latexcode, invalid_latex)
        with open(filename, 'w') as outfile:
            outfile.write(self.latexcode)


def replace_multiple(string: str, replacements: dict) -> str:
    for key, val in replacements.items():
        string = string.replace(key, val)
    return string


def trim(string):
    """Trim a multiline string.

    Docstring processing algorithm as implemented in PEP 257.
    """
    if not string:
        return ''
    lines = string.expandtabs().splitlines()
    indent = sys.maxsize
    for line in lines[1:]:
        stripped = line.lstrip()
        if stripped:
            indent = min(indent, len(line) - len(stripped))
    trimmed = [lines[0].strip()]
    if indent < sys.maxsize:
        for line in lines[1:]:
            trimmed.append(line[indent:].rstrip())
    while trimmed and not trimmed[-1]:
        trimmed.pop()
    while trimmed and not trimmed[0]:
        trimmed.pop(0)
    return '\n'.join(trimmed)
