#!/usr/bin/env python3

"""
Matplotlib bindings.
"""

from typing import Any, List, Optional

from matplotlib.lines import Line2D
from matplotlib.ticker import MultipleLocator
from pudb import set_trace as bp  # noqa: F401
from matplotlib import pyplot

LINESTYLE = "solid"
LINEWIDTH = 2
COLOR = "black"


class Interface:
    """
    User interface to plot matplotlib graphs.

    # Example

    ```python
    # To import the class.
    from interface import Interface

    # To create the window.
    interface = Interface()
    ```
    """

    def __init__(self, build: bool = False, nrows: int = 1, ncols: int = 1) -> None:
        if build:
            self.build(nrows, ncols)

    def build(self, nrows: int = 1, ncols: int = 1) -> None:
        # Create a figure.
        self.figure, self.axes = pyplot.subplots(nrows, ncols)
        self.plots: List[List[Line2D]] = []
        self.legend_titles: List[str] = []
        self.arange_axes()
        self.show(False)

    def set_title(self, title: str = "") -> None:
        # Set the title.
        self.figure.suptitle(title)

    def set_xlabel(self, index: int, label: str = "") -> None:
        # Change the label for the x axis.
        self.figure.axes[index].set_xlabel(label)

    def set_ylabel(self, index: int, label: str = "") -> None:
        # Change the label for the y axis.
        self.figure.axes[index].set_ylabel(label)

    def arange_axes(self) -> None:
        """Arange subplot axes."""
        for axis in self.figure.axes:
            axis.grid(True, which="major", color="#AAAAAA", linestyle="solid", alpha=0.3)
            axis.grid(True, which="minor", color="#BBBBBB", linestyle="dotted", alpha=0.3)
            axis.tick_params(which="major", length=3)
            axis.tick_params(which="minor", length=1)
            axis.tick_params(
                axis="x", which="both", direction="in", top=True, left="off", labelleft="on"
            )
            axis.tick_params(
                axis="y", which="both", direction="in", right=True, left="off", labelleft="on"
            )
        self.set_axis_limits()

    def set_axis_limits(
        self,
        index: Optional[int] = None,
        xmin: float = 0,
        xmax: float = 1,
        ymin: float = 0,
        ymax: float = 1,
    ) -> None:
        """
        Change xlim and ylim for the given axis index. If index is None, all axes are changed.
        Also automatically update minor grid ticks.
        """
        indices = range(0, len(self.figure.axes))
        if index is not None:
            indices = range(index, index + 1)
        for index in indices:
            WIDTH = xmax - xmin
            HEIGHT = ymax - ymin
            if WIDTH > 0:
                self.figure.axes[index].set_xlim(xmin, xmax)
            else:
                print("When changing axis limits, xmax is lower than xmin..")
            if HEIGHT > 0:
                self.figure.axes[index].set_ylim(ymin, ymax)
            else:
                print("When changing axis limits, ymax is lower than ymin..")
            XTICKS = self.figure.axes[index].xaxis.get_ticklocs()
            YTICKS = self.figure.axes[index].yaxis.get_ticklocs()
            XTICK_STEP = XTICKS[1] - XTICKS[0]
            YTICK_STEP = YTICKS[1] - YTICKS[0]
            NUMBER_MINOR_TICKS = 5
            self.figure.axes[index].xaxis.set_minor_locator(
                MultipleLocator(XTICK_STEP / NUMBER_MINOR_TICKS)
            )
            self.figure.axes[index].yaxis.set_minor_locator(
                MultipleLocator(YTICK_STEP / NUMBER_MINOR_TICKS)
            )

    def ticks(
        self,
        index: int = 0,
        xmajor: Optional[float] = None,
        xminor: Optional[float] = None,
        ymajor: Optional[float] = None,
        yminor: Optional[float] = None,
    ) -> None:
        """Set xticks major and minor locators."""
        if xmajor is not None:
            self.figure.axes[index].xaxis.set_major_locator(MultipleLocator(xmajor))
        if xminor is not None:
            self.figure.axes[index].xaxis.set_minor_locator(MultipleLocator(xminor))
        if ymajor is not None:
            self.figure.axes[index].yaxis.set_major_locator(MultipleLocator(xmajor))
        if yminor is not None:
            self.figure.axes[index].yaxis.set_minor_locator(MultipleLocator(xminor))

    def set_legend_title(self, index: int = 0, title: str = "") -> None:
        """Set the title of the legend."""
        while len(self.legend_titles) < index + 1:
            self.legend_titles.append("")
        self.legend_titles[index] = title
        self.update()

    def plot_kwargs_with_default(self, kwargs: Any) -> Any:
        """Update plot kwargs with default value of parameters."""
        if "linestyle" not in kwargs.keys():
            kwargs["linestyle"] = LINESTYLE
        if "linewidth" not in kwargs.keys():
            kwargs["linewidth"] = LINEWIDTH
        if "color" not in kwargs.keys():
            kwargs["color"] = COLOR
        return kwargs

    def plot(self, index: int = 0, *args: Any, **kwargs: Any) -> None:
        """Binding to simply plot on specific axis."""
        while len(self.plots) < index + 1:
            self.plots.append([])
        while len(self.legend_titles) < index + 1:
            self.legend_titles.append("")
        kwargs = self.plot_kwargs_with_default(kwargs)
        self.plots[index].append(self.figure.axes[index].plot(*args, **kwargs))
        self.update()

    def update(self) -> None:
        """Update the figure."""
        for axis, plots, title in zip(self.figure.axes, self.plots, self.legend_titles):
            if plots:
                axis.legend(
                    title=title,
                    loc="upper right",
                    fontsize="xx-small",
                    title_fontsize="xx-small",
                    fancybox=False,
                )
        pyplot.tight_layout()

    def save(self, name: str, dpi: int = 100) -> None:
        """To save the figure."""
        self.figure.savefig(name, dpi=dpi)

    def show(self, block: bool = True) -> None:
        """Show the graph."""
        pyplot.show(block=block)
