"""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 .character_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
    """

    translatex = str.maketrans(invalid_latex)

    def __init__(self, *, boxname: str, 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.boxname = as_latex_command(boxname)
        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:
            % Add "\newsavebox{self.boxname}" to your preamble, then include
            % the figure with
            %
            % \input{{<file name>.pdf_tex}}
            %
            % To scale the figure, write
            %
            % \def{self.widthcommand}{{<your desired width>}}
            % \input{{<file name>.pdf_tex}}
            %
            """)

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

            \ifx{self.widthcommand}\undefined%
              \savebox{{{self.boxname}}}{{
                \includegraphics[scale=1]{{{graphics_filename}}}
                }}
            \else%
              \sbox{{{self.boxname}}}{{
                \includegraphics[width={self.widthcommand}]{{{graphics_filename}}}
                }}
            \fi%
            \def\unitwidth{{{self.widthcommand}}}
            \def\unitheight{{\ht{self.boxname}}}

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

    def add_text(self, text, position, *,
                 rotation=0, color='black', anchor='center'):
        self.addline(rf"  \node [inner sep=0pt, {color}, "
                     rf"rotate={rotation}, "
                     rf"anchor={anchor}] "
                     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')
        with open(filename, 'w') as outfile:
            outfile.write(self.latexcode.translate(self.translatex))


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)
