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

"""
/***************************************************************************
 LightPollutionToolbox
                                 A QGIS plugin
 Light pollution indicators (focus on public lighting)
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2020-04-20
        copyright            : (C) 2020 by Mathieu Chailloux
        email                : mathieu@chailloux.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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__ = 'Mathieu Chailloux'
__date__ = '2020-04-20'
__copyright__ = '(C) 2020 by Mathieu Chailloux'

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

__revision__ = '$Format:%H$'

import os
import os.path
import tarfile
import processing
import glob
import math
import csv

from pathlib import Path

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsField,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingMultiStepFeedback,
                       QgsProcessingParameterField,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterNumber,
                       QgsProcessingParameterRange,
                       QgsProcessingParameterEnum,
                       QgsProcessingParameterFile,
                       QgsProcessingParameterRasterLayer,
                       QgsProcessingParameterRasterDestination)
from qgis import processing
from ..qgis_lib_mc import utils, qgsUtils, qgsTreatments, styles



class LampType:

    SHP = 'SHP'
    SBP = 'SBP'
    LED = 'LED'
    IM = 'IM'
    VM = 'VM'
    HAL = 'HAL'
    BF = 'BF'
    FC = 'FC'
    TF = 'TF'
    INC = 'INC'
    IND = 'IND'
    DI = 'DI'
    COS = 'COS'
    
    LAMP_TYPE_DESCR = {
        SHP : 'Sodium Haut Pression',
        SBP : 'Sodium Basse Pression',
        LED : 'Diodes Electro Luminescentes',
        IM : 'Iodures Métalliques',
        VM : 'Vapeur de Mercure',
        HAL : 'Halogène',
        BF : 'Ballon Fluorescent',
        FC : 'Lampe fluo-compacte',
        TF : 'Tube Fluorescent',
        INC : 'Incandescence',
        IND : 'Lampe à induction',
        DI : 'Dichroïque',
        COS : 'Cosmopolis',
    }
    
    LAMP_BLUE_PERC_CONST = {
        SHP : 10,
        SBP : 0,
        HAL : 13,
        INC : 12,
        # LED : 'Diodes Electro Luminescentes',
        # IM : 'Iodures Métalliques',
        # VM : 'Vapeur de Mercure',
        # HAL : 'Halogène',
        # BF : 'Ballon Fluorescent',
        # FC : 'Lampe fluo-compacte',
        # IND : 'Lampe à induction',
        # DI : 'Dichroïque',
        # COS : 'Cosmopolis',
    }
    
    # http://wikinight.free.fr/index.php/2017/10/02/367/
    # pourcentage de bleu en fonction de la température de couleur
    LAMP_BLUE_PERC_TREND = {
        LED : [(2700,19),(3000,22),(3500,26),(4000,30),(4500,33),(5000,37),(5700,41),(6500,46)],
        IM : [(3145,20),(4002,33),(4041,35)],
        TF : [(2940,20),(3480,26),(3969,30)],
        # FLUO_COMPACT : 'Lampe fluo-compacte',
        # INDUCTION : 'Lampe à induction',
        # DICHROIQUE : 'Dichroïque',
        # COSMOPOLIS : 'Cosmopolis',
    }
    LAMP_BLUE_PERC_TREND_XY = {
        LED : (0.00704,1.04418),
        IM : (0.01602,-30.42912),
        TF : (0.00974,-8.40427),
    }
    
    def getBluePerc(self,lamp_type,tempCoul=None):
        if lamp_type in self.LAMP_BLUE_PERC_CONST:
            return self.LAMP_BLUE_PERC_CONST[lamp_type]
        elif lamp_type in self.LAMP_BLUE_PERC_TREND_XY:
            if not tempCoul:
                raise QgsProcessingException("Missing color temperature")
            x,y = self.LAMP_BLUE_PERC_TREND_XY[lamp_type]
            res = int(x * tempCoul) + y
            return res
        else:
            return None

class FluxDispBaseAlg(QgsProcessingAlgorithm,LampType):

    INPUT = 'INPUT'
    FLUX_FIELD = 'FLUX_FIELD'
    FLUX_RADIUS_FIELD = 'flux_radius'
    RADIUS_MODE = 'RADIUS_MODE'
    RADIUS_COEFF = 'RADIUS_COEFF'
    RESOLUTION = 'RESOLUTION'
    OUTPUT = 'OUTPUT'
    
    DEFAULT_RES = 10.0
    
    def tr(self, string):
        return QCoreApplication.translate('Processing', string)
        
    def displayName(self):
        return self.tr(self.name())

    def group(self):
        return self.tr('Light Dispersal')

    def groupId(self):
        return 'lpm'

    def initParams(self):
        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT,
                self.tr('Lighting layer'),
                [QgsProcessing.TypeVectorPoint]))
        self.addParameter(
            QgsProcessingParameterField(
                self.FLUX_FIELD,
                self.tr('Flux field name'),
                parentLayerParameterName=self.INPUT,
                defaultValue="lumen"))
        self.addParameter(
            QgsProcessingParameterEnum(
                self.RADIUS_MODE,
                self.tr('Radius mode'),
                options=[self.tr('Div100'),self.tr('sqrt')],
                defaultValue=1))
        self.addParameter(
            QgsProcessingParameterNumber(
                self.RADIUS_COEFF,
                self.tr('Radius coeff'),
                type=QgsProcessingParameterNumber.Double,
                defaultValue=1,
                optional=True))
        self.addParameter(
            QgsProcessingParameterNumber(
                self.RESOLUTION,
                self.tr('Resolution'),
                type=QgsProcessingParameterNumber.Double,
                defaultValue=self.DEFAULT_RES))
                
    def initOutput(self):
        self.addParameter(
            QgsProcessingParameterRasterDestination(
                self.OUTPUT,
                self.tr('Output layer')))
                
    def parseParams(self, parameters, context):
        self.input = self.parameterAsVectorLayer(parameters, self.INPUT, context)
        self.flux_field = self.parameterAsString(parameters,self.FLUX_FIELD,context)
        self.radius_mode = self.parameterAsEnum(parameters, self.RADIUS_MODE, context)
        self.radius_coeff = self.parameterAsDouble(parameters,self.RADIUS_COEFF,context)
        self.resolution = self.parameterAsInt(parameters,self.RESOLUTION,context)
        self.output = self.parameterAsOutputLayer(parameters,self.OUTPUT,context)
        
        if not self.input:
            raise QgsProcessingException("No input layer")
        if self.flux_field not in self.input.fields().names():
            raise QgsProcessingException("Field '" + "' does not exist")
        
    def funcFluxRadius100(self,f):
        try:
            res = float(str(f[self.flux_field])) / 100
            coeff = self.radius_coeff if self.radius_coeff else 1
            return res * coeff
        except ValueError:
            return None
        
    def funcFluxRadiusSqrt(self,f):
        try:
            res =  math.sqrt(float(str(f[self.flux_field])))
            coeff = self.radius_coeff if self.radius_coeff else 1
            return res * coeff
        except ValueError:
            return None
        
        
class FluxDispAlg(FluxDispBaseAlg, LampType):
    
    def name(self):
        return 'fluxDisp'

    def displayName(self):
        return self.tr('Flux Disp')
        
    def createInstance(self):
        return FluxDispAlg()

    def initAlgorithm(self,config=None):
        self.initParams()
        self.initOutput()
    
    # def createFluxDistField(self,in_field):
        # out_field = self.FLUX_RADIUS_FIELD
        # qgsUtils.createOrUpdateField(self.input,self.in_field,self.funcFluxRadius,out_field)
        # if in_field not in self.input.fields().names():
            # raise QgsProcessingException("Field '" + in_field + "' does not exist")
    
        # out_field = self.FLUX_RADIUS_FIELD
        # if out_field not in self.input.fields().names():
            # field = QgsField(out_field, QVariant.Double)
            # self.input.dataProvider().addAttributes([field])
            # self.input.updateFields()
        
        # self.input.startEditing()    
        # for f in self.input.getFeatures():
            # try:
                # f[out_field] = float(str(f[in_field])) / 100
            # except ValueError:
                # f[out_field] = None
            # self.input.updateFeature(f)
        # self.input.commitChanges()
            
    
    def processAlgorithm(self, parameters, context, feedback):
        self.parseParams(parameters,context)
        # self.createFluxDistField(self.flux_field)
        func = self.funcFluxRadius100 if self.radius_mode == 0 else self.funcFluxRadiusSqrt
        qgsUtils.createOrUpdateField(self.input,func,self.FLUX_RADIUS_FIELD)
        self.out = qgsTreatments.applyHeatmap(self.input, self.output,
            resolution=self.resolution, radius_field=self.FLUX_RADIUS_FIELD,
            weight_field=self.flux_field,context=context,feedback=feedback)
            
        # self.out_layer = self.parameterAsRasterLayer(parameters,self.OUTPUT,context)
        # styles.setLightingQuantileStyle(self.out_layer)
        return { self.OUTPUT : self.output }
        
    def postProcessAlgorithm(self,context,feedback):
        out_layer = qgsUtils.loadRasterLayer(self.output)
        if not out_layer:
            raise QgsProcessingException("No out layer")
        styles.setLightingQuantileStyle(out_layer)
        return {self.OUTPUT: self.output }
        
class FluxDispTempCoulAlg(FluxDispBaseAlg):
    
    TEMP_COUL_FIELD = 'TEMP_COUL_FIELD'
    LAMP_TYPE_FIELD = 'LAMP_TYPE_FIELD'
    LAMP_TYPE_ASSOC = 'LAMP_TYPE_ASSOC'
    RANGE = 'RANGE'
    
    DEFAULT_LAMP_TYPE_ASSOC = "C:/Users/mathieu.chailloux/AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins/LightPollutionToolbox/assets/LampTypeAssoc_StGirons.csv"
    IN_LAMP_TYPE = 'IN_LAMP_TYPE'
    OUT_LAMP_TYPE = 'OUT_LAMP_TYPE'
    BLUE_PERC_FIELD = 'blue_perc'
    BLUE_WEIGHT_FIELD = 'blue_weight'
    
    def name(self):
        return 'fluxDispTempCoul'

    def displayName(self):
        return self.tr('Flux Disp Temp Coul')
        
    def createInstance(self):
        return FluxDispTempCoulAlg()

    def initAlgorithm(self,config=None):
        self.initParams()
        self.addParameter(
            QgsProcessingParameterField(
                self.TEMP_COUL_FIELD,
                self.tr('Color temperature field'),
                defaultValue='temperatur',
                parentLayerParameterName=self.INPUT))
        self.addParameter(
            QgsProcessingParameterField(
                self.LAMP_TYPE_FIELD,
                self.tr('Lamp type field'),
                defaultValue='Type mat s',
                parentLayerParameterName=self.INPUT))
        self.addParameter(
            QgsProcessingParameterFile(
                self.LAMP_TYPE_ASSOC,
                self.tr('Lamp types association file'),
                defaultValue=self.DEFAULT_LAMP_TYPE_ASSOC))
        # self.addParameter(
            # QgsProcessingParameterRange(
                # self.RANGE,
                # self.tr('Color temperature range'),
                # optional=True))
        self.initOutput()
        
    def parseLTFile(self,fname,feedback):
        fieldnames = [self.IN_LAMP_TYPE,self.OUT_LAMP_TYPE]
        self.lt_assoc = {}
        if os.path.isfile(fname):
            with open(fname,newline='') as csvfile:
                reader = csv.DictReader(csvfile,fieldnames=fieldnames,delimiter=';')
                for row in reader:
                    try:
                        in_lt, out_lt = row[self.IN_LAMP_TYPE], row[self.OUT_LAMP_TYPE]
                        if out_lt in self.LAMP_TYPE_DESCR:
                            self.lt_assoc[in_lt] = out_lt
                    except ValueError:
                        feedback.pushDebugInfo("Could not parse " + str(row))
                    except TypeError:
                        feedback.pushDebugInfo("Could not parse " + str(row))
        else:
            raise QgsProcessingException("File " + str(fname) + " does not exist")
            
    def funcBluePerc(self,f):
        temp_coul = f[self.temp_coul_field]
        lamp_type_init = f[self.lamp_type_field]
        if lamp_type_init in self.lt_assoc:
            lamp_type = self.lt_assoc[lamp_type_init]
            blue_perc = self.getBluePerc(lamp_type,temp_coul)
            return blue_perc
        else:
            return None

    def funcBlueWeight(self,f):
        if f[self.BLUE_PERC_FIELD]:
            try:
                flux = float(f[self.flux_field])
                blue_perc = float(f[self.BLUE_PERC_FIELD])
                return (flux * blue_perc) / 100
            except ValueError:
                utils.info("no flux : " + str(f[self.flux_field]))
                utils.info("no perc : " + str(f[self.BLUE_PERC_FIELD]))
                return None
        else:
            utils.info("no perc : " + str(f[self.BLUE_PERC_FIELD]))
            return None
    
    def processAlgorithm(self, parameters, context, feedback):
        # Parameters
        self.parseParams(parameters,context)
        self.lamp_type_field = self.parameterAsString(parameters,self.LAMP_TYPE_FIELD,context)
        self.temp_coul_field = self.parameterAsString(parameters,self.TEMP_COUL_FIELD,context)
        filename = self.parameterAsFile(parameters,self.LAMP_TYPE_ASSOC,context)
        # Body
        self.parseLTFile(filename,feedback)
        feedback.pushDebugInfo("LT :\n" + str(self.lt_assoc))
        qgsUtils.createOrUpdateField(self.input,self.funcFluxRadiusSqrt,self.FLUX_RADIUS_FIELD)
        qgsUtils.createOrUpdateField(self.input,self.funcBluePerc,self.BLUE_PERC_FIELD)
        qgsUtils.createOrUpdateField(self.input,self.funcBlueWeight,self.BLUE_WEIGHT_FIELD)
        out = qgsTreatments.applyHeatmap(self.input, self.output,
            resolution=self.resolution, radius_field=self.FLUX_RADIUS_FIELD,
            weight_field=self.BLUE_WEIGHT_FIELD,context=context,feedback=feedback)
            
        return { self.OUTPUT : out }
        
        
class LightDispSymbology(FluxDispBaseAlg):

    def initAlgorithm(self, config=None):
        self.addParameter(
            QgsProcessingParameterRasterLayer(
                self.INPUT,
                self.tr('Input layer')))
    
    
    def processAlgorithm(self, parameters, context, feedback):
        self.in_layer = self.parameterAsRasterLayer(parameters,self.INPUT,context)
        if not self.in_layer:
            raise QgsProcessingException("No input layer")
        return { self.OUTPUT : None }
        
    
    def postProcessAlgorithm(self,context,feedback):
        if not self.in_layer:
            raise QgsProcessingException("No input layer")
        styles.setLightingQuantileStyle(self.in_layer)
        return { self.OUTPUT : None }
        
    def name(self):
        return 'lightSymbology'
        
    def displayName(self):
        return self.tr('Apply lighting symbology')
        
    def shortHelpString(self):
        helpStr = "Apply lighting symbology to selected layer layer"
        return self.tr(helpStr)

    def createInstance(self):
        return LightDispSymbology()