# Window.py -- Pamhyr
# Copyright (C) 2023-2026  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 os
import logging

from tools import trace, timer, logger_exception

from View.Tools.PamhyrWindow import PamhyrWindow

from PyQt5 import QtWidgets
from PyQt5.QtGui import (
    QKeySequence,
)

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

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

from Modules import Modules

from View.InitialConditions.UndoCommand import (
    SetCommand, AddCommand, DelCommand,
    SortCommand, MoveCommand, InsertCommand,
    DuplicateCommand,
)

from View.InitialConditions.Table import (
    InitialConditionTableModel, ComboBoxDelegate,
)

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

from View.InitialConditions.PlotDRK import PlotDRK
from View.InitialConditions.PlotDischarge import PlotDischarge
from View.InitialConditions.translate import ICTranslate
from View.InitialConditions.DialogDepth import DepthDialog
from View.InitialConditions.DialogHeight import HeightDialog
from View.InitialConditions.DialogDischarge import DischargeDialog
from View.WaitingDialog import WaitingDialog

from Solver.Mage import Mage8

_translate = QCoreApplication.translate

logger = logging.getLogger()


class InitialConditionsWindow(PamhyrWindow):
    _pamhyr_ui = "InitialConditions"
    _pamhyr_name = "Initial condition"

    def __init__(self, study=None, config=None, reach=None, parent=None):
        trad = ICTranslate()

        if reach is not None:
            self._reach = reach
        else:
            self._reach = study.river.current_reach()

        name = (
            trad[self._pamhyr_name] +
            " - " + study.name +
            " - " + self._reach.name
        )

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

        # Add reach to hash computation data
        self._hash_data.append(self._reach)

        self._ics = study.river.initial_conditions.get(self._reach)

        self.setup_table()
        self.setup_plot()
        self.setup_connections()
        self.setub_dialogs()

    def setup_table(self):
        table = self.find(QTableView, f"tableView")
        self._delegate_rk = ComboBoxDelegate(
            reach=self._reach,
            parent=self
        )

        if self._study.is_editable():
            editable_headers = ["rk", "discharge", "elevation", "height"]
        else:
            editable_headers = []

        self._table = InitialConditionTableModel(
            reach=self._reach,
            table_view=table,
            table_headers=self._trad.get_dict("table_headers"),
            editable_headers=editable_headers,
            delegates={"rk": self._delegate_rk},
            data=self._study,
            undo=self._undo_stack,
            trad=self._trad
        )

        table.setModel(self._table)
        table.setSelectionBehavior(QAbstractItemView.SelectRows)
        table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        table.setAlternatingRowColors(True)

    def setup_plot(self):
        self.canvas_1 = MplCanvas(width=5, height=4, dpi=100)
        self.canvas_1.setObjectName("canvas_1")
        self.toolbar_1 = PamhyrPlotToolbar(
            self.canvas_1, self
        )
        self.plot_layout_1 = self.find(QVBoxLayout, "verticalLayout_1")
        self.plot_layout_1.addWidget(self.toolbar_1)
        self.plot_layout_1.addWidget(self.canvas_1)

        self.plot_1 = PlotDRK(
            canvas=self.canvas_1,
            data=self._ics,
            trad=self._trad,
            toolbar=self.toolbar_1,
            parent=self
        )
        self.plot_1.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_2 = PlotDischarge(
            canvas=self.canvas_2,
            data=self._ics,
            trad=self._trad,
            toolbar=self.toolbar_2,
            parent=self
        )
        self.plot_2.draw()

    def setup_connections(self):
        if self._study.is_editable():
            self.find(QAction, "action_add").triggered.connect(self.add)
            self.find(QAction, "action_del").triggered.connect(self.delete)
            self.find(QAction, "action_sort").triggered.connect(self.sort)
            self.find(QAction, "action_import").triggered\
                                               .connect(self.import_from_file)

            self.find(QPushButton, "pushButton_generate_1").clicked.connect(
                self.generate_growing_constant_depth
            )
            self.find(QPushButton, "pushButton_generate_2").clicked.connect(
                self.generate_discharge
            )
            self.find(QPushButton, "pushButton_generate_3").clicked.connect(
                self.generate_height
            )

        self._table.dataChanged.connect(self._update_plot)

    def setub_dialogs(self):
        self.height_values = [0.0, 0.0, 0.0]
        self.height_option = True
        self.discharge_value = 0.0
        self.discharge_option = True
        self.depth_value = 0.0
        self.depth_option = True

    def index_selected_row(self):
        table = self.find(QTableView, f"tableView")
        rows = table.selectionModel()\
                    .selectedRows()

        if len(rows) == 0:
            return 0

        return rows[0].row()

    def update(self):
        self._update(propagate=False)

    def _update(self, propagate=True):
        self._update_plot()

        if propagate:
            self._propagate_update(key=Modules.INITIAL_CONDITION)

    def _update_plot(self):
        self.plot_1.draw()
        self.plot_2.draw()
        self.plot_1.idle()
        self.plot_2.idle()

    def _propagated_update(self, key=Modules(0)):
        if Modules.GEOMETRY not in key:
            return

        self.update()

    def index_selected_rows(self):
        table = self.find(QTableView, f"tableView")
        return list(
            # Delete duplicate
            set(
                map(
                    lambda i: i.row(),
                    table.selectedIndexes()
                )
            )
        )

    def add(self):
        rows = self.index_selected_rows()
        if len(self._ics) == 0 or len(rows) == 0:
            self._table.add(0)
        else:
            self._table.add(rows[0])

        self._update()

    def delete(self):
        rows = self.index_selected_rows()
        if len(rows) == 0:
            return

        self._table.delete(rows)
        self._update()

    def sort(self):
        self._table.sort(False)
        self._update()

    def import_from_file(self):
        options = QFileDialog.Options()
        settings = QSettings(QSettings.IniFormat,
                             QSettings.UserScope, 'MyOrg', )
        options |= QFileDialog.DontUseNativeDialog

        file_types = [
            self._trad["file_bin"],
            self._trad["file_ini"],
            self._trad["file_all"],
        ]
        file_name, _ = QtWidgets.QFileDialog.getOpenFileName(
            self,
            self._trad["open_file"],
            "",
            ";; ".join(file_types),
            options=options
        )

        if file_name != "":
            size = os.stat(file_name).st_size
            # self._table.import_geometry(0, file_name)
            if file_name[-4:] == ".BIN":
                self._import_from_bin_file(file_name)
            elif file_name[-4:].upper() == ".INI":
                self._import_from_ini_file(file_name)

    def _import_from_bin_file(self, file_name):
        solver = Mage8("dummy")
        name = os.path.basename(file_name)\
                      .replace(".BIN", "")

        def reading():
            try:
                self._tmp_results = solver.results(
                    self._study,
                    os.path.dirname(file_name),
                    name=name
                )
            except Exception as e:
                logger.error(f"Failed to open results")
                logger_exception(e)

        dlg = WaitingDialog(
            payload_fn=reading,
            title="waiting_result",
            parent=self
        )
        dlg.exec_()
        results = self._tmp_results
        if results.is_valid:
            self._import_from_results(results)

    def _import_from_results(self, results):
        logger.debug(f"import from results: {results}")
        self._table.import_from_results(results)
        self._update()

    def _import_from_ini_file(self, file_name):
        logger.debug(f"import from INI file: {file_name}")
        self._table.read_from_ini(file_name)
        self._update()

    def move_up(self):
        row = self.index_selected_row()
        self._table.move_up(row)
        self._update()

    def move_down(self):
        row = self.index_selected_row()
        self._table.move_down(row)
        self._update()

    def _copy(self):
        rows = list(
            map(
                lambda row: row.row(),
                self.tableView.selectionModel().selectedRows()
            )
        )

        table = list(
            map(
                lambda eic: list(
                    map(
                        lambda k: eic[1][k],
                        ["rk", "discharge", "elevation"]
                    )
                ),
                filter(
                    lambda eic: eic[0] in rows,
                    enumerate(self._ics.lst())
                )
            )
        )

        self.copyTableIntoClipboard(table)

    def _paste(self):
        header, data = self.parseClipboardTable()

        if len(data) + len(header) == 0:
            return

        logger.debug(
            "IC: Paste: " +
            f"header = {header}, " +
            f"data = {data}"
        )

        try:
            row = self.index_selected_row()
            # self._table.paste(row, header, data)
            self._table.paste(row, [], data)
        except Exception as e:
            logger_exception(e)

        self._update()

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

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

    def generate_growing_constant_depth(self):
        if self._reach.reach.number_profiles > 0:
            dlg = DepthDialog(self.depth_value,
                              self.depth_option,
                              trad=self._trad,
                              parent=self)
            if dlg.exec():
                self.depth_value = dlg.value
                self.depth_option = dlg.option
                self._table.generate("growing",
                                     self.depth_value,
                                     self.depth_option)
                self._update()

    def generate_discharge(self):
        if self._reach.reach.number_profiles > 1:
            dlg = DischargeDialog(self.discharge_value,
                                  self.discharge_option,
                                  trad=self._trad,
                                  parent=self)
            if dlg.exec():
                self.discharge_value = dlg.value
                self.discharge_option = dlg.option
                self._table.generate("discharge",
                                     self.discharge_value,
                                     self.discharge_option)
                self._update()

    def generate_height(self):
        if self._reach.reach.number_profiles > 0:
            dlg = HeightDialog(self.height_values,
                               self.height_option,
                               trad=self._trad,
                               parent=self)
            if dlg.exec():
                self.height_values = dlg.values
                self.height_option = dlg.option
                self._table.generate("height",
                                     self.height_values,
                                     self.height_option)
                self._update()
