"""
/***************************************************************************
 SeilaplanPlugin
                                 A QGIS plugin
 Seillinien-Layoutplaner
                              -------------------
        begin                : 2013
        copyright            : (C) 2015 by ETH Zürich
        email                : seilaplanplugin@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

from math import floor

import numpy as np
from qgis.PyQt.QtCore import QCoreApplication, QSize, Qt, QT_VERSION_STR
from qgis.PyQt.QtWidgets import QSizePolicy

if QT_VERSION_STR.startswith("5"):
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
else:
    from matplotlib.backends.backend_qtagg import (
        FigureCanvasQTAgg as FigureCanvas,
    )

from matplotlib.collections import LineCollection
from matplotlib.figure import Figure

from SEILAPLAN.gui.map_marker import POLE_COLOR, PROFILE_COLOR, SECTION_COLOR
from SEILAPLAN.gui.plotting_tools import zoom_with_wheel
from SEILAPLAN.utils.qgis_utils import isQgisInDarkMode


class ProfilePlot(FigureCanvas):

    def __init__(self, parent=None, width=10, height=4, dpi=72):
        self.win = parent
        background_color = "#7a7a7a" if isQgisInDarkMode() else "#efefef"
        self.fig = Figure(figsize=(width, height), dpi=dpi, facecolor=background_color)
        self.axes = self.fig.add_subplot(111)
        self.axes.set_facecolor(background_color)

        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)

        self.linePoints = []
        self.line_exists = False
        self.profile = None
        self.labelScale = None
        # Reference to toolbar
        self.tbar = None

        # Mouse position
        self.x_data = None
        self.y_data = None
        # Cursor cross position
        self.xcursor = 0
        self.ycursor = 0
        # Cursor cross
        self.lx = None
        self.ly = None
        # Listener for canvas events
        self.evtMovePole = None
        self.evtPressPole = None
        self.evtMoveSection = None
        self.evtPressSection = None

        # Enable zoom with scroll wheel
        zoomFunc = zoom_with_wheel(self, self.axes, zoomScale=1.3)  # noqa: F841

        self.axes.set_aspect("equal", "datalim")
        self.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
        FigureCanvas.setSizePolicy(
            self, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
        )
        self.setMinimumSize(QSize(600, 400))
        FigureCanvas.updateGeometry(self)

        # Fit plot tightly into window
        self.fig.tight_layout(pad=0.2, w_pad=0.1, h_pad=0.1)

    def plotData(self, plotData):
        """
        :type plotData: tool.profile.Profile
        """
        self.axes.clear()
        self.__setupAxes()
        self.profile = plotData

        # Set plot extent
        xmin = np.min(self.profile.di_disp)
        ymin = np.min(self.profile.zi_disp)
        xmax = np.max(self.profile.di_disp)
        ymax = np.max(self.profile.zi_disp)
        self.labelScale = 1
        # Data point of profile
        self.x_data = self.profile.di
        self.y_data = self.profile.zi

        # Add plot data (whole profile)
        pltSegs = np.column_stack([self.profile.di_disp, self.profile.zi_disp])
        pltSegs = tuple(map(tuple, pltSegs))
        lineColl = LineCollection([pltSegs], linewidths=1.5, colors="#004f03")
        self.axes.add_collection(lineColl)
        # Add profile section between A and E
        pltSegsAE = np.column_stack([self.x_data, self.y_data])
        pltSegsAE = tuple(map(tuple, pltSegsAE))
        lineCollAE = LineCollection(
            [pltSegsAE],
            linewidths=2.5,
            picker=True,
            colors=PROFILE_COLOR,
            label="LBL",
        )
        self.axes.add_collection(lineCollAE)

        # Draw start and end
        self.axes.scatter(
            self.x_data[[0, -1]],
            self.y_data[[0, -1]],
            zorder=100,
            c=POLE_COLOR,
            s=80,
            marker="o",
        )
        # Label
        self.axes.text(
            self.x_data[0],
            self.y_data[0] + self.labelScale,
            "A",
            ha="center",
            va="bottom",
            fontsize=12,
        )
        self.axes.text(
            self.x_data[-1],
            self.y_data[-1] + self.labelScale,
            "E",
            ha="center",
            va="bottom",
            fontsize=12,
        )

        if self.profile.surveyPnts is not None:
            surveyCol = "#00400d" if isQgisInDarkMode() else "green"
            # Add markers for survey points
            for pointX, pointY, idx, notes in zip(
                self.profile.surveyPnts["d"],
                self.profile.surveyPnts["z"],
                self.profile.surveyPnts["nr"],
                self.profile.surveyPnts["notes"],
            ):
                self.axes.plot(
                    [pointX, pointX],
                    [pointY, pointY - 5 * self.labelScale],
                    color=surveyCol,
                    linewidth=1.5,
                )
                labelText = f"{idx}"
                rot = 0
                ha = "center"
                va = "top"
                if notes:
                    labelText = f"{idx}{':' if notes else ''} {notes if len(notes) <= 25 else notes[:22] + '...'}"
                    # For less placement issues, labels are rotated 45°
                    rot = 55
                    ha = "right"
                    va = "center"
                    # Label rotation is switched if profile line inclines
                    if self.y_data[0] < self.y_data[-1]:
                        rot = 305
                        ha = "left"
                self.axes.text(
                    pointX,
                    pointY - 6 * self.labelScale,
                    labelText,
                    ha=ha,
                    va=va,
                    fontsize=12,
                    color=surveyCol,
                    rotation=rot,
                    rotation_mode="anchor",
                )

        # Init cursor cross position
        self.xcursor = self.x_data[floor(len(self.x_data) / 2)]
        self.ycursor = self.y_data[floor(len(self.x_data) / 2)]
        self.lx = self.axes.axhline(lw=1.5, ls="dashed", y=self.ycursor)
        self.ly = self.axes.axvline(lw=1.5, ls="dashed", x=self.xcursor)
        self.ly.set_visible(False)
        self.lx.set_visible(False)

        self.axes.set_xlim(xmin - 2 * self.labelScale, xmax + 2 * self.labelScale)
        self.axes.set_ylim(ymin - 2 * self.labelScale, ymax + 2 * self.labelScale)

        self.draw()
        # Set new plot extent as home extent (for home button)
        self.tbar.update()
        self.tbar.push_current()

    def acitvateCrosshairPole(self):
        self.evtMovePole = self.mpl_connect("motion_notify_event", self.onMouseMoveP)
        self.evtPressPole = self.mpl_connect("button_press_event", self.onMousePressP)
        self.ly.set_color(POLE_COLOR)
        self.lx.set_color(POLE_COLOR)
        self.ly.set_visible(True)
        self.lx.set_visible(True)
        self.win.activateMapCursor(self.xcursor, POLE_COLOR)
        self.draw()

    def deactivateCrosshairPole(self):
        self.mpl_disconnect(self.evtMovePole)
        self.mpl_disconnect(self.evtPressPole)
        self.ly.set_visible(False)
        self.lx.set_visible(False)
        self.draw()

    def onMouseMoveP(self, event):
        if not event.inaxes:
            return
        x, _ = event.xdata, event.ydata
        indx = np.argmax(self.x_data >= x)
        if indx == 0:
            # Cursor outside of profile
            return
        xa = self.x_data[indx]
        ya = self.y_data[indx]
        # Update the line positions
        self.lx.set_ydata([ya])
        self.ly.set_xdata([xa])
        self.xcursor = xa
        self.ycursor = ya
        self.draw()
        # Update cursor on map
        self.win.updateMapCursor(xa, POLE_COLOR)

    def onMousePressP(self, event):
        if not event.inaxes:
            return
        self.deactivateCrosshairPole()
        self.win.deactivateMapCursor()
        posX = int(round(self.xcursor, 0))
        # z Value will be derived from x value
        self.win.addPole(posX, None)

    def createPoint(self, posX, posY):
        scat = self.axes.scatter(posX, posY, zorder=100, c=POLE_COLOR, s=80, marker="s")
        self.draw()
        return scat

    def deletePoint(self, point):
        point.remove()
        self.draw()

    def activateCrosshairSection(self):
        """Cursor cross for defining sections without supports."""
        self.evtMoveSection = self.mpl_connect("motion_notify_event", self.onMouseMoveS)
        self.evtPressSection = self.mpl_connect(
            "button_press_event", self.onMousePressS
        )
        self.ly.set_color(SECTION_COLOR)
        self.lx.set_color(SECTION_COLOR)
        self.win.activateMapCursor(1, SECTION_COLOR)
        self.ly.set_visible(True)
        self.lx.set_visible(True)
        self.draw()

    def deactivateCrosshairSection(self):
        self.mpl_disconnect(self.evtMoveSection)
        self.mpl_disconnect(self.evtPressSection)
        self.ly.set_visible(False)
        self.lx.set_visible(False)
        self.line_exists = False
        self.linePoints.clear()
        self.draw()

    def onMouseMoveS(self, event):
        if not event.inaxes:
            return
        x, _ = event.xdata, event.ydata
        indx = np.argmax(self.x_data >= x)
        if indx == 0:
            # Cursor outside of profile
            return
        xa = self.x_data[indx]
        ya = self.y_data[indx]
        # Update the line positions
        self.lx.set_ydata([ya])
        self.ly.set_xdata([xa])
        self.xcursor = xa
        self.ycursor = ya
        # Overdraw profile line
        self.win.updateMapCursor(xa, SECTION_COLOR)
        if self.line_exists:
            self.win.lineMoved(xa)
        self.draw()

    def onMousePressS(self, event):
        if not event.inaxes:
            return
        x, y = event.xdata, event.ydata
        indx = np.argmax(self.x_data >= x)
        x = self.x_data[indx]
        y = self.y_data[indx]

        if len(self.linePoints) == 0:
            # Initialize marker lines for sections without supports
            self.drawSection(x, y)
            self.line_exists = True
            self.win.activateMapLine(x)
        elif len(self.linePoints) == 1:
            # Set line
            self.drawSection(x, y)
            self.win.finishLine(x)
            self.deactivateCrosshairSection()
        self.draw()

    def drawSection(self, x, y):
        self.linePoints.append([x, y])
        self.axes.vlines(
            x,
            y - self.labelScale,
            y + self.labelScale,
            lw=2,
            color=SECTION_COLOR,
        )
        if len(self.linePoints) == 2:
            idxA = np.argmax(self.x_data >= self.linePoints[0][0])
            idxE = np.argmax(self.x_data > self.linePoints[1][0])
            self.axes.plot(
                self.x_data[idxA:idxE],
                self.y_data[idxA:idxE],
                linewidth=2,
                color=SECTION_COLOR,
            )
            self.linePoints.clear()

    def __setupAxes(self):
        self.axes.set_xlabel(self.tr("Horizontaldistanz [m]"), fontsize=11)
        self.axes.set_ylabel(self.tr("Hoehe [m.ue.M]"), fontsize=11)
        grid_color = "#636363" if isQgisInDarkMode() else "#c7c7c7"
        self.axes.grid(which="major", color=grid_color, lw=1)
        self.axes.grid(which="minor", color=grid_color, lw=1, linestyle=":")
        self.axes.ticklabel_format(style="plain", useOffset=False)
        self.axes.tick_params(
            axis="both",
            which="major",
            length=5,
            width=1,
            bottom=True,
            top=False,
            left=True,
            right=False,
        )
        self.axes.tick_params(axis="both", which="both", direction="in")
        self.axes.minorticks_on()

    def setToolbar(self, tbar):
        self.tbar = tbar

    def reset(self):
        self.axes.clear()

    def tr(self, message, **kwargs):
        """Get the translation for a string using Qt translation API.
        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString

        Parameters
        ----------
        **kwargs
        """
        return QCoreApplication.translate(type(self).__name__, message)
