# -*- 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,
                        QgsProject,
                        QgsFeatureSink,
                        QgsProcessingAlgorithm,
                        QgsProcessingParameterFeatureSource,
                        QgsProcessingParameterFeatureSink,
                        QgsProcessingParameterNumber,
                        QgsProcessingParameterEnum,
                        QgsProcessingParameterBoolean,
                        QgsProcessingParameterString,
                        QgsProcessingParameterExtent,
                        QgsProcessingParameterField,
                        QgsProcessingParameterExpression,
                        QgsProcessingParameterFileDestination,
                        QgsProcessingParameterVectorLayer,
                        QgsVectorDataProvider,
                        QgsVectorLayer,
                        QgsFields)
import gc

class AjoutChamp(QgsProcessingAlgorithm):
    """
    This is an example algorithm that takes a vector layer and
    creates a new identical one.
    """

    types_data = ['String', 'Double', 'DateTime']

    INPUT = 'INPUT'
    NOUVEAU = 'NOUVEAU'
    EXISTANT = 'EXISTANT'
    TYPE = 'TYPE'
    TAILLE = 'TAILLE'
    PRECISION = 'PRECISION'
    FILTRE = 'FILTRE'
    FORMULE = 'FORMULE'

    def initAlgorithm(self, config):
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                self.INPUT,
                self.tr('Layer'),
            )
        )

        self.addParameter(
            QgsProcessingParameterExpression(
                self.EXISTANT,
                self.tr('Field'),
                parentLayerParameterName=self.INPUT,
                optional=False
            )
        )        

        self.addParameter(
            QgsProcessingParameterEnum(
                self.TYPE,
                self.tr('Type'),
                options=self.types_data,
                defaultValue=0
            )
        )
        self.addParameter(
            QgsProcessingParameterNumber(
                self.TAILLE,
                self.tr('Size'),
                QgsProcessingParameterNumber.Integer,
                defaultValue=20,
                minValue=0,
                maxValue=255
            )
        )
        self.addParameter(
            QgsProcessingParameterNumber(
                self.PRECISION,
                self.tr('Precision'),
                QgsProcessingParameterNumber.Integer,
                defaultValue=15,
                minValue=0,
                maxValue=15
            )
        )        

        self.addParameter(
            QgsProcessingParameterExpression(
                self.FILTRE,
                self.tr('Filter'),
                None,
                self.INPUT,
                optional=True
            )
        )
        self.addParameter(
            QgsProcessingParameterExpression(
                self.FORMULE,
                self.tr('Formula'),
                None,
                self.INPUT
            )
        )
        

    def processAlgorithm(self, parameters, context, feedback):
        couche = self.parameterAsVectorLayer(parameters, self.INPUT, context)
        champ_existant = self.parameterAsExpression(parameters, self.EXISTANT, context)
        typo = self.types_data[self.parameterAsEnum(parameters, self.TYPE, context)]
        taille = self.parameterAsInt(parameters, self.TAILLE, context)
        precision = self.parameterAsInt(parameters, self.PRECISION, context)
        filtre = QgsExpression(self.parameterAsExpression(parameters, self.FILTRE, context))
        formule = QgsExpression(self.parameterAsExpression(parameters, self.FORMULE, context))

        tableau = couche
        champ_existant = champ_existant.strip('"').strip("'")
        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 de eval() par un dictionnaire de correspondance sûr
                    variant_types = {
                        'String': QVariant.String,
                        'Double': QVariant.Double,
                        'DateTime': QVariant.DateTime
                    }
                    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"))
                
                lib_champ = champ_existant
            else:
                lib_champ = champ_existant
            
            if len(filtre.dump()) == 0:
                request = QgsFeatureRequest()
            else:
                expr = filtre
                request = QgsFeatureRequest(expr)
            
            tableau2 = couche
            
            formuleContexte = self.createExpressionContext(parameters, context)
            formuleContexte.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(tableau))
            formule.prepare(formuleContexte)

            id_champ = tableau2.fields().lookupField(lib_champ)
            feedback.setProgressText(lib_champ)

            features = list(tableau2.getFeatures(request))
            nb = len(features)
            n = tableau2.featureCount()
            feedback.setProgressText(self.tr("updating field..."))
            tableau.startEditing()
            tableau.beginEditCommand(self.tr("updating field"))
            
            tableau.blockSignals(True)
            
            if tableau.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues:
                for p, f in enumerate(features):
                    num = f.id()
                    formuleContexte.setFeature(f)
                    valeur = formule.evaluate(formuleContexte)
                    valid = {id_champ: valeur}
                    tableau.changeAttributeValues(num, valid)
                    if n > 0:
                        feedback.setProgress(p * 100 / n)
            else:
                feedback.setProgressText(self.tr("Warning: The layer is not editable"))

            tableau.endEditCommand()
            tableau.commitChanges()
            
            tableau.blockSignals(False)

        gc.collect()
        return {self.INPUT: self.INPUT}


    def name(self):
        return 'update_field'

    def displayName(self):
        return self.tr('Update field')

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

    def groupId(self):
        return 'Network'

    def tr(self, string, context=''):
        if context == '':
            context = self.__class__.__name__
        return QCoreApplication.translate(context, string)

    def trAlgorithm(self, string, context=''):
        if context == '':
            context = self.__class__.__name__
        return string, QCoreApplication.translate(context, string)
        
    def shortHelpString(self):
        return self.tr("""
        Add or update a field in a Qgis layer from an expression formula on a set of rows
        
        Parameters:
            layer : a vector layer
            field : name of the field to update or name of the new field
            type : field data type (if new field)
            size : field size (if new field)
            precision : field precision (if new field and real type)
            filter: expression determining which rows will be updated
            formula : expression that will be written in the field( for filtered rows only)
        """)

    def createInstance(self):
        return AjoutChamp()