######################################################################
#             / ____|  ____|  __ \| |  | |  ____|  ____|             #
#            | |    | |__  | |__) | |__| | |__  | |__                #
#            | |    |  __| |  ___/|  __  |  __| |  __|               #
#            | |____| |____| |    | |  | | |____| |____              #
#             \_____|______|_|    |_|  |_|______|______|             #
######################################################################
#
# CEPHEE
# Copyright (C) 2024 Toulouse INP
#
# 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 :
# <http://www.gnu.org/licenses/>.
#
######################################################################

# global
from qgis.PyQt.QtWidgets import QDockWidget, QFileDialog
from qgis.PyQt.QtCore import pyqtSignal, QDir, pyqtSlot, Qt
from qgis.core import (QgsVectorLayer, QgsRasterLayer, QgsProject, QgsMapLayerProxyModel, QgsMessageLog, QgsPointXY,
                       Qgis, QgsTask, QgsApplication, QgsCoordinateReferenceSystem)
from .ui.Ui_DataWidget import Ui_DataWidget
from qgis.gui import QgsMapToolEmitPoint
hydro_lib = None

# local
from ..core.Data import *

# optional
try:
    import pysheds
    computeGlobal_avail = True
    hydro_lib='pysheds'
except ImportError:
    computeGlobal_avail = False

try:
    import pyflwdir
    computeGlobal_avail = True
    hydro_lib = 'pyflwdir'
except ImportError:
    computeGlobal_avail = False


class DataWidget(QDockWidget, Ui_DataWidget):
    closingWidget = pyqtSignal(name="closingWidget")

    def __init__(self, iface, BV, param, parent = None):
        super(DataWidget, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        self.BV = BV
        self.param = param
        self.task = None
        self.param.work_path = path.join(QDir.homePath(), 'CEPHEE_project')
        self.lineEdit_workPath.setText(self.param.work_path)
        self.pushButton_work_path.clicked.connect(self.select_work_path)
        self.pushButton_DEM_path.clicked.connect(self.select_DEM_path)
        self.comboBox_riverLayerSelection.currentTextChanged.connect(self.update_ui)
        self.checkBox_outletPointDetection.stateChanged.connect(self.update_ui)
        self.checkBox_computeGlobal.stateChanged.connect(self.update_ui)
        self.update_ui()
        self.comboBox_DEMlayerSelection.setFilters(QgsMapLayerProxyModel.RasterLayer)
        self.comboBox_DEMlayerSelection.setAllowEmptyLayer(True, "no layer")
        self.comboBox_DEMlayerSelection.setCurrentIndex(0)
        self.comboBox_boundary.setFilters(QgsMapLayerProxyModel.PolygonLayer)
        self.comboBox_boundary.setAllowEmptyLayer(True, "no layer")
        self.comboBox_boundary.setCurrentIndex(0)
        self.comboBox_riverLayerSelection.setFilters(QgsMapLayerProxyModel.VectorLayer)
        self.comboBox_riverLayerSelection.setAllowEmptyLayer(True, "no layer")
        self.comboBox_riverLayerSelection.setCurrentIndex(0)
        self.comboBox_crossSectionLayerSelection.setFilters(QgsMapLayerProxyModel.VectorLayer)
        self.comboBox_crossSectionLayerSelection.setAllowEmptyLayer(True, "no layer")
        self.comboBox_crossSectionLayerSelection.setCurrentIndex(0)
        self.comboBox_bankLayerSelection.setAllowEmptyLayer(True, "no layer")
        self.comboBox_bankLayerSelection.setCurrentIndex(0)
        self.pushButton_process.clicked.connect(self.process)
        self.option_DEM()
        self.radioButton_pathToFolder.toggled.connect(self.option_DEM)
        self.radioButton_QGISlayer.toggled.connect(self.option_DEM)
        self.comboBox_DEMlayerSelection.layerChanged.connect(self.option_DEM)
        self.ASC_grid_projection.setCrs(QgsProject.instance().crs())

        # capture coordinates tool
        self.captureCoordinatesTool = CaptureCoordinatesTool(iface.mapCanvas(), self.lineEdit_outletX,self.lineEdit_outletY)
        self.pushButton_captureCoordinates.clicked.connect(self.start_capture)
        self.captureCoordinatesTool.capturePoint.connect(self.update_coordinates)

    def closeEvent(self, event):
        self.closingWidget.emit()
        event.accept()

    def option_DEM(self):
        self.comboBox_DEMlayerSelection.hide()
        if self.radioButton_pathToFolder.isChecked():
            self.pushButton_DEM_path.show()
            self.lineEdit_DEM_path.show()
            self.comboBox_extension.show()
            self.comboBox_boundary.setEnabled(True)
        else:
            self.pushButton_DEM_path.hide()
            self.lineEdit_DEM_path.hide()
            self.comboBox_extension.hide()
            self.comboBox_boundary.setDisabled(True)
            if self.radioButton_QGISlayer.isChecked():
                self.comboBox_DEMlayerSelection.show()
            elif self.radioButton_noDEM.isChecked():
                pass

    def select_work_path(self):
        work_path = QFileDialog.getExistingDirectory(parent = None, caption="Select working directory")
        if path:
            self.lineEdit_workPath.setText(work_path)

    def select_DEM_path(self):
        DEM_path = QFileDialog.getExistingDirectory(parent = None, caption="Select DEM directory")
        if path:
            self.lineEdit_DEM_path.setText(DEM_path)

    def update_ui(self):
        if self.comboBox_riverLayerSelection.currentText() == "no layer":
            self.checkBox_computeGlobal.setChecked(True)
        if self.checkBox_computeGlobal.isChecked():
            self.lineEdit_resolution.setEnabled(True)
            self.lineEdit_minAccumulativeArea.setEnabled(True)
            self.comboBox_hydrolibSelection.setEnabled(True)
        else:
            self.lineEdit_resolution.setDisabled(True)
            self.lineEdit_minAccumulativeArea.setDisabled(True)
            self.comboBox_hydrolibSelection.setDisabled(True)
        if not self.checkBox_outletPointDetection.isChecked():
            self.lineEdit_outletX.setEnabled(True)
            self.lineEdit_outletY.setEnabled(True)
        else:
            self.lineEdit_outletX.setDisabled(True)
            self.lineEdit_outletY.setDisabled(True)


    def ui_to_param(self):
        self.param['C']['work_path'] = self.lineEdit_workPath.text()
        self.param['C']['DEM_CRS_ID'] = self.ASC_grid_projection.crs().authid()
        # DEM options
        self.param['I']['boundary_filepath'] = None
        if self.radioButton_noDEM.isChecked():
            self.param['I']['DEM_path'] = None
            self.param['I']['DEM_file_extension'] = None
        elif self.radioButton_pathToFolder.isChecked():
            self.param['I']['DEM_path']  = self.lineEdit_DEM_path.text()
            self.param['I']['DEM_file_extension'] = self.comboBox_extension.currentText()
            if self.comboBox_boundary.currentText() == "no layer":
                self.param['I']['boundary_filepath'] = self.comboBox_boundary.currentLayer().dataProvider().dataSourceUri()
        elif self.radioButton_QGISlayer.isChecked():
            self.param['I']['DEM_path'] = self.comboBox_DEMlayerSelection.currentLayer().dataProvider().dataSourceUri()
            self.param['I']['DEM_file_extension'] = None
        # River network options
        if self.comboBox_riverLayerSelection.currentText() == "no layer":
            self.param['I']['network_filepath'] = None
            self.lineEdit_minAccumulativeArea.setEnabled(True)
        else:
            self.param['I']['network_filepath'] = self.comboBox_riverLayerSelection.currentLayer().dataProvider().dataSourceUri()
        if self.comboBox_crossSectionLayerSelection.currentText() == "no layer":
            self.param['I']['XS_filepath'] = None
        else:
            self.param['I']['XS_filepath'] = self.comboBox_crossSectionLayerSelection.currentLayer().dataProvider().dataSourceUri()

        if self.comboBox_bankLayerSelection.currentText() == "no layer":
            self.param['I']['riverbanks_filepath'] = None
        else:
            self.param['I']['riverbanks_filepath'] = self.comboBox_bankLayerSelection.currentLayer().dataProvider().dataSourceUri()

        if not self.checkBox_outletPointDetection.isChecked():
            self.param['I']['outlet_point'] = Point(float(self.lineEdit_outletX.text()), float(self.lineEdit_outletY.text()))
        else:
            self.param['I']['outlet_point'] = None

        # Options
        self.param['N']['minDistJunction'] =  float(self.lineEdit_minDistJunction.text())
        self.param['C']['computeGlobal'] = self.checkBox_computeGlobal.isChecked()
        if self.param['C']['computeGlobal']:
            self.param['C']['hydro_lib'] = self.comboBox_hydrolibSelection.currentText()
        else:
            self.param['C']['hydro_lib'] = None
        self.param['N']['classeMax'] = int(self.lineEdit_classMax.text())
        self.param['C']['resolution'] = float(self.lineEdit_resolution.text())
        self.param['N']['minAccumulativeArea']  = float(self.lineEdit_minAccumulativeArea.text())

    @pyqtSlot(QgsPointXY)
    def update_coordinates(self, pt):
        self.lineEdit_outletX.setText(str(int(pt.x())))
        self.lineEdit_outletY.setText(str(int(pt.y())))
        self.end_capture()

    def start_capture(self):
        self.captureCoordinatesTool.savedMapTool = self.iface.mapCanvas().mapTool()
        self.iface.mapCanvas().setMapTool(self.captureCoordinatesTool)

    def end_capture(self):
        self.iface.mapCanvas().setMapTool(self.captureCoordinatesTool.savedMapTool)
        self.captureCoordinatesTool.savedMapTool = None
        self.captureCoordinatesTool.deactivate()
        self.checkBox_outletPointDetection.setChecked(False)

    def process(self):
        self.ui_to_param()
        self.task = BackgroundProcess('process_DATA', self.BV, self.param)
        QgsApplication.taskManager().addTask(self.task)


class BackgroundProcess(QgsTask):

    def __init__(self, description, BV, param):
        super().__init__(description, QgsTask.CanCancel)
        self.BV = BV
        self.param = param

    def run(self):
        QgsMessageLog.logMessage(message=str(self.param['C']['DEM_CRS_ID']) ,
                                 tag="CEPHEE", level=Qgis.MessageLevel.Info)
        QgsMessageLog.logMessage(message="Starting data process", tag="CEPHEE", level=Qgis.MessageLevel.Info)
        self.setProgress(0)
        run_data_process(self.BV, self.param)

        QgsMessageLog.logMessage(message="Saving shapefile and load new layers",
                                 tag="CEPHEE", level=Qgis.MessageLevel.Info)
        # load global DEM layer
        dem_layer = QgsRasterLayer(self.BV.global_DEM_path, "global_DEM")
        QgsProject.instance().addMapLayer(dem_layer)
        # save the ordered network and load in QGIS
        network_filepath = os.path.join(self.param.work_path, 'ordered_network.shp')
        self.BV.ordered_network.to_file(network_filepath, driver="ESRI Shapefile")
        ordered_hydro_layer = QgsVectorLayer(network_filepath, "ordered network")
        ordered_hydro_layer.setCrs(QgsCoordinateReferenceSystem(self.BV.crs.to_string()))
        QgsProject.instance().addMapLayer(ordered_hydro_layer)

        # save junction if present and load in QGIS
        if not self.BV.junction.empty:
            self.BV.junction.to_file(path.join(self.param.work_path, 'junction.shp'))
            junction_layer = QgsVectorLayer(path.join(self.param.work_path, 'junction.shp'), 'junction')
            QgsProject.instance().addMapLayer(junction_layer)

        self.setProgress(100)

        return True

    def finished(self, result):
        if result:
            QgsMessageLog.logMessage(message="Process OK", tag="CEPHEE", level=Qgis.MessageLevel.Info)
        else:
            QgsMessageLog.logMessage(message="Process Error", tag="CEPHEE", level=Qgis.MessageLevel.Critical)


class CaptureCoordinatesTool(QgsMapToolEmitPoint):
    """Class to interact with the map canvas to capture the coordinate
    when the mouse button is pressed."""
    capturePoint = pyqtSignal(QgsPointXY)
    captureFinished = pyqtSignal()

    def __init__(self, canvas, X, Y):
        QgsMapToolEmitPoint.__init__(self, canvas)
        self.canvas = canvas
        self.savedMapTool = None

    def activate(self):
        self.canvas.setCursor(Qt.CrossCursor)

    def canvasReleaseEvent(self, event):
        pt = self.toMapCoordinates(event.originalPixelPoint())
        self.capturePoint.emit(pt)
