'''
/***************************************************************************
Name	      :  reclassify_grids_controller.py
Description:  Reclassify Grids tool added to FEWSTools window
copyright  :  (C) 2019-2023 by FEWS
email      :  minxuansun@contractor.usgs.gov
Modified   :  04/10/2020 - MSun - Fix wrong output folder problem.
              04/21/2020 - MSun - Fix loading raster list problem
              07/14/2020 - cholen - Adjust reclassify error
              08/28/2020 - cholen - Notify on completion
              10/08/2020 - cholen - Fix os sep on browse
              10/22/2020 - cholen - Change wksp_setup name to match other files
              01/20/2022 - cholen - New gdal utilities, cleanup
              02/22/2022 - cholen - Add tiff support
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
'''
import os
import io
import re

from qgis.utils import iface
from qgis.core import QgsMessageLog, Qgis
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QDialog, QFileDialog, QTableWidgetItem, QMessageBox

from fews_tools.controllers.time_series_list_controller import TimeSeriesListController
from fews_tools.models.workspace_setup_model import WorkspaceSetupModel
from fews_tools.workers.reclassify_grids_worker import ReclassifyGridsWorker

from fews_tools import fews_tools_config as config
from fews_tools.utilities import geoclim_utilities as util

from fews_tools.forms.Ui_ReclassifyGrids import Ui_ReclassifyGrids
from fews_tools.forms.Ui_ReclassTableRowAdd import Ui_ReclassTableRowAdd
from fews_tools.forms.Ui_ReclassTableRowDelete import Ui_ReclassTableRowDelete


class ReclassifyGridsController(QDialog):
    '''
    Class to Reclassify Grids
    '''

    def __init__(self):
        RASTER_LABELS = (['ROW', 'FILENAME'])
        QDialog.__init__(self)
        self.ui = Ui_ReclassifyGrids()
        self.ui.setupUi(self)
        self.wrksp_setup = WorkspaceSetupModel()
        self.reclassify_list = []
        self.input_file_list = []
        self.output_file_list = []
        self.worker = None
        self.thread = None
        self.is_processing = False
        self.closed = False  # A flag to indicate if close_event is triggered
        self.close_event = None
        # make connections to methods
        self.ui.addFileButton.clicked.connect(self.add_file)
        self.ui.addValButton.clicked.connect(self.add_value_row)
        self.ui.browseInputListButton.clicked.connect(self.load_list)
        self.ui.removeFileButton.clicked.connect(self.remove_file)
        self.ui.removeValButton.clicked.connect(self.remove_value_row)
        self.ui.processButton.clicked.connect(self.start_reclassify_grids)
        self.ui.saveListFileButton.clicked.connect(self.save_file_list)
        self.ui.browseOutputButton.clicked.connect(self.browse_output)
        self.ui.timeSeriesButton.clicked.connect(self.time_series)
        # sets default output, user can change
        self.output_path = self.wrksp_setup.get_output_path()
        self.ui.outputLineEdit.setText(self.output_path)
        self.ui.outputPrefixLineEdit.setText('reclass.')
        # deal with read only controls
        self.ui.rasterLineEdit.setEnabled(False)
        self.ui.reclassTableWidget.setEnabled(False)
        self.ui.outputLineEdit.setEnabled(False)
        # header for value
        hdr_old_min = QTableWidgetItem("Old Values >")
        hdr_old_max = QTableWidgetItem("Old Values <=")
        hdr_new_val = QTableWidgetItem("New Value")
        self.ui.reclassTableWidget.setHorizontalHeaderItem(0, hdr_old_min)
        self.ui.reclassTableWidget.setHorizontalHeaderItem(1, hdr_old_max)
        self.ui.reclassTableWidget.setHorizontalHeaderItem(2, hdr_new_val)
        # header for input table
        self.ui.rstrTableWidget.setRowCount(0)
        self.ui.rstrTableWidget.setColumnCount(2)
        self.ui.rstrTableWidget.setHorizontalHeaderLabels(RASTER_LABELS)
        self.ui.rstrTableWidget.resizeRowsToContents()

        self.ui.progressBar.setValue(0)

    def add_file(self):
        '''
        Function to add filenames to file list widget.

        '''
        file_list = QFileDialog.getOpenFileNames(
            self,
            u'Select List File(s)',
            self.wrksp_setup.get_workspace(),
            # '(*.*)')
            config.RST_FILE_TYPES)
        if file_list[0]:
            for files in file_list[0]:
                temp_str = self.wrksp_setup.fix_os_sep_in_path(files)

                current_row_count = self.ui.rstrTableWidget.rowCount()
                self.ui.rstrTableWidget.insertRow(current_row_count)
                self.ui.rstrTableWidget.setItem(
                    current_row_count, 0,
                    QTableWidgetItem(
                        os.path.splitext(os.path.basename(temp_str))[0]))
                self.ui.rstrTableWidget.setItem(
                    current_row_count, 1, QTableWidgetItem(str(temp_str)))

    def add_value_row(self):
        '''
        Function to add value row to table.
        params(integer) - value - value widget to adjust.
        '''
        add_row_dlg = ReclassifyRow()
        add_row_dlg.exec_()
        row_list = add_row_dlg.get_row_info()
        err = self.compare_reclass_values(row_list)
        if err is False:
            for item in range(self.ui.reclassTableWidget.rowCount()):
                if self.ui.reclassTableWidget.item(item, 0) is None:
                    self.ui.reclassTableWidget.setItem(
                        item, 0, QTableWidgetItem(str(row_list[0])))
                    self.ui.reclassTableWidget.setItem(
                        item, 1, QTableWidgetItem(str(row_list[1])))
                    self.ui.reclassTableWidget.setItem(
                        item, 2, QTableWidgetItem(str(row_list[2])))
                    break

    def compare_reclass_values(self, row_list):
        '''
        Compare values to existing table rows.
        params(list) - row_list - Values to check against table.
        returns(boolean) - err - True if matches existing entry, else False
        '''
        err = False
        if row_list == [0, 0, 0]:
            err = True
        else:
            for item in range(self.ui.reclassTableWidget.rowCount()):
                if self.ui.reclassTableWidget.item(item, 0) is not None:
                    compare_row = [
                        int(self.ui.reclassTableWidget.item(item, 0).text()),
                        int(self.ui.reclassTableWidget.item(item, 1).text()),
                        int(self.ui.reclassTableWidget.item(item, 2).text())]
                    if row_list == compare_row:
                        QMessageBox.information(self,
                                                u"Error!!",
                                                u"Row already exists!",
                                                QMessageBox.Ok)
                        err = True
                        break
        return err

    def get_reclassify_list(self):
        '''
        Get the reclassify list from the table items
        '''
        self.reclassify_list = []
        for item in range(self.ui.reclassTableWidget.rowCount()):
            if self.ui.reclassTableWidget.item(item, 0) is not None:
                self.reclassify_list.append(
                    [int(self.ui.reclassTableWidget.item(item, 0).text()),
                     int(self.ui.reclassTableWidget.item(item, 1).text()),
                     int(self.ui.reclassTableWidget.item(item, 2).text())])

    def load_list(self):
        '''
        Function to load file list of rasters into table widget.
        '''
        list_path = self.wrksp_setup.get_workspace()
        file_name = QFileDialog.getOpenFileName(
            self,
            'Select an Image List File',
            list_path,
            'List Files (*.lst);;'
            'Comma Seperate Values (*.csv);;'
            'All Files (*.*)')
        if file_name[0]:
            self.ui.rasterLineEdit.setText(
                self.wrksp_setup.fix_os_sep_in_path(file_name[0]))

            with io.open(file_name[0], 'r', newline='\n') as src_file:
                for row in src_file:
                    row = row.rstrip()
                    row_count = self.ui.rstrTableWidget.rowCount()
                    col_1 = os.path.splitext(os.path.basename(row))[0]
                    col_2 = row
                    self.ui.rstrTableWidget.insertRow(row_count)
                    self.ui.rstrTableWidget.setItem(row_count, 0,
                                                     QTableWidgetItem(col_1))
                    self.ui.rstrTableWidget.setItem(row_count, 1,
                                                     QTableWidgetItem(col_2))

    def reclassify_error(self, err, ex_str):
        '''
        Function to log any errors from the object and thread.
        Args:  err - not used
               ex_str string: Exception info
        '''
        self.is_processing = False
        QgsMessageLog.logMessage(ex_str, level=Qgis.Critical)
        QMessageBox.critical(self, 'Error!', ex_str, QMessageBox.Ok)
        iface.mainWindow().statusBar().showMessage(ex_str)

    def reclassify_finished(self, return_value):
        '''
        Function to clean up the reclassify worker object and thread.
        '''
        self.is_processing = False
        self.thread.quit()
        self.thread.wait()
        if self.closed:
            self.thread_worker_cleanup()
            self.close_event.accept()
            QDialog.closeEvent(self, self.close_event)
            return
        if self.worker.killed:
            self.ui.progressBar.setValue(0)
            msg = (u"Reclassify grids aborted by user")
            iface.messageBar().pushCritical("Reclassify grids", msg)
            QgsMessageLog.logMessage(msg, level=Qgis.Critical)
            iface.mainWindow().statusBar().showMessage(msg, 3000)
        elif return_value is not None:
            util.remove_temp_files(self.output_path)
            self.ui.progressBar.setValue(100)
            msg = 'Reclassify grids Completed'
            QgsMessageLog.logMessage(msg, level=Qgis.Info)
            iface.mainWindow().statusBar().showMessage(msg, 3000)
        else:
            # notify of an error
            msg = (u'Error: Reclassify grids did not finish')
            iface.messageBar().pushCritical('Reclassify grids', msg)
            QgsMessageLog.logMessage(msg, level=Qgis.Critical)
            iface.mainWindow().statusBar().showMessage(msg, 3000)

        # Unlock UI
        self.ui.cancelButton.setEnabled(False)
        self.ui.processButton.setEnabled(True)
        self.ui.closeButton.setEnabled(True)
        self.ui.browseOutputButton.setEnabled(True)
        self.ui.rstrTableWidget.setEnabled(True)
        self.ui.reclassTableWidget.setEnabled(True)
        self.ui.addFileButton.setEnabled(True)
        self.ui.outputPrefixLineEdit.setEnabled(True)
        self.ui.timeSeriesButton.setEnabled(True)
        self.ui.browseInputListButton.setEnabled(True)
        self.ui.removeFileButton.setEnabled(True)
        self.ui.saveListFileButton.setEnabled(True)
        self.ui.addValButton.setEnabled(True)
        self.ui.removeValButton.setEnabled(True)
        self.thread_worker_cleanup()
        all_done = util.notify_process_completed('Reclassify grids')
        if all_done:
            self.ui.closeButton.click()

    def remove_file(self):
        '''
        Function to remove files from list widget.
        '''
        rows = self.ui.rstrTableWidget.selectionModel().selectedRows()
        indexes = []
        for row in rows:
            indexes.append(row.row())
        indexes = sorted(indexes, reverse=True)
        for rm_index in indexes:
            self.ui.rstrTableWidget.removeRow(rm_index)
        self.ui.progressBar.setValue(0)

    def remove_value_row(self):
        '''
        Function to delete rows from table.
        params(integer) - value - value widget to adjust
        '''
        del_row_dlg = ReclassifyRowDelete()
        del_row_dlg.exec_()
        del_index = del_row_dlg.get_delete_index()

        # update reclassify list
        self.get_reclassify_list()

        if del_index != 0:
            old_min = int(self.ui.reclassTableWidget.item((del_index - 1), 0).text())
            old_max = int(self.ui.reclassTableWidget.item((del_index - 1), 1).text())
            new_val = int(self.ui.reclassTableWidget.item((del_index - 1), 2).text())
            temp = [old_min, old_max, new_val]

            if temp in self.reclassify_list:
                self.reclassify_list.remove(temp)
            else:
                QMessageBox.information(self,
                                        u"Error!!",
                                        u"Row not found!",
                                        QMessageBox.Ok)
                return

            self.ui.reclassTableWidget.removeRow(del_index - 1)
            # replace with an empty row at end
            self.ui.reclassTableWidget.insertRow(11)

    def save_file_list(self):
        '''
        Function to save file list from list widget.
        '''
        list_filename = os.path.join(self.output_path, 'tempListFile.lst')
        f_name = QFileDialog.getSaveFileName(None,
                                             u'Save File',
                                             list_filename,
                                             '*.lst')
        if f_name[0]:
            file_save = open(f_name[0], "w")
            row_count = self.ui.rstrTableWidget.rowCount()
            for i in range(0, row_count):
                item = self.ui.rstrTableWidget.item(i, 1)
                file_name = item.text()
                file_save.writelines(file_name + '\n')
            file_save.close()

    def thread_worker_cleanup(self):
        """
        Cleanup thread if it exists.
        """
        if self.thread is not None:
            self.thread.deleteLater()

    def browse_output(self):
        '''
        Function to browse directory
        '''
        dir_name = QFileDialog.getExistingDirectory(None,
                                                    u'Select Output Directory',
                                                    self.output_path)
        if dir_name:
            self.ui.outputLineEdit.setText(
                self.wrksp_setup.fix_os_sep_in_path(dir_name))


    def start_reclassify_grids(self):
        '''
        Function to start grid reclassification
        '''
        self.ui.progressBar.setValue(0)
        self.output_path = self.ui.outputLineEdit.text()

        # Remove temp files before running to make sure output area is clean
        util.remove_temp_files(self.output_path)

        # get the list of filenames from widget, construct output names
        row_count = self.ui.rstrTableWidget.rowCount()
        self.input_file_list = []
        self.output_file_list = []
        for i in range(row_count):
            text = self.ui.rstrTableWidget.item(i, 1).text()
            self.input_file_list.append(text)
            f_basename = os.path.basename(str(text))
            f_output = os.path.join(self.output_path,
                                    (self.ui.outputPrefixLineEdit.text() + f_basename))
            self.output_file_list.append(f_output)

        self.get_reclassify_list()
        if not self.reclassify_list:
            QMessageBox.critical(self,
                                 u'Empty Table',
                                 u'Verify reclassify table entries!',
                                 QMessageBox.Ok)
            return
        # checks file prefix for errors
        prefix_name = self.ui.outputPrefixLineEdit.text()
        pattern = r'[^\._a-zA-Z0-9]'
        if len(prefix_name) > 15 or len(prefix_name) <= 0:
            QMessageBox.information(self, 'Error!!',
                                    'Prefix must be between 1 and 15 '
                                    'characters!',
                                    QMessageBox.Ok)
            return
        # checks if list name contains only alphanumeric letters
        if re.search(pattern, prefix_name):
            QMessageBox.information(self, 'Error!!',
                                    'Prefix name must contain only '
                                    'underscores, decimals, and/or '
                                    'alphanumeric characters!!',
                                    QMessageBox.Ok)
            return
        # sort the reclassify list in the first column
        self.reclassify_list = sorted(self.reclassify_list)
        # verify that the overlap is only first entry per row
        used_values = []
        for entry in self.reclassify_list:
            row_vals = [i for i in range(entry[0], entry[-1])]
            if len(used_values) == 0:
                used_values = row_vals
            else:
                for val in row_vals:
                    if val in used_values:
                        QMessageBox.information(self,
                                                u'Error!!- '
                                                'Duplicate Values',
                                                str(val) + ' is duplicated!!',
                                                QMessageBox.Ok)
                        return
                    used_values.append(val)

        # update outputPath and create if necessary
        if not os.path.exists(self.output_path):
            os.makedirs(self.output_path)

        self.worker = ReclassifyGridsWorker(self.input_file_list,
                                            self.output_file_list,
                                            self.reclassify_list)

        self.ui.cancelButton.clicked.connect(self.worker.kill)
        self.ui.cancelButton.setEnabled(True)
        self.ui.processButton.setEnabled(False)
        self.ui.closeButton.setEnabled(False)
        self.ui.browseOutputButton.setEnabled(False)
        self.ui.rstrTableWidget.setEnabled(False)
        self.ui.reclassTableWidget.setEnabled(False)
        self.ui.addFileButton.setEnabled(False)
        self.ui.outputPrefixLineEdit.setEnabled(False)
        self.ui.timeSeriesButton.setEnabled(False)
        self.ui.browseInputListButton.setEnabled(False)
        self.ui.removeFileButton.setEnabled(False)
        self.ui.saveListFileButton.setEnabled(False)
        self.ui.addValButton.setEnabled(False)
        self.ui.removeValButton.setEnabled(False)

        iface.mainWindow().statusBar().showMessage(u"Reclassify running...")

        # start the process in a new thread
        iface.messageBar().clearWidgets()
        self.thread = QtCore.QThread(self)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.thread.start()
        self.worker.finished.connect(self.reclassify_finished)
        self.worker.error.connect(self.reclassify_error)
        self.worker.progress.connect(self.ui.progressBar.setValue)
        self.is_processing = True

    def time_series(self):
        '''
       Function to add time series list to table
       '''
        self.ui.rstrTableWidget.setRowCount(0)
        t_seriesdlg = TimeSeriesListController()
        t_seriesdlg.exec_()
        selected_files_list = t_seriesdlg.available_files_list
        list_file_name = t_seriesdlg.get_file_information()
        self.ui.rasterLineEdit.setText(list_file_name)
        for file in selected_files_list:
            current_row_count = self.ui.rstrTableWidget.rowCount()
            self.ui.rstrTableWidget.insertRow(current_row_count)
            row = os.path.splitext(os.path.basename(file))[0]
            self.ui.rstrTableWidget.setItem(
                current_row_count, 0, QTableWidgetItem(str(row)))
            self.ui.rstrTableWidget.setItem(
                current_row_count, 1, QTableWidgetItem(str(file)))

    def closeEvent(self, a_0: QtGui.QCloseEvent) -> None:
        '''
        Close event used to handle a case where user closes the form
        while the process is running.
        '''
        self.closed = True
        if self.worker:
            self.worker.kill()
        self.close_event = a_0
        if self.is_processing:
            self.close_event.ignore()
        else:
            self.close_event.accept()

class ReclassifyRow(QDialog):
    '''
    Class for Reclassify Row
    '''

    def __init__(self):
        QDialog.__init__(self)
        self.ui = Ui_ReclassTableRowAdd()
        self.ui.setupUi(self)
        self.min_value = 0
        self.max_value = 0
        self.new_val = 0
        self.ui.oldValFromSpinBox.setValue(self.min_value)
        self.ui.oldValToSpinBox.setValue(self.max_value)
        self.ui.newValSpinBox.setValue(self.new_val)

        # make connections to methods
        self.ui.addPushButton.clicked.connect(self.create_row)
        # close is handled by QDialog

    def create_row(self):
        '''
        Function to create a row for table.
        '''
        if self.ui.oldValToSpinBox.value() < self.ui.oldValFromSpinBox.value():
            QMessageBox.critical(self,
                                 u"Invalid entries!!",
                                 u"Old value range is incorrect!",
                                 QMessageBox.Ok)
        else:
            self.min_value = self.ui.oldValFromSpinBox.value()
            self.max_value = self.ui.oldValToSpinBox.value()
            self.new_val = self.ui.newValSpinBox.value()

            self.close()
        # reset the form since the init method won't be called again
        self.ui.oldValFromSpinBox.setValue(0)
        self.ui.oldValToSpinBox.setValue(0)
        self.ui.newValSpinBox.setValue(0)

    def get_row_info(self):
        '''
        Gets info from table row
        '''
        row_list = [self.min_value, self.max_value, self.new_val]
        return row_list


class ReclassifyRowDelete(QDialog):
    '''
    Class for Reclassify Row delete
    '''

    def __init__(self):
        QDialog.__init__(self)
        self.ui = Ui_ReclassTableRowDelete()
        self.ui.setupUi(self)
        self.index = 0
        # make connections to method
        self.ui.deletePushButton.clicked.connect(self.delete_index)

    def delete_index(self):
        '''
        Function to delete index of row selected from table.
        '''
        self.index = self.ui.rowIndexSpinBox.value()
        self.close()
        # resets the form, init won't be called again
        self.ui.rowIndexSpinBox.setValue(0)

    def get_delete_index(self):
        '''
        Function to get index of rows to delete
        '''
        return self.index
