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

"""
/***************************************************************************
 Networks
                                 A QGIS plugin
 Networks
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2018-02-26
        copyright            : (C) 2018 by Patrick Palmier
        email                : patrick.palmier@cerema.fr
 ***************************************************************************/

/***************************************************************************
 * *
 * 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__ = 'Patrick Palmier'
__date__ = '2018-02-26'
__copyright__ = '(C) 2018 by Patrick Palmier'

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

__revision__ = '$Format:%H$'

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import *
from qgis.utils import *
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterNumber,
                       QgsProcessingParameterBoolean,
                       QgsProcessingParameterString,
                       QgsProcessingParameterExtent,
                       QgsProcessingParameterField,
                       QgsProcessingParameterExpression,
                       QgsProcessingParameterFileDestination,
                       QgsProcessingParameterFile,
                       QgsProcessingParameterEnum,
                       QgsProcessingParameterVectorLayer,
                       QgsVectorDataProvider,
                       QgsVectorLayer,
                       QgsFields)
import math
import os


class FichierTempsIJ(QgsProcessingAlgorithm):
    """
    This is an example algorithm that takes a vector layer and
    creates a new identical one.
    """
    
    types_data = ['String', 'Double', 'DateTime', 'Int']

    INPUT = 'INPUT'
    VARIABLE = 'VARIABLE'
    FILTRE = 'FILTRE'
    RECO = 'RECO'
    TIMED = 'TIMED'
    OUTPUT = 'OUTPUT'


    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFile('INPUT', self.tr('link times ouput file'), behavior=QgsProcessingParameterFile.File, fileFilter='Fichier texte (*.txt);;Tous les fichiers (*.*)', defaultValue=None))
        self.addParameter(QgsProcessingParameterString('VARIABLE', self.tr('variable'), multiLine=False, defaultValue='temps'))
        self.addParameter(QgsProcessingParameterExpression('FILTRE', self.tr('filter'), None, self.INPUT, optional=True))
        self.addParameter(QgsProcessingParameterBoolean('RECO', self.tr('remove initial/final boarding time'), defaultValue=True))
        self.addParameter(QgsProcessingParameterBoolean('TIMED', self.tr('time based links only'), defaultValue=False))
        self.addParameter(QgsProcessingParameterFileDestination('OUTPUT', self.tr('link indicators file'), fileFilter='Fichier texte (*.txt);;Tous les fichiers (*.*)', defaultValue=None))

    def processAlgorithm(self, parameters, context, feedback):
        fichier_reseau = self.parameterAsFile(parameters, self.INPUT, context)
        variable = self.parameterAsString(parameters, self.VARIABLE, context)
        filtre = QgsExpression(self.parameterAsExpression(parameters, self.FILTRE, context))
        reco = self.parameterAsBoolean(parameters, self.RECO, context)
        timed = self.parameterAsBoolean(parameters, self.TIMED, context)
        fichier_resultat = self.parameterAsFileOutput(parameters, self.OUTPUT, context)

        feedback.setProgressText("loading...")
        network_file = open(fichier_reseau, "r")
        ligne = network_file.readline()
        champs = ligne.strip().split(';')
        
        formuleContexte = self.createExpressionContext(parameters, context)
        filtre.prepare(formuleContexte)
        
        try:
            id_var = champs.index(variable)
        except ValueError:
            id_var = champs.index('temps')
            
        try:
            id_ij = champs.index('ij')
        except ValueError:
            id_ij = champs.index('i')
            
        try:
            id_type = champs.index('type')
        except ValueError:
            id_type = -1
            
        id_pole = champs.index('pole')
        id_origin = champs.index('origin')
        
        id_heureo = champs.index('heureo')
        id_heured = champs.index('heured')
        id_tatt1 = champs.index('tatt1')
        id_tcorr = champs.index('tcorr')
        id_tatt = champs.index('tatt')
        
        dict_links = {}
        
        for ligne in network_file:
            e = ligne.strip().split(';')
            if len(e) < 2:
                continue
            
            # Application du filtre si défini
            if len(filtre.dump()) > 0:
                f = QgsFeature()
                fields = QgsFields()
                for c in champs:
                    fields.append(QgsField(c, QVariant.String))
                f.setFields(fields)
                for num, val in enumerate(e):
                    f.setAttribute(num, val)
                formuleContexte.setFeature(f)
                res = filtre.evaluate(formuleContexte)
                if not res:
                    continue
            
            # Traitement des liens temporels
            if timed and id_type >= 0:
                if e[id_type] != 'timed':
                    continue
            
            ij = e[id_ij]
            valeur = float(e[id_var])
            
            if reco:
                h_dep = float(e[id_heureo])
                h_arr = float(e[id_heured])
                tatt1 = float(e[id_tatt1])
                tcorr = float(e[id_tcorr])
                tatt = float(e[id_tatt])
                
                if variable == 'temps':
                    valeur = valeur - tatt1 - (h_arr - h_dep - valeur)
                elif variable == 'tatt':
                    valeur = valeur - tatt1
                    
            pole = e[id_pole]
            origin = e[id_origin]
            
            if ij not in dict_links:
                dict_links[ij] = [valeur, valeur, valeur, 1, pole, pole, [valeur], 1, 1, [pole], [pole], origin, origin]
            else:
                # Calcul de la moyenne cumulée
                dict_links[ij][0] += valeur
                
                # Minimum
                if valeur < dict_links[ij][1]:
                    dict_links[ij][1] = valeur
                    dict_links[ij][4] = pole
                    dict_links[ij][11] = origin
                    
                # Maximum
                if valeur > dict_links[ij][2]:
                    dict_links[ij][2] = valeur
                    dict_links[ij][5] = pole
                    dict_links[ij][12] = origin
                    
                dict_links[ij][3] += 1
                dict_links[ij][6].append(valeur)
                
                if pole not in dict_links[ij][9]:
                    dict_links[ij][9].append(pole)
                    dict_links[ij][7] += 1
                if pole not in dict_links[ij][10]:
                    dict_links[ij][10].append(pole)
                    dict_links[ij][8] += 1
                    
        network_file.close()
        
        output_file = open(fichier_resultat, "w")
        output_file.write("ij;avg;nb;min;max;pole_min;pole_max;departures;arrivals;sdev;o_min;o_max\n")
        
        for k, v in dict_links.items():
            avg = v[0] / v[3]
            sdev = 0
            for val in v[6]:
                sdev += (val - avg) ** 2
            sdev = math.sqrt(sdev / v[3])
            
            output_file.write(f"{k};{avg};{v[3]};{v[1]};{v[2]};{v[4]};{v[5]};{v[7]};{v[8]};{sdev};{v[11]};{v[12]}\n")
            
        output_file.close()

        # Bloc de création du tableau QGIS Layer (Là où se situait l'évaluation dynamique risquée)
        uri = f"file:///{fichier_resultat.replace(os.sep, '/')}?delimiter=;&type=csv"
        tableau = QgsVectorLayer(uri, "Indicateurs de temps par arcs ij", "delimitedtext")
        
        champ_existant = "ij"
        typo = "String"
        taille = 20
        precision = 0
        
        champs = tableau.fields()
        noms_champs = [c.name() for c in champs]
        
        if len(champ_existant) > 0:
            if champ_existant not in noms_champs:
                if tableau.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues:
                    
                    # CORRECTION BANDIT: Remplacement sécurisé du eval() dynamique par un dictionnaire (Dictionnaire de correspondance)
                    variant_types = {
                        'String': QVariant.String,
                        'Double': QVariant.Double,
                        'DateTime': QVariant.DateTime,
                        'Int': QVariant.Int,
                        'Integer': QVariant.Int
                    }
                    qvariant_type = variant_types.get(typo, QVariant.String)

                    tableau.dataProvider().addAttributes([
                        QgsField(champ_existant, qvariant_type, len=taille, prec=precision)
                    ])
                    tableau.updateFields()
                else:
                    feedback.setProgressText(self.tr("Warning: The layer is not editable"))

        QgsProject.instance().addMapLayer(tableau)
        return {self.OUTPUT: fichier_resultat}

    def name(self):
        return 'indicators_by_link'

    def displayName(self):
        return self.tr('Indicators by link (ij)')

    def group(self):
        return self.tr('Analysis')

    def groupId(self):
        return 'Analysis'

    def tr(self, string):
        return QCoreApplication.translate('FichierTempsIJ', string)
        
    def shortHelpString(self):
        return self.tr("""
        Produce indicators aggregated by link. This analysis is usefull when you want to study the variation of travel times during a time period
		
        Parameters:
            link times ouput file: the Musliw link times  output file (<FILENAME>_temps.txt)
			variable: The name of the variable for indicator computation (temps (time) by default)
            filter: expression to filter nodes times outfile (ex: ncorr<3 )
            remove initial/final boarding time:  If checked the initial or final waiting time (between the excepted arrival or departure time
            and the real one is substracted from the total travel time
			time based links only: If checked only time based links are analysed
            link indicators file: name of the result file (delimited text with ";" as separator) which containes the following attributes 
            ij: link id
            avg: average value 
            nb: number of od rows in the matrix file 
            min: minimum value indicator
            max: maximum value indicator
            pole_min: first node of intermodality corresponding to the minimum value 
            pole_max: first node of intermodality corresponding to the maximum value 
            departures: number of different departures (number of different alternatives) 
            arrivals:number of different arrivals (number of different alternatives) 
            sdev: standard deviation value
            o_min: origin with minimum value
            o_max: origin with maximum value
        """)

    def createInstance(self):
        return FichierTempsIJ()