# -*- coding: utf-8 -*-
"""
/***************************************************************************
 GQF
                                 A QGIS plugin
 GreaterQF anthropogenic heat flux model
                              -------------------
        begin                : 2016-06-20
        git sha              : $Format:%H$
        copyright            : (C) 2016 by University of reading
        email                : a.m.gabey@reading.ac.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from __future__ import absolute_import
from builtins import str
from builtins import object
from qgis.PyQt.QtCore import QSettings, QTranslator, qVersion, QCoreApplication, Qt
from qgis.PyQt.QtWidgets import QAction, QMessageBox, QFileDialog
from qgis.PyQt.QtGui import QIcon
from qgis.core import Qgis
from .greater_qf_dialog import GreaterQFDialog
from datetime import timedelta
from datetime import datetime as dt
import os.path
import webbrowser

# GQF specific code
from .PythonQF2.Config import Config
from .PythonQF2.GreaterQF import Model
from .time_displayer import time_displayer

try:
    import pandas as pd
    import matplotlib as plt
except:
    pass

class GreaterQF(object):
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'GreaterQF_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Check dependencies
        try:
            import pandas
            import matplotlib as plt
        except Exception as e:
            QMessageBox.critical(None, 'Error',
                                 'This plugin requires the pandas and matplotlib packages to be installed. '
                                 'Please consult the manual for further information')
            return
        self.dlg = GreaterQFDialog()
        self.setup() # Establish all object params
        self.connectButtons()
        self.dlg.cmdRunCancel.clicked.connect(self.startWorker, Qt.UniqueConnection)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&GQF')
        # self.toolbar = self.iface.addToolBar(u'GQF')
        # self.toolbar.setObjectName(u'GQF')

    def connectButtons(self):
        ''' Connect buttons to default actions '''
        self.dlg.cmdVisualise.clicked.connect(self.visualise, Qt.UniqueConnection)                                   # Visualisation dialog
        self.dlg.pushButtonClose.clicked.connect(self.dlg.close, Qt.UniqueConnection)                                # Close this dialog
        self.dlg.pushButtonRaw.clicked.connect(self.outputFolder, Qt.UniqueConnection)                               # Output folder
        self.dlg.cmdPrepare.clicked.connect(self.disaggregate, Qt.UniqueConnection)                                  # Prepare input data
        self.dlg.cmdLoadResults.clicked.connect(self.loadResults, Qt.UniqueConnection)                               # Load a previous model run's results
        self.dlg.chkDateRange.clicked.connect(self.useDateRange, Qt.UniqueConnection)
        self.dlg.chkDateList.clicked.connect(self.useDateList, Qt.UniqueConnection)
        self.dlg.pushButtonParams.clicked.connect(self.loadParams, Qt.UniqueConnection)              # Params file
        self.dlg.pushButtonDataSources.clicked.connect(self.dataSources, Qt.UniqueConnection)        # Data sources file
        self.dlg.pushButtonHelp.clicked.connect(self.help, Qt.UniqueConnection) # Launch web-based help
        self.dlg.cmdProcessedDataPath.clicked.connect(self.chooseProcessedDataPath, Qt.UniqueConnection)             # Select prepared input data

    def setup(self):
        ''' Set up the dialog box with empty parameters and a fresh model object'''
         # Create the dialog (after translation) and keep reference
        self.model = Model()
        self.useDateRange()

        # Populate text boxes with default entries
        self.dlg.txtParams.setText('')
        self.dlg.txtDataSources.setText('')
        self.dlg.textOutput_raw.setText('')
        self.dlg.txtProcessedDataPath.setText('')
        self.dlg.txtDateList.setText('')


    def chooseProcessedDataPath(self):
        '''
        Allows user to select folder containing pre-processed input data. Validates the folder and checks for a valid manifest.
        :return:
        '''

        fileDialog = QFileDialog()
        fileDialog.setFileMode(QFileDialog.Directory)
        fileDialog.setOption(QFileDialog.ShowDirsOnly, True)
        # fileDialog.setFileMode(2)
        # fileDialog.setAcceptMode(0)
        result = fileDialog.exec_()
        if result == 1:
            selectedFolder = fileDialog.selectedFiles()[0]
            # Check for manifest file or reject
            try:
                self.model.setPreProcessedInputFolder(selectedFolder)
            except Exception as e:
                QMessageBox.critical(None, 'Error setting processed data path', str(e))
                return
            self.dlg.txtProcessedDataPath.setText(selectedFolder)
            self.processedDataPath = selectedFolder
            # Enable model run button
            self.dlg.cmdRunCancel.setEnabled(True)

    def outputFolder(self):
        # Let user select folder into which model outputs will be saved
        fileDialog = QFileDialog()
        fileDialog.setFileMode(QFileDialog.Directory)
        fileDialog.setOption(QFileDialog.ShowDirsOnly, True)
        # fileDialog.setFileMode(4)
        # fileDialog.setAcceptMode(1)
        result = fileDialog.exec_()
        if result == 1:
            self.model.setOutputDir(fileDialog.selectedFiles()[0])
            self.dlg.textOutput_raw.setText(fileDialog.selectedFiles()[0])

    def dataSources(self):
        # Select and parse data sources file
        a = QFileDialog()
        a.open()
        result = a.exec_()
        if result == 1:
            df = a.selectedFiles()
            try:
                self.model.setDataSources(df[0])
            except Exception as e:
                QMessageBox.critical(None, 'Invalid Data Sources file provided', str(e))
                return

            self.dlg.txtDataSources.setText(df[0])

    def disaggregate(self):
        '''
        Disaggregates model inputs to files on hard drive (saves user having to repeat over and over)
        :return: Dict containing information about each disaggregated file, for use by model
        '''

        self.dlg.cmdPrepare.setEnabled(False)
        if QMessageBox.question(self.dlg, "Prepare input data",
                                "QGIS will freeze for a moment while preparing input. Du you want to continue?",
                                QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok:
            run = 1
        else:
            QMessageBox.critical(self.dlg, "Prepare input data", "No input data prepared. Model cannot be executed")
            run = 0

        if run == 1:
            processed = self.model.processInputData()
            self.dlg.txtProcessedDataPath.setText(processed)  # Update the UI to show path being used
            self.model.setPreProcessedInputFolder(processed)

    def help(self):
        url = "https://umep-docs.readthedocs.io/en/latest/processor/Urban%20Energy%20Balance%20GQ.html"
        webbrowser.open_new_tab(url)

    def visualise(self):
        # Launch results visualisation dialog, specifying the current model outputs directory as the one to use
        if not self.model.resultsAvailable:
            QMessageBox.critical(None, 'No results available', 'Nothing to plot: Either run the model or load some previous results')#
            return

        a = time_displayer(self.model, self.iface)
        a.exec_()

    def loadParams(self):
        # Select and parse parameters namelist
        a = QFileDialog()
        a.open()
        result = a.exec_()
        if result == 1:
            cf = a.selectedFiles()
            try:
                self.model.setParameters(cf[0])
            except Exception as e:
                QMessageBox.critical(None, 'Invalid parameters file', str(e))
                return

            self.dlg.txtParams.setText(cf[0])

    def loadResults(self):
        ''' Load a previous model run for visualisation'''
        self.reset() # Clear everything as we are loading previous results

        fileDialog = QFileDialog()
        # fileDialog.setFileMode(2)
        # fileDialog.setAcceptMode(0)
        fileDialog.setFileMode(QFileDialog.Directory)
        fileDialog.setOption(QFileDialog.ShowDirsOnly, True)
        result = fileDialog.exec_()
        if result == 1:
            selectedFolder = fileDialog.selectedFiles()[0]
            # Check for manifest file or reject
            try:
                locations = self.model.loadModelResults(selectedFolder)
            except Exception as e:
                QMessageBox.critical(None, 'Error loading previous model results', str(e))
                return


            self.dlg.cmdLoadResults.clicked.disconnect()
            self.dlg.cmdLoadResults.clicked.connect(self.reset, Qt.UniqueConnection)
            self.dlg.cmdLoadResults.setText('Clear data')
            self.dlg.cmdVisualise.setEnabled(True)

            # Update text boxes with file locations for user information
            self.dlg.txtProcessedDataPath.setText(locations['processedInputData'])
            self.dlg.textOutput_raw.setText(locations['outputPath'])
            self.dlg.txtDataSources.setText(locations['dsFile'])

            try:
                self.model.setDataSources(locations['dsFile'])
            except Exception as e:
                QMessageBox.critical(None, 'Error loading previous model data sources', str(e) + '. Re-runs not available')
                self.dlg.cmdRunCancel.setEnabled(False)

            self.dlg.txtParams.setText(locations['paramsFile'])
            try:
                self.model.setParameters(locations['paramsFile'])
            except Exception as e:
                QMessageBox.critical(None, 'Error loading previous model configuration', str(e) + '. Re-runs not available')
                self.dlg.cmdRunCancel.setEnabled(False)

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        return QCoreApplication.translate('GQF', message)

    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/GreaterQF/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'GQF'),
            callback=self.run,
            parent=self.iface.mainWindow())

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(
                self.tr(u'&GQF'),
                action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar

    def startWorker(self):
        # Do input validation
        # Get start date(s): One start date if a range, multiple if a list

        if self.dateRange:
            # Date range supplied
            if self.dlg.startDate.date().toPyDate() >= self.dlg.endDate.date().toPyDate():
                QMessageBox.critical(None, 'Error', 'Start date must be earlier than end date')
                return
            startDates = [self.dlg.startDate.date().toPyDate()]
            endDates = [self.dlg.endDate.date().toPyDate()]
        else:
            # Date list supplied
            # Parse date list
            text = self.dlg.txtDateList.text()
            dateList = text.split(',')
            startDates = []
            endDates = []
            for date in dateList:
                try:
                    startDates.append(dt.strptime(date.strip(), '%Y-%m-%d'))
                    endDates.append(startDates[-1] + timedelta(hours=24))
                except Exception:
                    raise ValueError('Invalid date list: ' + date + '. Must be comma-separated and in format YYYY-mm-dd')

        if self.dlg.startDate.date().toPyDate() >= self.dlg.endDate.date().toPyDate():
            QMessageBox.critical(None, 'Error', 'Start date must be earlier than end date')
            return

        # Run-time configuration
        # Component options (checkboxes)
        componentOptions = {'sensible':self.dlg.chkSensibleQf.checkState()>0, 'latent':self.dlg.chkLatentQf.checkState()>0, 'wastewater':self.dlg.chkWastewaterQf.checkState()>0}
        startDate = self.dlg.startDate.date().toPyDate().strftime('%Y-%m-%d')
        endDate = self.dlg.endDate.date().toPyDate().strftime('%Y-%m-%d')

        doLatent = componentOptions['latent']
        doSensible = componentOptions['sensible']
        doWastewater = componentOptions['wastewater']
        doAll = doLatent & doWastewater & doSensible

        # Create a Config object based on form entries
        configParams = {'start_dates': startDates,
                        'end_dates': endDates,
                        'all_qf': doAll,
                        'sensible_qf': doSensible,
                        'latent_qf': doLatent,
                        'wastewater_qf': doWastewater}

        config = Config()
        config.loadFromDictionary(configParams)
        self.model.setConfig(config)
        self.dlg.cmdRunCancel.setEnabled(False)
        self.dlg.pushButtonClose.setEnabled(False)
        # RUN MODEL HERE
        #self.model.run() #TODO Moved it outside to find error
        try:
            self.model.run()
            self.dlg.progressBar.setValue(100)
            self.iface.messageBar().pushMessage("GQF", "Model run complete. Click 'visualise' to view output",
                                            level=Qgis.Info)
            # Swap the "load" button to a "Clear" button
            self.dlg.cmdLoadResults.clicked.disconnect()
            self.dlg.cmdLoadResults.clicked.connect(self.reset, Qt.UniqueConnection)
            self.dlg.cmdLoadResults.setText('Clear data')
        except Exception as e:
            QMessageBox.critical(None, 'Error running GQF', str(e))

        self.dlg.cmdRunCancel.setEnabled(True)
        self.dlg.pushButtonClose.setEnabled(True)

    def run(self):
        """Run method that performs all the real work"""
        # show the dialog
        self.dlg.show()
        self.dlg.exec_()

    def reset(self):
        ''' Reset model object and dialogues so that user can start again'''
        self.setup()
        # Replace the "clear results" box with "Load results"
        self.dlg.cmdLoadResults.clicked.disconnect()
        self.dlg.cmdLoadResults.clicked.connect(self.loadResults, Qt.UniqueConnection)
        self.dlg.cmdLoadResults.setText('Load results')
        self.dlg.cmdRunCancel.setEnabled(True)
        self.dlg.chkDateRange.setChecked(True)
        self.dlg.chkDateList.setChecked(False)

    def useDateRange(self):
        '''
        Enable date range boxes and disable date list box
        :return:
        '''
        self.dateRange = True
        self.dlg.startDate.setEnabled(True)
        self.dlg.endDate.setEnabled(True)
        self.dlg.txtDateList.setEnabled(False)

    def useDateList(self):
        '''
        Enable date list box and disable date range boxes
        :return:
        '''
        self.dateRange = False
        self.dlg.startDate.setEnabled(False)
        self.dlg.endDate.setEnabled(False)
        self.dlg.txtDateList.setEnabled(True)
