# -*- coding: utf-8 -*-

"""
/***************************************************************************
 Prepare CoolParksTool
                                 A QGIS plugin
 This plugin prepare data for the CoolParksTool
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2023-07-06
        copyright            : (C) 2023 by Jérémy Bernard / University of Gothenburg
        email                : jeremy.bernard@zaclys.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

__author__ = 'Jérémy Bernard'
__date__ = '2023-07-06'
__copyright__ = '(C) 2023 by Jérémy Bernard'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'

import os
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterField,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterNumber,
                       QgsProcessingParameterMatrix,
                       QgsProcessingParameterFolderDestination,
                       QgsProcessingParameterString,
                       QgsProcessingParameterRasterLayer,
                       QgsProcessingParameterBoolean,
                       QgsRasterLayer,
                       QgsVectorLayer,
                       QgsProject,
                       QgsProcessingContext,
                       QgsProcessingParameterEnum,
                       QgsProcessingParameterFile,
                       QgsProcessingException,
                       QgsLayerTreeGroup)
from qgis.PyQt.QtWidgets import QMessageBox

from pathlib import Path
import geopandas as gpd
from qgis.PyQt.QtGui import QIcon
import inspect
import processing

from .functions.coolparks_postprocess import loadCoolParksRaster, loadCoolParksVector, Renamer
from .functions import mainCalculations
from .functions.globalVariables import *
from .functions import WriteMetadata
from .functions.DataUtil import trunc_to, round_to


class CoolParksAnalyzerAlgorithm(QgsProcessingAlgorithm):
    """
    
    """

    # Constants used to refer to parameters and outputs. They will be
    # used when calling the algorithm from another algorithm, or when
    # calling from the QGIS console.

    # Input variables
    SCENARIO_REF_DIRECTORY = "SCENARIO_REF_DIRECTORY"
    SCENARIO_ALT_DIRECTORY = "SCENARIO_ALT_DIRECTORY"
    CHANGES = "CHANGES"
    OUTPUT_DIRECTORY = "OUTPUT_DIRECTORY"
    
    def initAlgorithm(self, config):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """
        
        # We add the input parameters
        self.addParameter(
           QgsProcessingParameterEnum(
               self.CHANGES, 
               self.tr('What is the change between alternative and reference scenario ?'),
               LIST_OF_CHANGES.values,
               defaultValue=None,
               optional = False))
        self.addParameter(
            QgsProcessingParameterFile(
                self.SCENARIO_REF_DIRECTORY,
                self.tr(f'Directory of the reference scenario (select the corresponding weather folder in the "{OUTPUT_PROCESSOR_FOLDER}" folder)'),
                behavior=QgsProcessingParameterFile.Folder))
        self.addParameter(
            QgsProcessingParameterFile(
                self.SCENARIO_ALT_DIRECTORY,
                self.tr(f'Directory of the alternative scenario (select the corresponding weather folder in the "{OUTPUT_PROCESSOR_FOLDER}" folder)'),
                behavior=QgsProcessingParameterFile.Folder))
        self.addParameter(
            QgsProcessingParameterFolderDestination(
                self.OUTPUT_DIRECTORY,
                self.tr('Directory to save the outputs'),
                TEMPO_DIRECTORY))
        

    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """
        
        refScenarioDirectory = self.parameterAsString(parameters, self.SCENARIO_REF_DIRECTORY, context)
        altScenarioDirectory = self.parameterAsString(parameters, self.SCENARIO_ALT_DIRECTORY, context)
        changes = self.parameterAsString(parameters, self.CHANGES, context)
        changes_string = LIST_OF_CHANGES[int(changes)]
        outputDirectory = self.parameterAsString(parameters, self.OUTPUT_DIRECTORY, context)
        
        # Test the reference and alternative scenario directory combinations
        # First test that they are results directories
        if not os.path.exists(refScenarioDirectory + os.sep + BUILD_INDEP_VAR + ".geojson"):
            raise QgsProcessingException(f'The reference scenario does not contain any results')
        if not os.path.exists(altScenarioDirectory + os.sep + BUILD_INDEP_VAR + ".geojson"):
            raise QgsProcessingException(f'The alternative scenario does not contain any results')
        if refScenarioDirectory == altScenarioDirectory:
            raise QgsProcessingException(f'You are proposing to compare the same scenarios...')
        # if changes_string == "buildings characteristics":
        #     build_ref = gpd.read_file(Path(altScenarioDirectory).parent.parent\
        #                              .joinpath(Path(os.path.join(OUTPUT_PREPROCESSOR_FOLDER,
        #                                                          OUTPUT_BUILD_INDIC + ".geojson"))))
        #     build_alt = gpd.read_file(Path(refScenarioDirectory).parent.parent\
        #                              .joinpath(Path(os.path.join(OUTPUT_PREPROCESSOR_FOLDER,
        #                                                          OUTPUT_BUILD_INDIC + ".geojson"))))
        #     test_geom = (build_ref.geom_almost_equals(build_alt).sum() == build_ref[ID_FIELD_BUILD].count())
        #     test_height = ((build_ref[HEIGHT_FIELD] == build_alt[HEIGHT_FIELD]).sum() == build_ref[ID_FIELD_BUILD].count())
        #     if not test_geom or not test_height:
        #         raise QgsProcessingException(f'You have specified that the change between scenario was "buildings characteristics". Buildings location and height should be the same in reference and alternative scenarios')
        if changes_string == "weather":
            if (Path(refScenarioDirectory).parent != Path(altScenarioDirectory).parent):
                raise QgsProcessingException(f'The alternative and reference weather should be saved in the same scenario folder')
        else:
            extent_ref = processing.run("native:polygonfromlayerextent",
                                        {'INPUT':refScenarioDirectory + os.sep + BUILD_INDEP_VAR + ".geojson",
                                         'ROUND_TO':0,
                                         'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"]
            extent_alt = processing.run("native:polygonfromlayerextent",
                                        {'INPUT':altScenarioDirectory + os.sep + BUILD_INDEP_VAR + ".geojson",
                                         'ROUND_TO':0,
                                         'OUTPUT':'TEMPORARY_OUTPUT'})["OUTPUT"]            
            tempo_overlap = os.path.join(TEMPO_DIRECTORY, "overlap.geojson")
            overlap = processing.run("native:calculatevectoroverlaps",
                                     {'INPUT':extent_ref,'LAYERS':[extent_alt],
                                      'OUTPUT':tempo_overlap,
                                      'GRID_SIZE':None})["OUTPUT"]
            # It seems the column where is saved the result has not the same name
            # depending on the QGIS version...
            df_overlap = gpd.read_file(overlap)
            if df_overlap.columns.isin(["Extent_pc"]).sum() == 1:
                col_name = "Extent_pc"
            else:
                col_name = df_overlap.columns[-2]
            if gpd.read_file(overlap).loc[0, col_name] < 50:
                raise QgsProcessingException(f'Reference and alternative scenario are not located in the same area')
        
        # Test that the parks have exactly the same shape (only composition should differ !!)
        park_ref = gpd.read_file(Path(refScenarioDirectory).parent.parent\
                                 .joinpath(Path(os.path.join(OUTPUT_PREPROCESSOR_FOLDER,
                                                             PARK_BOUNDARIES_TAB + ".geojson"))))
        park_alt = gpd.read_file(Path(altScenarioDirectory).parent.parent\
                                 .joinpath(Path(os.path.join(OUTPUT_PREPROCESSOR_FOLDER,
                                                             PARK_BOUNDARIES_TAB + ".geojson"))))
        if (park_ref.intersection(park_alt).area != park_ref.area)[0]:
            raise QgsProcessingException(f'Only park composition should differ between the reference and alternative parks. In your case, park shape differs')

        # if feedback:
        #     feedback.setProgressText("Writing settings for this model run to specified output folder (Filename: RunInfoURock_YYYY_DOY_HHMM.txt)")
        # WriteMetadataURock.writeRunInfo(outputDirectory, build_file, heightBuild,
        #                                 veg_file, attenuationVeg, baseHeightVeg, topHeightVeg,
        #                                 z_ref, v_ref, windDirection, profileType,
        #                                 profileFile,
        #                                 meshSize, dz)
        
        # Calculates the difference of effects between the two scenarios
        finalDirectory, dict_build_glob, diff_build_path, diff_deltaT_path,\
            diff_T_path, diff_build_extremums, dict_deltaT_glob = \
                mainCalculations.compareScenarios(refScenarioDirectory = refScenarioDirectory, 
                                                  altScenarioDirectory = altScenarioDirectory,
                                                  change = changes_string,
                                                  outputDirectory = outputDirectory)
        

        # Use the directory name used for the scenario comparison as a 
        # group in the map layer where to load the results
        group_name = finalDirectory.split(os.sep)[-1]
        # Get the root of the layer tree
        root = QgsProject.instance().layerTreeRoot()
        # Create a new group
        new_group = QgsLayerTreeGroup(group_name)
        # # Add the group to the root of the layer tree
        # root.insertChildNode(0, new_group)
        
        # Calculates the min and max values of the rasters and vectors
        deltaT_min_value = 0
        deltaT_max_value = 0
        T_min_value = 0
        T_max_value = 0
        for tp in [DAY_TIME, NIGHT_TIME]:
            # In case only building charac have been changed, no differences of air temp
            # between ref and alt, thus just show the effect of the park on its surroundings
            # if changes_string == "buildings characteristics":
            #     diff_deltaT_path[tp] = refScenarioDirectory + os.sep + OUTPUT_DT + "_" + str(tp) + "h"
            
            if diff_deltaT_path[tp]:  
                layer_stat = processing.run("native:rasterlayerstatistics", 
                                            {'INPUT':diff_deltaT_path[tp],
                                            'BAND':1,
                                            'OUTPUT_HTML_FILE':'TEMPORARY_OUTPUT'})
                if layer_stat['MIN'] < deltaT_min_value:
                    deltaT_min_value = layer_stat['MIN']
                if layer_stat['MAX'] > deltaT_max_value:
                    deltaT_max_value = layer_stat['MAX']
                    
            if diff_T_path[tp]:  
                layer_stat = processing.run("native:rasterlayerstatistics", 
                                            {'INPUT':diff_T_path[tp],
                                            'BAND':1,
                                            'OUTPUT_HTML_FILE':'TEMPORARY_OUTPUT'})
                if layer_stat['MIN'] < T_min_value:
                    T_min_value = layer_stat['MIN']
                if layer_stat['MAX'] > T_max_value:
                    T_max_value = layer_stat['MAX']      
                    
        # Calculates the number of significant digits
        if NB_ISOVALUES < 10:
            sign_digits = 1
        else:
            sign_digits = 2
        
        global layernames
        layernames = {}
        i = 0
        # Convert the raster layers into contours and load the resulting layers into QGIS with a given style
        for tp in [DAY_TIME, NIGHT_TIME]:
            if diff_deltaT_path[tp]:                     
                # Calculate interval size
                interval_isovalues_dT = trunc_to((deltaT_max_value-deltaT_min_value) / NB_ISOVALUES,
                                                 sign_digits,
                                                 True)
                
                # Convert the raster results into contours
                processing.run("gdal:contour_polygon", 
                               {'INPUT':diff_deltaT_path[tp],
                                'BAND':1,
                                'INTERVAL':f'{interval_isovalues_dT}',
                                'CREATE_3D':False,
                                'IGNORE_NODATA':False,
                                'NODATA':None,
                                'OFFSET':f'{0 + interval_isovalues_dT / 2}',
                                'EXTRA':'','FIELD_NAME_MIN':'ELEV_MIN',
                                'FIELD_NAME_MAX':'ELEV_MAX',
                                'OUTPUT': diff_deltaT_path[tp] + ".geojson"})   
                
                # if changes_string == "buildings characteristics":
                #     layernames[i] = Renamer(f"Cooling (ref) at {tp}:00 (°C)")
                # else:
                layernames[i] = Renamer(f"Cooling (alt - ref) at {tp}:00 (°C)")
                # Load the vector layer with a given style
                loadCoolParksVector(filepath = diff_deltaT_path[tp] + ".geojson",
                                    layername = layernames[i],
                                    variable = None,
                                    subgroup = new_group,
                                    vector_min = deltaT_min_value,
                                    vector_max = deltaT_max_value,
                                    feedback = feedback,
                                    context = context,
                                    valueZero = 0,
                                    opacity = DEFAULT_OPACITY)
                
                i += 1

                # loadCoolParksRaster(filepath = diff_deltaT_path[tp],
                #                     specific_scale = True,
                #                     subgroup = new_group,
                #                     raster_min = deltaT_min_value,
                #                     raster_max = deltaT_max_value,
                #                     feedback = feedback,
                #                     context = context)
            if diff_T_path[tp]:        
                # Calculate interval size
                interval_isovalues_T = trunc_to((T_max_value-T_min_value) / NB_ISOVALUES,
                                                sign_digits,
                                                True)
                # Convert the raster results into contours
                processing.run("gdal:contour_polygon", 
                               {'INPUT':diff_T_path[tp],
                                'BAND':1,
                                'INTERVAL':f'{interval_isovalues_T}',
                                'CREATE_3D':False,
                                'IGNORE_NODATA':False,
                                'NODATA':None,
                                'OFFSET':f'{0 + interval_isovalues_T / 2}',
                                'EXTRA':'','FIELD_NAME_MIN':'ELEV_MIN',
                                'FIELD_NAME_MAX':'ELEV_MAX',
                                'OUTPUT': diff_T_path[tp] + ".geojson"}) 
                
                # Load the vector layer with a given style
                layernames[i] = Renamer(f"Air temperature (alt-ref) at {tp}:00 (°C)")
                loadCoolParksVector(filepath = diff_T_path[tp] + ".geojson",
                                    layername = layernames[i],
                                    variable = None,
                                    subgroup = new_group,
                                    vector_min = T_min_value,
                                    vector_max = T_max_value,
                                    feedback = feedback,
                                    context = context,
                                    valueZero = 0,
                                    opacity = DEFAULT_OPACITY)
                
                # loadCoolParksRaster(filepath = diff_T_path[tp],
                #                     specific_scale = False,
                #                     subgroup = new_group,
                #                     raster_min = T_min_value,
                #                     raster_max = T_max_value,
                #                     feedback = feedback,
                #                     context = context)
                
                i += 1
        
        if diff_build_path:
            for var in diff_build_extremums:
                layernames[i] = Renamer(BUILDING_LEGEND_POSTPROCESS[var])
                loadCoolParksVector(filepath = diff_build_path,
                                    layername = layernames[i],
                                    variable = var,
                                    subgroup = new_group,
                                    vector_min = diff_build_extremums[var][0],
                                    vector_max = diff_build_extremums[var][1],
                                    feedback = feedback,
                                    context = context,
                                    valueZero = 0,
                                    opacity = 1)
                i += 1
        
        # Return the output file names
        return {self.OUTPUT_DIRECTORY: finalDirectory,
                f'A.1. Average air temperature modification at {DAY_TIME}:00 in the city due to the park in '+ \
                f'the reference scenario': dict_deltaT_glob[DAY_TIME][REF_SCEN] + '°C',
                f'A.2. Average air temperature modification at {DAY_TIME}:00 in the city due to the park in'+\
                f'the alternative scenario': dict_deltaT_glob[DAY_TIME][ALT_SCEN] + '°C',
                f'B.1. Average air temperature modification at {NIGHT_TIME}:00 in the city due to the park in '+ \
                f'the reference scenario': dict_deltaT_glob[NIGHT_TIME][REF_SCEN] + '°C',
                f'B.2. Average air temperature modification at {NIGHT_TIME}:00 in the city due to the park in '+ \
                f'the alternative scenario': dict_deltaT_glob[NIGHT_TIME][ALT_SCEN] + '°C',
                f'C.1. Total building energy need saved thanks to the park in '+ \
                f'the reference scenario': f'{dict_build_glob["ENERGY_IMPACT_REF"]}',
                f'C.2. Total building energy need saved thanks to the park in '+ \
                f'the alternative scenario': f'{dict_build_glob["ENERGY_IMPACT_ALT"]}',
                f'D.1. Average degree-hour of thermal discomfort in buildings saved thanks to the park in '+ \
                f'the reference scenario': f'{dict_build_glob["THERM_COMFORT_REF"]}',
                f'D.2. Average degree-hour of thermal discomfort in buildings saved thanks to the park in '+ \
                f'the alternative scenario': f'{dict_build_glob["THERM_COMFORT_ALT"]}'
                }
    
    def name(self):
        """
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return 'coolparkstool_postprocess'

    def displayName(self):
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return self.tr('3. Compare 2 scenarios')

    def group(self):
        """
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        """
        return self.tr(self.groupId())

    def groupId(self):
        """
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return ''

    def tr(self, string):
        return QCoreApplication.translate('Processing', string)
    
    def shortHelpString(self):
        return self.tr('The CoolParksTool "3. Compare two scenario" module is used '+
                       'to compare 2 scenarios when:\n'+
                       '    - only the park composition differs,'+
                       '    - only the urban morphology differs,'+
                       '    - only the weather conditions differ.'
        '\n'
        '\n'
        """You need to first use the '2. Calculate park effect' for the 2 scenarios you 
        want to simulate."""
        '\n'
        '\n'
        '---------------\n'
        'Full manual available via the <b>Help</b>-button.')

    def helpUrl(self):
        url = "https://github.com/j3r3m1/coolparkstool"
        return url
    
    def icon(self):
        cmd_folder = Path(os.path.split(inspect.getfile(inspect.currentframe()))[0]).parent
        icon = QIcon(str(cmd_folder) + "/icons/urock.png")
        return icon

    def createInstance(self):
        return CoolParksAnalyzerAlgorithm()
