"""
/***************************************************************************
Name            :  climate_data_download_controller.py
Description     :  Climate Data Download controller class for FEWSTools plugin,
                        updated from QGIS2
copyright       :  (C) 2019-2023 by FEWS
email           :  minxuansun@contractor.usgs.gov
Created         :  12/18/2019 - achristianson
Modified        :  02/10/2020 - msun - Close dialog when download complete
                   07/13/2020 - cholen - Update to use util to fill ds control
                   07/14/2020 - cholen - Adjust error
                   08/28/2020 - cholen - Notify on completion, widget fills
                                fix, enable/disable controls on stop/start
                   11/13/2020 - cholen - Fix bug in getting date strings
                                for download
                   12/28/2020 - cholen - Adjust for summed downloads
                   03/10/2021 - cholen - Fix for no new files
                   02/29/2022 - msun - Add tiff support, changes for consistency
                   07/13/2022 - cholen - Handle FEWS daily format
                   09/16/2022 - jhowton - Added download missing data alert
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 os.path
import glob
from datetime import date

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

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

from fews_tools.models.datasets_model import DatasetsModel
from fews_tools.forms.Ui_ClimDownload import Ui_ClimDownload
from fews_tools.workers.climate_data_download_worker import ClimateDataDownloadWorker


class ClimateDataDownloadController(QDialog):
    '''
    Class for downloading GeoCLIM data
    '''

    def __init__(self):
        QDialog.__init__(self)
        self.is_processing = False  # Flag to indicate if running a process
        self.ui = Ui_ClimDownload()
        self.ui.setupUi(self)
        self.datasets_info = DatasetsModel()
        self.ds_dic = {}  # Should contain dataset info
        self.int_dic = {}
        self.periods_per_month = 0
        self.periods_per_year = 0
        self.start_date = 0
        self.end_date = 0
        self.date_string_list = []  # List of date strings for download files
        self.ui.frmPentDekComboBox.setVisible(False)
        self.ui.toPentDekComboBox.setVisible(False)
        self.closed = False  # A flag to indicate if close_event is triggered
        self.close_event = None
        self.thread = None
        self.worker = None
        self.ui.datasetComboBox.currentIndexChanged.connect(
            self.dataset_selection_changed)
        self.ui.downloadCountSpinBox.setEnabled(False)
        self.ui.cancelButton.setEnabled(False)
        self.ui.processButton.clicked.connect(self.data_download_start)
        self.ui.progressBar.setVisible(True)
        self.ui.progressBar.setValue(0)
        util.fill_dataset_combo(self.ui.datasetComboBox)
        self.ui.frmPentDekComboBox.currentIndexChanged.connect(
            self.set_download_count)
        self.ui.toPentDekComboBox.currentIndexChanged.connect(
            self.set_download_count)
        self.ui.frmMonthComboBox.currentIndexChanged.connect(
            self.set_download_count)
        self.ui.toMonthComboBox.currentIndexChanged.connect(
            self.set_download_count)
        self.ui.frmYearComboBox.currentIndexChanged.connect(
            self.set_download_count)
        self.ui.toYearComboBox.currentIndexChanged.connect(
            self.set_download_count)

    def data_download_start(self):
        """
        Downloads data from database based on selections
        """
        self.ui.progressBar.setValue(0)
        if self.start_date > self.end_date:
            # Warning - start_date is larger than end_date
            QMessageBox.warning(
                self, 'Warning',
                'Top date should be smaller than bottom date. '
                'Re-select dates.', QMessageBox.Ok)
        elif '' in [self.ds_dic["REMOTEHOST"],
                    self.ds_dic["REMOTEDIRECTORY"],
                    self.ds_dic["USERNAME"],
                    self.ds_dic["PASSWORD"]]:
            QMessageBox.warning(
                self, 'Warning',
                'FTP settings are required to download data. '
                'Check dataset for missing FTP settings.', QMessageBox.Ok)
        else:
            # Gathers list of files in local workspace
            local_raster_list = \
                glob.glob(os.path.join(self.ds_dic['DATAFOLDER'],
                                       '*' + self.ds_dic['DATASUFFIX']))

            local_raster_basename_list = []
            for path in local_raster_list:
                no_extension_path = os.path.splitext(path)[0]
                file_basename = os.path.basename(no_extension_path)
                local_raster_basename_list.append(file_basename)
            # gathers list of files that will be downloaded
            final_data_file_list = []
            for date_string in self.date_string_list:
                file_name = (self.ds_dic['DATAPREFIX'] + str(date_string))
                final_data_file_list.append(file_name)

            # compare list to see if some files exist in local
            # get intersection of two file list
            exist_files = \
                list(set(local_raster_basename_list) &
                     set(final_data_file_list))
            if exist_files:
                # If files exist, ask user if the files are overwritten
                reply = \
                    QMessageBox.question(
                        self, 'Overwrite Data?',
                        'Some of the dates you want to download already have '
                        'PET/PPT data.\n Do you wish to overwrite the '
                        'existing data?\n Click Yes to overwrite, or\n No to '
                        'only download missing data.',
                        QMessageBox.Yes, QMessageBox.No)
                if reply == QMessageBox.No:
                    # remove existing files from download list
                    # if don't want to overwrite
                    final_data_file_list = \
                        list(set(final_data_file_list) - set(exist_files))
            if final_data_file_list:
                self.worker = ClimateDataDownloadWorker(
                    final_data_file_list, self.ds_dic)
                self.ui.cancelButton.clicked.connect(self.worker.kill)
                self.ui.cancelButton.setEnabled(True)
                self.ui.closeButton.setEnabled(False)
                self.ui.processButton.setEnabled(False)
                self.ui.datasetComboBox.setEnabled(False)
                self.ui.frmMonthComboBox.setEnabled(False)
                self.ui.toMonthComboBox.setEnabled(False)
                self.ui.frmYearComboBox.setEnabled(False)
                self.ui.toYearComboBox.setEnabled(False)
                self.ui.frmPentDekComboBox.setEnabled(False)
                self.ui.toPentDekComboBox.setEnabled(False)

                message_text = "Climate data downloading"
                iface.mainWindow().statusBar().showMessage(message_text)

                # start the data download in a new thread
                thread = QtCore.QThread(self)
                self.worker.moveToThread(thread)
                self.worker.finished.connect(self.data_download_finished)
                self.worker.error.connect(self.data_download_error)
                self.worker.missing_file.connect(
                    self.data_download_missing_file)
                self.worker.progress.connect(self.ui.progressBar.setValue)
                thread.started.connect(self.worker.run)
                self.is_processing = True
                thread.start()
                self.thread = thread
            else:
                QMessageBox.information(
                    self, 'Information',
                    'All selected data is already downloaded', QMessageBox.Ok)

    def data_download_finished(self, return_value):
        """
        Clean up the work and thread
        """
        self.is_processing = False
        if self.closed:
            self.worker.deleteLater()
            self.thread.quit()
            self.thread.wait()
            self.thread.deleteLater()
            self.close_event.accept()
            QDialog.closeEvent(self, self.close_event)
            return
        self.thread.quit()
        self.thread.wait()
        self.thread.deleteLater()
        message_text = self.worker.err_msg
        self.worker.deleteLater()
        if return_value:
            return_code, return_string = return_value
            message_text = \
                'Exit code: ' + str(return_code) + '; ' + return_string
        else:
            if not message_text:
                message_text = "Error: Climate Data Download failed"
            QgsMessageLog.logMessage(message_text, level=Qgis.Critical)
            iface.messageBar().pushMessage(message_text,
                                           level=Qgis.Critical)
            iface.mainWindow().statusBar().showMessage(message_text)
        self.ui.processButton.setEnabled(True)
        self.ui.cancelButton.setEnabled(False)
        self.ui.closeButton.setEnabled(True)
        self.ui.datasetComboBox.setEnabled(True)
        self.ui.frmMonthComboBox.setEnabled(True)
        self.ui.toMonthComboBox.setEnabled(True)
        self.ui.frmYearComboBox.setEnabled(True)
        self.ui.toYearComboBox.setEnabled(True)
        self.ui.frmPentDekComboBox.setEnabled(True)
        self.ui.toPentDekComboBox.setEnabled(True)
        all_done = util.notify_process_completed('Download Data')
        if all_done:
            self.ui.closeButton.click()

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

    def data_download_missing_file(self, missing_file, err):
        '''
        Log missing file message.
        Args:  missing_file(str) - File name
               err(int) - Response code
        '''
        if err == 404:
            iface.messageBar().pushMessage(
                missing_file + ":  File not found at source.",
                level=Qgis.Warning)
        else:
            iface.messageBar().pushMessage(
                missing_file + ": Download error, response: " + str(err),
                level=Qgis.Critical)

    def dataset_selection_changed(self):
        """
        Function to update database object and dependent gui widgets.
        """
        err = self.datasets_info.query_named_dataset(
            self.ui.datasetComboBox.currentText())
        if err:
            raise IOError('Database read failed')
        self.ds_dic = self.datasets_info.get_ds_dictionary()
        self.int_dic = util.get_interval_dic(self.ds_dic['PERIODICITY'])
        self.periods_per_year = len(self.int_dic)
        mid_point = util.get_midpoint(self.ds_dic)
        current_year = date.today().year
        year_list = [*range(1900, (current_year + 1))]
        year_list = [str(item) for item in year_list]
        util.fill_year_widget(self.ui.frmYearComboBox, False, year_list,
                              1, self.periods_per_year, mid_point)
        util.fill_year_widget(self.ui.toYearComboBox, False, year_list,
                              1, self.periods_per_year, mid_point)
        mon_list = [str(item).zfill(2) for item in [*range(1, 13)]]
        util.fill_misc_widget(self.ui.frmMonthComboBox, mon_list, False)
        util.fill_misc_widget(self.ui.toMonthComboBox, mon_list, False)
        util.fill_ds_type_widget(self.ui.parameterLineEdit, self.ds_dic)
        if self.ds_dic['PERIODICITY'] == config.PERIODICITY_LIST[1]:
            self.periods_per_month = 1
            self.ui.frmPentDekComboBox.setVisible(False)
            self.ui.toPentDekComboBox.setVisible(False)
            self.ui.lblFrmPer.setText('')
            self.ui.lblToPer.setText('')
            self.ui.lblPerCount.setText('months')
        elif self.ds_dic['PERIODICITY'] == config.PERIODICITY_LIST[0]:
            self.periods_per_month = 3
            self.ui.frmPentDekComboBox.setVisible(True)
            self.ui.toPentDekComboBox.setVisible(True)
            self.ui.lblFrmPer.setText('Dekad')
            self.ui.lblToPer.setText('Dekad')
            self.ui.lblPerCount.setText('dekads')
        elif self.ds_dic['PERIODICITY'] == config.PERIODICITY_LIST[2]:
            self.periods_per_month = 6
            self.ui.frmPentDekComboBox.setVisible(True)
            self.ui.toPentDekComboBox.setVisible(True)
            self.ui.lblFrmPer.setText('Pentad')
            self.ui.lblToPer.setText('Pentad')
            self.ui.lblPerCount.setText('pentads')
        elif self.ds_dic['PERIODICITY'] == config.PERIODICITY_LIST[3]:
            self.periods_per_month = 31
            self.ui.frmPentDekComboBox.setVisible(True)
            self.ui.toPentDekComboBox.setVisible(True)
            self.ui.lblFrmPer.setText('Day')
            self.ui.lblToPer.setText('Day')
            self.ui.lblPerCount.setText('days')
        self.ui.frmPentDekComboBox.setMaxCount(self.periods_per_month)
        self.ui.toPentDekComboBox.setMaxCount(self.periods_per_month)
        per_list = [str(item) for item in
                    [*range(1, (self.periods_per_month + 1))]]
        # need to pad if daily
        if self.ds_dic['PERIODICITY'] == config.PERIODICITY_LIST[3]:
            per_list = [entry.zfill(2) for entry in per_list]
        util.fill_misc_widget(self.ui.frmPentDekComboBox, per_list, False)
        util.fill_misc_widget(self.ui.toPentDekComboBox, per_list, False)
        util.fill_interval_widget(self.ui.durationLineEdit, self.ds_dic)
        self.ui.extentLineEdit.setText(self.ds_dic['DATAEXTENT'])
        self.set_download_count()

    def set_download_count(self):
        """
        Figures the number of periods that are downloading, and
        updates the start_date, end_date, and date_string_list attributes.
        date_string_list stores the list of (period of year) strings from
        start date to end date
        """
        sep = "."
        # get the gui info and translate it to datestrings for start and end
        from_year = self.ui.frmYearComboBox.currentText()
        from_month = self.ui.frmMonthComboBox.currentText()  # this is left padded
        to_year = self.ui.toYearComboBox.currentText()
        to_month = self.ui.toMonthComboBox.currentText()
        from_period = None
        to_period = None
        # sometimes this gets hit without all the values ready
        # if that happens, just exit
        if "" in [from_year, from_month, to_year, to_month]:
            return
        if self.ds_dic['PERIODICITY'] != config.PERIODICITY_LIST[1]:
            from_period = self.ui.frmPentDekComboBox.currentText().lstrip('0')
            to_period = self.ui.toPentDekComboBox.currentText().lstrip('0')
            if "" in [from_period, to_period]:
                return

        if self.ds_dic['DATADATEFORMAT'] in ['YYYYMM', 'YYMM']:
            # for monthly
            self.start_date = from_year + from_month
            self.end_date = to_year + to_month
            self.date_string_list = util.get_date_string_list_per_year_format(
                self.start_date, self.end_date, self.periods_per_year)
        elif self.ds_dic['DATADATEFORMAT'] == 'YYYY.MM':
            self.start_date = from_year + sep + from_month
            self.end_date = to_year + sep + to_month
            self.date_string_list = util.get_date_string_list_per_month_format(
                self.start_date, self.end_date, self.periods_per_month)
        elif self.ds_dic['DATADATEFORMAT'] in ['YYYY.MM.P', 'YYYY.MM.K']:
            self.start_date = from_year + sep + from_month + sep + from_period
            self.end_date = to_year + sep + to_month + sep + to_period
            self.date_string_list = util.get_date_string_list_per_month_format(
                self.start_date, self.end_date, self.periods_per_month)
        elif self.ds_dic['DATADATEFORMAT'] == 'YYYY.MM.DD':
            # need to keep the leading 0 on the period
            from_period = self.ui.frmPentDekComboBox.currentText()
            to_period = self.ui.toPentDekComboBox.currentText()
            self.start_date = from_year + sep + from_month + sep + from_period
            self.end_date = to_year + sep + to_month + sep + to_period
            self.date_string_list = util.get_date_string_list_per_month_format(
                self.start_date, self.end_date, self.periods_per_month)
        elif self.ds_dic['DATADATEFORMAT'][-3:] not in ['MMK', 'MMP']:
            # for dekadal/pentadal period of year formats
            new_start_per = dwnld_util.get_per_year_from_per_month(
                self.int_dic, (from_month + from_period))
            new_end_per = dwnld_util.get_per_year_from_per_month(
                self.int_dic, (to_month + to_period))
            self.start_date = from_year + new_start_per
            self.end_date = to_year + new_end_per
            self.date_string_list = util.get_date_string_list_per_year_format(
                self.start_date, self.end_date, self.periods_per_year)
        else:
            # for dekadal/pentadal period of month formats
            self.start_date = from_year + from_month + from_period
            self.end_date = to_year + to_month + to_period
            self.date_string_list = util.get_date_string_list_per_month_format(
                self.start_date, self.end_date, self.periods_per_month)
        # print(str(self.ds_dic['DATASETNAME']))
        # print(str(self.ds_dic['DATADATEFORMAT']))
        # print(str(self.start_date))
        # print(str(self.end_date))
        # print(str(self.date_string_list))
        self.ui.downloadCountSpinBox.setValue(len(self.date_string_list) - 1)

    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()
