"""Convert the lock file to a powerpoint presentation."""

from __future__ import annotations

import argparse

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any

import tomli

from pptx import Presentation
from pptx.util import Pt

from spectrafit.api.pptx_model import PPTXDataAPI
from spectrafit.api.pptx_model import PPTXLayoutAPI
from spectrafit.api.pptx_model import PPTXPositionAPI
from spectrafit.api.pptx_model import PPTXRatioAPI
from spectrafit.api.pptx_model import PPTXStructureAPI
from spectrafit.plugins.converter import Converter


if TYPE_CHECKING:
    from collections.abc import MutableMapping

    import pandas as pd


class PPTXElements:
    """Generate a powerpoint presentation from a the spectrafit output."""

    def __init__(self, slide: type[Any]) -> None:
        """Create a powerpoint presentation from a the spectrafit output.

        Args:
            slide (type): The slide of the powerpoint presentation.

        """
        self.slide = slide

    def create_textbox(
        self,
        text: str,
        position: PPTXPositionAPI,
        font_size: Pt | None = None,
    ) -> None:
        """Create a textbox from the input file.

        Args:
            text (str): The text of the textbox.
            position (PPTXPositionAPI): The position of the textbox in the powerpoint
                presentation.
            font_size (Optional[Pt]): The font size of the textbox. Defaults to 16 pt.

        """
        tx_box = self.slide.shapes.add_textbox(
            left=position.left,
            top=position.top,
            width=position.width,
            height=position.height,
        )

        # Set default font size if None
        if font_size is None:  # pragma: no cover
            font_size = Pt(16)

        tf = tx_box.text_frame
        tf.text = text
        tf.size = font_size

    def create_figure(
        self,
        fname: Path,
        position_figure: PPTXPositionAPI,
        position_textbox: PPTXPositionAPI,
        text: str,
        font_size: Pt,
    ) -> None:
        """Create a figure from the input file.

        Args:
            fname (Path): The temporay filename of the figure.
            position_figure (PPTXPositionAPI): The position of the figure in the
                powerpoint presentation.
            position_textbox (PPTXPositionAPI): The position of the textbox in the
                powerpoint presentation.
            text (str): The text of the textbox.
            font_size (Pt): The font size of the textbox.

        """
        self.slide.shapes.add_picture(
            str(fname),
            left=position_figure.left,
            top=position_figure.top,
            width=position_figure.width,
            height=position_figure.height,
        )
        self.create_textbox(
            text=text,
            position=position_textbox,
            font_size=font_size,
        )

    def create_table(
        self,
        df: pd.DataFrame,
        position_table: PPTXPositionAPI,
        transpose: bool,
        index_hidden: bool,
        text: str,
        position_textbox: PPTXPositionAPI,
        font_size: Pt | None = None,
    ) -> None:
        """Create a table from the input file.

        Args:
            df (pd.DataFrame): The data of the table.
                powerpoint presentation.
            position_table (PPTXPositionAPI): The position of the table in the
                powerpoint presentation.
            transpose (bool): Transpose the table for row-like presentation in
                powerpoint presentation.
            index_hidden (bool): Hide the index of the table in the powerpoint
                presentation.
            text (str): The text of the table legend.
            position_textbox (PPTXPositionAPI): The position of the textbox in the
                powerpoint presentation.
            font_size (Pt, optional): The font size of the table. Defaults to Pt(12).

        """
        df = df.round(2)
        if transpose:
            df = df.transpose()

        # Set default font size if None
        if font_size is None:  # pragma: no cover
            font_size = Pt(12)

        self.extract_table(
            df=df,
            position_table=position_table,
            index_hidden=index_hidden,
        )

        self.create_textbox(
            text=text,
            position=PPTXPositionAPI(
                left=position_textbox.left,
                top=position_textbox.top,
                width=position_textbox.width,
                height=position_textbox.height,
            ),
            font_size=font_size,
        )

    def extract_table(
        self,
        df: pd.DataFrame,
        position_table: PPTXPositionAPI,
        index_hidden: bool,
    ) -> None:
        """Create a table from the input file.

        Args:
            df (pd.DataFrame): The data of the table(s) in the powerpoint presentation.
            position_table (PPTXPositionAPI): The position of the table in the
                powerpoint presentation.
            index_hidden (bool): Hide the index of the table in the powerpoint
                presentation.

        """
        rows, cols = df.shape
        table = self.slide.shapes.add_table(
            rows=rows + 1,
            cols=cols + (not index_hidden),
            left=position_table.left,
            top=position_table.top,
            width=position_table.width,
            height=position_table.height,
        )
        table.table.cell(0, 0).text = ""
        if index_hidden:
            for i, col in enumerate(df.columns):
                table.table.cell(0, i).text = str(col)
                for j, value in enumerate(df[col]):
                    table.table.cell(j + 1, i).text = str(value)
        else:
            for i, index in enumerate(df.index, start=1):
                table.table.cell(i, 0).text = str(index)
            for i, col in enumerate(df.columns, start=1):
                table.table.cell(0, i).text = col
                for j, value in enumerate(df[col]):
                    table.table.cell(j + 1, i).text = str(value)

    def create_credit(
        self,
        fname: Path,
        text: str,
        position_logo: PPTXPositionAPI,
        position_text: PPTXPositionAPI,
        font_size: Pt | None = None,
    ) -> None:
        """Create a credit for spectrafit.

        Args:
            fname (Path): The temporay filename of the figure.
            text (str): The text of the credit.
            position_logo (PPTXPositionAPI): The position of the logo in the powerpoint
                presentation.
            position_text (PPTXPositionAPI): The position of the text in the powerpoint
                presentation.
            font_size (Optional[Pt]): The font size of the textbox. Defaults to 14 pt.

        """  # Set default font size if None
        if font_size is None:  # pragma: no cover
            font_size = Pt(14)

        self.create_figure(
            fname=fname,
            position_figure=position_logo,
            text=text,
            position_textbox=position_text,
            font_size=font_size,
        )

    def create_title(self, text: str, position: PPTXPositionAPI) -> None:
        """Create a title from the input file.

        Args:
            text (str): The text of the title.
            position (PPTXPositionAPI): The position of the title in the powerpoint
                presentation.

        """
        title = self.slide.shapes.title
        title.text = text
        title.left = position.left
        title.top = position.top
        title.width = position.width
        title.height = position.height

    def create_subtitle(self, text: str, position: PPTXPositionAPI, index: int) -> None:
        """Create a subtitle from the input file.

        Args:
            text (str): The text of the subtitle.
            position (PPTXPositionAPI): The position of the subtitle in the powerpoint
                presentation.
            index (int): The index of the subtitle in the powerpoint presentation.

        """
        subtitle = self.slide.placeholders[index]
        subtitle.text = text
        subtitle.left = position.left
        subtitle.top = position.top
        subtitle.width = position.width
        subtitle.height = position.height


class PPTXLayout(PPTXElements):
    """Generate a powerpoint presentation from a the spectrafit output."""

    def __init__(
        self,
        ratio: PPTXRatioAPI,
        structure: PPTXStructureAPI,
        fname: Path,
    ) -> None:
        """Create a powerpoint presentation from a the spectrafit output.

        Args:
            ratio (PPTXRatioAPI): The ratio of the powerpoint presentation.
            structure (PPTXStructureAPI): The structure of the powerpoint presentation.
            fname (Path): The temporay filename of the powerpoint presentation.

        """
        self.ratio = ratio
        self.structure = structure
        self.fname = fname

        self._initialiaze()
        super().__init__(self.slide)

    def _initialiaze(self) -> None:
        """Initialize the powerpoint presentation."""
        self.prs = Presentation()
        self.prs.slide_width = self.ratio.width
        self.prs.slide_height = self.ratio.height
        self.slide = self.prs.slides.add_slide(self.prs.slide_layouts[3])

    def top_element(self) -> None:
        """Create the top element of the powerpoint presentation."""
        self.create_title(
            text=self.structure.header.text,
            position=self.structure.header.position,
        )

    def lefr_element(self) -> None:
        """Create the left element of the powerpoint presentation."""
        self.create_subtitle(
            text=self.structure.sub_title_left.text,
            position=self.structure.sub_title_left.position,
            index=self.structure.sub_title_left.index,
        )
        self.create_figure(
            fname=self.structure.sub_title_left.figure.fname,
            position_figure=self.structure.sub_title_left.figure.position,
            text=self.structure.sub_title_left.figure.description.text,
            position_textbox=self.structure.sub_title_left.figure.description.position,
            font_size=self.structure.sub_title_left.figure.description.font_size,
        )

    def right_element(self) -> None:
        """Create the right element of the powerpoint presentation."""
        self.create_subtitle(
            text=self.structure.sub_title_right.text,
            position=self.structure.sub_title_right.position,
            index=self.structure.sub_title_right.index,
        )
        self.create_table(
            df=self.structure.sub_title_right.table_1.df,
            position_table=self.structure.sub_title_right.table_1.position,
            transpose=self.structure.sub_title_right.table_1.transpose,
            index_hidden=self.structure.sub_title_right.table_1.index_hidden,
            text=self.structure.sub_title_right.table_1.description.text,
            position_textbox=(
                self.structure.sub_title_right.table_1.description.position
            ),
            font_size=self.structure.sub_title_right.table_1.description.font_size,
        )
        self.create_table(
            df=self.structure.sub_title_right.table_2.df,
            position_table=self.structure.sub_title_right.table_2.position,
            transpose=self.structure.sub_title_right.table_2.transpose,
            index_hidden=self.structure.sub_title_right.table_2.index_hidden,
            text=self.structure.sub_title_right.table_2.description.text,
            position_textbox=(
                self.structure.sub_title_right.table_2.description.position
            ),
            font_size=self.structure.sub_title_right.table_2.description.font_size,
        )
        self.create_table(
            df=self.structure.sub_title_right.table_3.df,
            position_table=self.structure.sub_title_right.table_3.position,
            transpose=self.structure.sub_title_right.table_3.transpose,
            index_hidden=self.structure.sub_title_right.table_3.index_hidden,
            text=self.structure.sub_title_right.table_3.description.text,
            position_textbox=(
                self.structure.sub_title_right.table_3.description.position
            ),
            font_size=self.structure.sub_title_right.table_3.description.font_size,
        )
        self.create_credit(
            fname=self.structure.sub_title_right.credit.fname,
            text=self.structure.sub_title_right.credit.description.text,
            position_logo=self.structure.sub_title_right.credit.position,
            position_text=self.structure.sub_title_right.credit.description.position,
            font_size=self.structure.sub_title_right.credit.description.font_size,
        )

    def save(self) -> None:
        """Save the powerpoint presentation."""
        self.prs.save(str(self.fname))

    def __call__(self) -> None:
        """Create the powerpoint presentation."""
        self.top_element()
        self.lefr_element()
        self.right_element()
        self.save()


class PPTXConverter(Converter):
    """Generate a powerpoint presentation from a the spectrafit output.

    Attributes:
        pixel_size (Dict[str, Dict[str, int]]): The pixel size of the powerpoint
            presentation.

    """

    pixel_size = PPTXLayoutAPI.pptx_formats.keys()

    def get_args(self) -> dict[str, Any]:
        """Get the arguments from the command line.

        Returns:
            Dict[str, Any]: Return the input file arguments as a dictionary without
                additional information beyond the command line arguments.

        """
        parse = argparse.ArgumentParser(
            description="Converter for 'SpectraFit' from *.lock output to a "
            "PowerPoint presentation.",
            usage="%(prog)s [options] infile",
        )
        parse.add_argument(
            "infile",
            type=Path,
            help="Filename of the *.lock file to convert to a powerpoint presentation.",
        )
        parse.add_argument(
            "-f",
            "--file-format",
            help="File format of the PowerPoint presentation. Default is '16:9'.",
            type=str,
            default="16:9",
            choices=self.pixel_size,
        )
        return vars(parse.parse_args())

    @staticmethod
    def convert(infile: Path, file_format: str) -> MutableMapping[str, Any]:
        """Convert the lock file to a powerpoint presentation.

        Args:
            infile (Path): The input file of the as a path object.
            file_format (str): The powerpoint presentation file format.

        Raises:
            ValueError: If the file format is not supported.
            ValueError: If the input file is not a `*.lock` file.

        Returns:
            MutableMapping[str, Any]: The converted file as a dictionary.

        """
        if file_format not in PPTXConverter.pixel_size:
            msg = (
                f"File format '{file_format}' is not supported;"
                f"it must be one of {PPTXConverter.pixel_size}"
            )
            raise ValueError(msg)

        if infile.suffix != ".lock":
            msg = f"File format '{infile.suffix}' is not supported; it must be '.lock'"
            raise ValueError(msg)

        with infile.open("rb") as f:
            data = PPTXDataAPI(**tomli.load(f))

        return {file_format: data}

    def save(self, data: Any, fname: Path, export_format: str) -> None:
        """Save the powerpoint presentation."""
        pptx_layout = PPTXLayoutAPI(
            export_format,
            data=data[export_format],
        ).get_pptx_layout()

        PPTXLayout(
            ratio=pptx_layout.ratio,
            structure=pptx_layout.structure,
            fname=Path(f"{fname.stem}_{export_format.replace(':', '_')}.pptx"),
        )()

    def __call__(self) -> None:
        """Convert the lock file to a powerpoint presentation."""
        args = self.get_args()
        data = self.convert(args["infile"], args["file_format"])
        self.save(data, args["infile"], args["file_format"])


def command_line_runner() -> None:
    """Command line interface for the converter plugin."""
    PPTXConverter()()
