# Window.py -- Pamhyr
# Copyright (C) 2023  INRAE
#
# 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/>.

# -*- coding: utf-8 -*-

import logging
from datetime import datetime

from tools import trace, timer

from View.Tools.PamhyrWindow import PamhyrWindow

from PyQt5.QtGui import (
    QKeySequence,
)

from PyQt5.QtCore import (
    Qt, QVariant, QAbstractTableModel,
    QCoreApplication, QModelIndex, pyqtSlot,
    QItemSelectionModel,
)

from PyQt5.QtWidgets import (
    QDialogButtonBox, QPushButton, QLineEdit,
    QFileDialog, QTableView, QAbstractItemView,
    QUndoStack, QShortcut, QAction, QItemDelegate,
    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
    QSlider, QLabel, QWidget, QGridLayout,
)

from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar

from View.Results.PlotXY import PlotXY
from View.Results.PlotAC import PlotAC
from View.Results.PlotKPC import PlotKPC
from View.Results.PlotH import PlotH
from View.Results.PlotSedReach import PlotSedReach
from View.Results.PlotSedProfile import PlotSedProfile

from View.Results.CustomPlot.Plot import CustomPlot
from View.Results.CustomPlot.CustomPlotValuesSelectionDialog import (
    CustomPlotValuesSelectionDialog,
)

from View.Results.Table import TableModel
from View.Results.translate import ResultsTranslate
from View.Stricklers.Window import StricklersWindow

_translate = QCoreApplication.translate

logger = logging.getLogger()


class ResultsWindow(PamhyrWindow):
    _pamhyr_ui = "Results"
    _pamhyr_name = "Results"

    def __init__(self, study=None, config=None,
                 solver=None, results=None,
                 parent=None):
        self._solver = solver
        self._results = results

        self._timestamps = sorted(list(self._results.get("timestamps")))

        name = (
            self._pamhyr_name + " - "
            + study.name + " - "
            + self._solver.name
        )

        super(ResultsWindow, self).__init__(
            title=name,
            study=study,
            config=config,
            trad=ResultsTranslate(),
            parent=parent
        )

        self._hash_data.append(self._solver)
        self._hash_data.append(self._results)

        self._additional_plot = {}

        self.setup_table()
        self.setup_plot()
        self.setup_slider()
        self.setup_statusbar()
        self.setup_connections()

    def setup_table(self):
        self._table = {}
        for t in ["reach", "profile"]:
            table = self.find(QTableView, f"tableView_{t}")
            self._table[t] = TableModel(
                table_view=table,
                table_headers=self._trad.get_dict(f"table_headers_{t}"),
                data=self._results,
                undo=self._undo_stack,
                opt_data=t
            )

    def setup_slider(self):
        self._slider_profile = self.find(QSlider, f"verticalSlider_profile")
        default_reach = self._results.river.reach(0)
        self._slider_profile.setMaximum(len(default_reach.profiles) - 1)
        self._slider_profile.setValue(0)

        self._slider_time = self.find(QSlider, f"horizontalSlider_time")
        self._slider_time.setMaximum(len(self._timestamps) - 1)
        self._slider_time.setValue(len(self._timestamps) - 1)

    def setup_plot(self):
        self.canvas = MplCanvas(width=5, height=4, dpi=100)
        self.canvas.setObjectName("canvas")
        self.toolbar = PamhyrPlotToolbar(
            self.canvas, self
        )
        self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
        self.plot_layout.addWidget(self.toolbar)
        self.plot_layout.addWidget(self.canvas)

        self.plot_xy = PlotXY(
            canvas=self.canvas,
            results=self._results,
            reach_id=0,
            profile_id=0,
            toolbar=self.toolbar,
            display_current=False
        )
        self.plot_xy.draw()

        self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
        self.canvas_2.setObjectName("canvas_2")
        self.toolbar_2 = PamhyrPlotToolbar(
            self.canvas_2, self
        )
        self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
        self.plot_layout_2.addWidget(self.toolbar_2)
        self.plot_layout_2.addWidget(self.canvas_2)

        self.plot_kpc = PlotKPC(
            canvas=self.canvas_2,
            results=self._results,
            reach_id=0,
            profile_id=0,
            toolbar=self.toolbar_2
        )
        self.plot_kpc.draw()

        self.canvas_3 = MplCanvas(width=5, height=4, dpi=100)
        self.canvas_3.setObjectName("canvas_3")
        self.toolbar_3 = PamhyrPlotToolbar(
            self.canvas_3, self
        )
        self.plot_layout_3 = self.find(QVBoxLayout, "verticalLayout_3")
        self.plot_layout_3.addWidget(self.toolbar_3)
        self.plot_layout_3.addWidget(self.canvas_3)

        self.plot_ac = PlotAC(
            canvas=self.canvas_3,
            results=self._results,
            reach_id=0,
            profile_id=0,
            toolbar=self.toolbar_3
        )
        self.plot_ac.draw()

        self.canvas_4 = MplCanvas(width=5, height=4, dpi=100)
        self.canvas_4.setObjectName("canvas_4")
        self.toolbar_4 = PamhyrPlotToolbar(
            self.canvas_4, self
        )
        self.plot_layout_4 = self.find(
            QVBoxLayout, "verticalLayout_hydrograph")
        self.plot_layout_4.addWidget(self.toolbar_4)
        self.plot_layout_4.addWidget(self.canvas_4)

        self.plot_h = PlotH(
            canvas=self.canvas_4,
            results=self._results,
            reach_id=0,
            profile_id=0,
            toolbar=self.toolbar_4
        )
        self.plot_h.draw()

        self.canvas_5 = MplCanvas(width=5, height=4, dpi=100)
        self.canvas_5.setObjectName("canvas_5")
        self.toolbar_5 = PamhyrPlotToolbar(
            self.canvas_5, self
        )
        self.plot_layout_5 = self.find(QVBoxLayout, "verticalLayout_sed_reach")
        self.plot_layout_5.addWidget(self.toolbar_5)
        self.plot_layout_5.addWidget(self.canvas_5)

        if self._study.river.has_sediment():
            self.plot_sed_reach = PlotSedReach(
                canvas=self.canvas_5,
                results=self._results,
                reach_id=0,
                profile_id=0,
                toolbar=self.toolbar_5
            )
            self.plot_sed_reach.draw()

        self.canvas_6 = MplCanvas(width=5, height=4, dpi=100)
        self.canvas_6.setObjectName("canvas_6")
        self.toolbar_6 = PamhyrPlotToolbar(
            self.canvas_6, self
        )
        self.plot_layout_6 = self.find(
            QVBoxLayout, "verticalLayout_sed_profile")
        self.plot_layout_6.addWidget(self.toolbar_6)
        self.plot_layout_6.addWidget(self.canvas_6)

        if self._study.river.has_sediment():
            self.plot_sed_profile = PlotSedProfile(
                canvas=self.canvas_6,
                results=self._results,
                reach_id=0,
                profile_id=0,
                toolbar=self.toolbar_6
            )
            self.plot_sed_profile.draw()

    def _compute_status_label(self):
        # Timestamp
        ts = self._timestamps[self._slider_time.value()]

        t0 = datetime.fromtimestamp(0)
        fts = str(
            datetime.fromtimestamp(ts) - t0
        )
        fts.replace("days", _translate("Results", "days"))\
           .replace("day", _translate("Results", "day"))

        # Reach
        table = self.find(QTableView, f"tableView_reach")
        indexes = table.selectedIndexes()
        if len(indexes) == 0:
            reach = self._study.river.edges()[0]
        else:
            reach = self._study.river.edges()[indexes[0].row()]

        # Profile
        table = self.find(QTableView, f"tableView_profile")
        indexes = table.selectedIndexes()
        if len(indexes) == 0:
            profile = reach.reach.profile(0)
        else:
            profile = reach.reach.profile(indexes[0].row())

        pname = profile.name if profile.name != "" else profile.kp

        return (f"Reach: {reach.name} | " +
                f"Profile: {pname} | " +
                f"Timestamp : {fts} ({ts} sec)")

    def setup_statusbar(self):
        txt = self._compute_status_label()
        self._status_label = QLabel(txt)
        self.statusbar.addPermanentWidget(self._status_label)

    def update_statusbar(self):
        txt = self._compute_status_label()
        self._status_label.setText(txt)

    def setup_connections(self):
        # Action
        actions = {
            "action_reload": self._reload,
            "action_add": self._add_custom_plot
        }

        for action in actions:
            self.find(QAction, action).triggered.connect(
                actions[action]
            )

        # Table and Plot
        fun = {
            "reach": self._set_current_reach,
            "profile": self._set_current_profile,
        }

        for t in ["reach", "profile"]:
            table = self.find(QTableView, f"tableView_{t}")

            table.selectionModel()\
                 .selectionChanged\
                 .connect(fun[t])

            self._table[t].dataChanged.connect(fun[t])

        self._slider_profile.valueChanged.connect(
            self._set_current_profile_slider)
        self._slider_time.valueChanged.connect(self._set_current_timestamp)

    def update_table_selection_reach(self, ind):
        table = self.find(QTableView, f"tableView_reach")
        selectionModel = table.selectionModel()
        index = table.model().index(ind, 0)

        selectionModel.select(
            index,
            QItemSelectionModel.Rows |
            QItemSelectionModel.ClearAndSelect |
            QItemSelectionModel.Select
        )
        table.scrollTo(index)

    def update_table_selection_profile(self, ind):
        table = self.find(QTableView, f"tableView_profile")
        selectionModel = table.selectionModel()
        index = table.model().index(ind, 0)

        selectionModel.select(
            index,
            QItemSelectionModel.Rows |
            QItemSelectionModel.ClearAndSelect |
            QItemSelectionModel.Select
        )
        table.scrollTo(index)

    def update(self, reach_id=None, profile_id=None, timestamp=None):
        if reach_id is not None:
            self.plot_xy.set_reach(reach_id)
            self.plot_ac.set_reach(reach_id)
            self.plot_kpc.set_reach(reach_id)
            self.plot_h.set_reach(reach_id)

            if self._study.river.has_sediment():
                self.plot_sed_reach.set_reach(reach_id)
                self.plot_sed_profile.set_reach(reach_id)

            for plot in self._additional_plot:
                self._additional_plot[plot].set_reach(reach_id)

            self.update_table_selection_reach(reach_id)
            self.update_table_selection_profile(0)

        if profile_id is not None:
            self.plot_xy.set_profile(profile_id)
            self.plot_ac.set_profile(profile_id)
            self.plot_kpc.set_profile(profile_id)
            self.plot_h.set_profile(profile_id)

            if self._study.river.has_sediment():
                self.plot_sed_reach.set_profile(profile_id)
                self.plot_sed_profile.set_profile(profile_id)

            for plot in self._additional_plot:
                self._additional_plot[plot].set_profile(profile_id)

            self.update_table_selection_profile(profile_id)

        if timestamp is not None:
            self.plot_xy.set_timestamp(timestamp)
            self.plot_ac.set_timestamp(timestamp)
            self.plot_kpc.set_timestamp(timestamp)
            # self.plot_h.set_timestamp(timestamp)

            if self._study.river.has_sediment():
                self.plot_sed_reach.set_timestamp(timestamp)
                self.plot_sed_profile.set_timestamp(timestamp)

            for plot in self._additional_plot:
                self._additional_plot[plot].set_timestamp(timestamp)

        self.update_statusbar()

    def _get_current_reach(self):
        table = self.find(QTableView, f"tableView_reach")
        indexes = table.selectedIndexes()
        if len(indexes) == 0:
            return 0

        return indexes[0].row()

    def _get_current_profile(self):
        table = self.find(QTableView, f"tableView_profile")
        indexes = table.selectedIndexes()
        if len(indexes) == 0:
            return 0

        return indexes[0].row()

    def _get_current_timestamp(self):
        return self._timestamps[
            self._slider_time.value()
        ]

    def _set_current_reach(self):
        table = self.find(QTableView, f"tableView_reach")
        indexes = table.selectedIndexes()
        if len(indexes) == 0:
            return

        self.update(reach_id=indexes[0].row())

    def _set_current_profile(self):
        table = self.find(QTableView, f"tableView_profile")
        indexes = table.selectedIndexes()
        if len(indexes) == 0:
            return

        ind = indexes[0].row()
        self.update(profile_id=ind)
        self._slider_profile.setValue(ind)

    def _set_current_profile_slider(self):
        pid = self._slider_profile.value()
        self.update(profile_id=pid)

    def _set_current_timestamp(self):
        timestamp = self._timestamps[self._slider_time.value()]
        self.update(timestamp=timestamp)

    def _reload_plots(self):
        self.plot_xy.results = self._results
        self.plot_ac.results = self._results
        self.plot_kpc.results = self._results
        self.plot_h.results = self._results

        if self._study.river.has_sediment():
            self.plot_sed_reach.results = self._results
            self.plot_sed_profile.results = self._results

        self.plot_xy.draw()
        self.plot_ac.draw()
        self.plot_kpc.draw()
        self.plot_h.draw()

        if self._study.river.has_sediment():
            self.plot_sed_reach.draw()
            self.plot_sed_profile.draw()

    def _reload_slider(self):
        self._slider_time = self.find(QSlider, f"horizontalSlider_time")
        self._slider_time.setMaximum(len(self._timestamps) - 1)
        self._slider_time.setValue(len(self._timestamps) - 1)

    def _reload(self):
        logger.debug("Reload results...")
        self._results = self._results.reload()

        self._timestamps = sorted(list(self._results.get("timestamps")))

        self._reload_plots()
        self._reload_slider()

    def _add_custom_plot(self):
        dlg = CustomPlotValuesSelectionDialog(parent=self)
        if dlg.exec():
            x, y = dlg.value
            self.create_new_tab_custom_plot(x, y)

    def create_new_tab_custom_plot(self, x: str, y: list):
        name = f"{x}: {','.join(y)}"
        wname = f"tab_custom_{x}_{y}"

        tab_widget = self.find(QTabWidget, f"tabWidget")

        # This plot already exists
        if name in self._additional_plot:
            tab_widget.setCurrentWidget(
                tab_widget.findChild(QWidget, wname)
            )
            return

        widget = QWidget()
        grid = QGridLayout()

        widget.setObjectName(wname)

        canvas = MplCanvas(width=5, height=4, dpi=100)
        canvas.setObjectName(f"canvas_{x}_{y}")
        toolbar = PamhyrPlotToolbar(
            canvas, self
        )

        plot = CustomPlot(
            x, y,
            self._get_current_reach(),
            self._get_current_profile(),
            self._get_current_timestamp(),
            data=self._results,
            canvas=canvas,
            toolbar=toolbar,
            parent=self,
        )
        plot.draw()

        # Add plot to additional plot
        self._additional_plot[name] = plot

        grid.addWidget(toolbar, 0, 0)
        grid.addWidget(canvas, 1, 0)
        widget.setLayout(grid)
        tab_widget.addTab(widget, name)

    def _copy(self):
        logger.info("TODO: copy")

    def _paste(self):
        logger.info("TODO: paste")

    def _undo(self):
        self._table.undo()

    def _redo(self):
        self._table.redo()
