"""Apply any scale command to subsequent figure environment.

Implementation Note: The not a number (NAN) marker is used to indicate absence of scale command.
"""

from collections.abc import Iterable
from enum import Enum
import math

from liitos import log

Modus = Enum('Modus', 'COPY SCALE')
NAN = float('nan')

EQ = '='
SP = ' '
SCALE_START_TRIGGER_STARTSWITH = r'\scale'
BARE_GRAPHICS_START_STARTSWITH = r'\includegraphics{'
WRAPPED_GRAPHICS_START_IN = r'\pandocbounded{\includegraphics'
EVEN_MORE_SO = r'\pandocbounded{\includegraphics[keepaspectratio,alt={'  # HACK A DID ACK


def filter_seek_scale(line: str, slot: int, modus: Modus, rescale: float, outgoing: list[str]) -> tuple[Modus, float]:
    r"""Filter line, seek for a scale command, and return updated mnodus, rescale pair.

    Examples:

    >>> o = []
    >>> line = SCALE_START_TRIGGER_STARTSWITH + EQ # r'\scale='
    >>> m, r = filter_seek_scale(line, 0, Modus.COPY, NAN, o)
    >>> assert not o
    >>> assert m == Modus.SCALE
    >>> assert math.isnan(r)

    >>> o = []
    >>> m, r = filter_seek_scale('foo', 0, Modus.COPY, NAN, o)
    >>> assert o == ['foo']
    >>> assert m == Modus.COPY
    >>> assert math.isnan(r)

    >>> o = []
    >>> m, r = filter_seek_scale(r'\scale=80\%', 0, Modus.COPY, NAN, o)
    >>> assert not o
    >>> assert m == Modus.SCALE
    >>> assert r == 0.8

    >>> o = []
    >>> m, r = filter_seek_scale(r'\scale   80\%', 0, Modus.COPY, NAN, o)
    >>> assert not o
    >>> assert m == Modus.SCALE
    >>> assert r == 0.8

    >>> o = []
    >>> m, r = filter_seek_scale(r'\scale =  0.8', 0, Modus.COPY, NAN, o)
    >>> assert not o
    >>> assert m == Modus.SCALE
    >>> assert r == 0.8
    """
    if any(line.startswith(SCALE_START_TRIGGER_STARTSWITH + other) for other in (EQ, SP)):
        log.info(f'trigger a scale mod for the next figure environment at line #{slot + 1}|{line}')
        modus = Modus.SCALE
        scale = line  # only for reporting will not pass the filter
        try:
            # \scale    =    75\%  --> 75\%
            # \scale         75\%  --> 75\%
            sca = scale.split(EQ, 1)[1].strip() if EQ in scale else SP.join(scale.split()).split(SP, 1)[1].strip()
            rescale = float(sca.replace(r'\%', '')) / 100 if r'\%' in sca else float(sca)
        except Exception as err:
            log.error(f'failed to parse scale value from {line.strip()} with err: {err}')
    else:
        outgoing.append(line)

    return modus, rescale


def filter_seek_figure(line: str, slot: int, modus: Modus, rescale: float, outgoing: list[str]) -> tuple[Modus, float]:
    r"""Filter line, seek for a figure, rescale if applicable, and return updated mnodus, rescale pair.

    Examples:

    >>> o = []
    >>> m, r = filter_seek_figure(r'\includegraphics{', 0, Modus.COPY, NAN, o)
    >>> assert o == [r'\includegraphics{']
    >>> assert m == Modus.COPY
    >>> assert math.isnan(r)

    >>> o = []
    >>> m, r = filter_seek_figure('foo', 0, Modus.COPY, 0.8, o)
    >>> assert o == ['foo']
    >>> assert m == Modus.COPY
    >>> assert r == 0.8

    >>> o = []
    >>> rescale = 0.8
    >>> m, r = filter_seek_figure(r'\pandocbounded{\includegraphics', 0, Modus.COPY, rescale, o)
    >>> assert o[0].startswith(r'\pandocbounded{\includegraphics')
    >>> assert f'textwidth,height={rescale}' in o[0]
    >>> assert m == Modus.COPY
    >>> assert math.isnan(r)

    >>> o = []
    >>> m, r = filter_seek_figure(r'\pandocbounded{\includegraphics', 0, Modus.COPY, NAN, o)
    >>> assert o[0].startswith(r'\pandocbounded{\includegraphics')
    >>> assert m == Modus.COPY
    >>> assert math.isnan(r)
    """
    if line.startswith(BARE_GRAPHICS_START_STARTSWITH):
        if not math.isnan(rescale):
            log.info(f'- found the scale target start at line #{slot + 1}|{line}')
            target = line.replace(BARE_GRAPHICS_START_STARTSWITH, '{')
            option = f'[width={round(rescale, 2)}\\textwidth,height={round(rescale, 2)}' '\\textheight,keepaspectratio]'
            outgoing.append(f'\\includegraphics{option}{target}')
        else:
            outgoing.append(line)
        modus = Modus.COPY
        rescale = NAN
    elif EVEN_MORE_SO in line:
        if not math.isnan(rescale):
            log.info(f'- found the scale target start at line #{slot + 1}|{line}')
            target = line.replace(WRAPPED_GRAPHICS_START_IN, '').replace('[keepaspectratio', '')
            parts = target.split('}}')
            rest, inside = ('', '') if len(parts) < 2 else (parts[1].lstrip('}'), parts[0] + '}')
            option = f'[width={round(rescale, 2)}\\textwidth,height={round(rescale, 2)}' '\\textheight,keepaspectratio'
            patched = f'{WRAPPED_GRAPHICS_START_IN}{option}{inside}}}{rest}'
            hack = '}}'
            if not patched.endswith(hack):
                patched += hack
            outgoing.append(patched)
        else:
            outgoing.append(line)
        modus = Modus.COPY
        rescale = NAN
    elif WRAPPED_GRAPHICS_START_IN in line:
        if not math.isnan(rescale):
            log.info(f'- found the scale target start at line #{slot + 1}|{line}')
            target = line.replace(WRAPPED_GRAPHICS_START_IN, '').replace('[keepaspectratio]', '')
            parts = target.split('}}')
            rest, inside = ('', '') if len(parts) < 2 else (parts[1].lstrip('}'), parts[0] + '}')
            option = f'[width={round(rescale, 2)}\\textwidth,height={round(rescale, 2)}' '\\textheight,keepaspectratio'
            outgoing.append(f'{WRAPPED_GRAPHICS_START_IN}{option}{inside}}}{rest}')
        else:
            outgoing.append(line)
        modus = Modus.COPY
        rescale = NAN
    else:
        outgoing.append(line)

    return modus, rescale


def scale(incoming: Iterable[str], lookup: dict[str, str] | None = None) -> list[str]:
    r"""Scan for scale command and if, apply it to the includegraphics LaTeX command.

    Examples:

    >>> in_lines = [r'\scale=80\%', '', r'\includegraphics{', '', 'quux']
    >>> scaled = scale(in_lines)
    >>> scaled
    ['', '\\includegraphics[width=0.8\\textwidth,height=0.8\\textheight,keepaspectratio]{', '', 'quux']


    >>> in_lines = ['foo', '', r'\includegraphics{', '', 'quux']
    >>> scaled = scale(in_lines)
    >>> scaled
    ['foo', '', '\\includegraphics{', '', 'quux']
    """
    outgoing: list[str] = []
    modus = Modus.COPY
    rescale = NAN
    for slot, line in enumerate(incoming):
        line = line.rstrip('\n')
        if modus == Modus.COPY:
            modus, rescale = filter_seek_scale(line, slot, modus, rescale, outgoing)
        else:  # if modus == Modus.SCALE:
            modus, rescale = filter_seek_figure(line, slot, modus, rescale, outgoing)

    return outgoing
