'use strict';
define(function() {
  /**
   * Class : EditRulesFactory
   * Cette classe est chargée d'exécuter les règles métiers d'édition
   * associées à une couche (un FeatureTypeInfo).
   */
  var EditRulesFactory = function(
    $q,
    EditRulesProvider,
    EditFactory,
    $filter,
    EditTypesFactory,
    gclayers,
    AdvancedEditionFactory,
    gcRestrictionProvider,
    FeatureTypeFactory,
    gceditsaveAttachmentFactory,
    gaJsUtils,
    bizeditProvider,
    $rootScope,
    extendedNgDialog,
    AssociationFactory
  ) {
    var Edit = {};
    var format = new ol.format.GeoJSON();
    var templatefeatcollect = {
      type: 'FeatureCollection',
      features: [],
    };
    var erfScope = {
      rulesEnabled: true,
    };
    const numericPattern = /^-?\d*\.?\d+$/;

    /**
     * Function: add
     * @param {object} editdescription objet en cours d'édition/création
     * @param {object} featureType: couche auquel feature appartient.
     * @param {ol.Map} map map openlayers de l'application KIS-MAP
     */
    function executeInitRules(editdescription, featureType, map) {
      var deferred = $q.defer();

      if (applyRules()) {
        var rules = Object.assign({}, EditRulesProvider.initRules);

        for (var i = 0; i < featureType.rules.length; i++) {
          var ruleConf = featureType.rules[i];
          if (
            ruleConf != undefined &&
            Array.isArray(ruleConf.editTypes) &&
            editdescription &&
            ruleConf.editTypes.some(editType => editType.name == editdescription.editType)
          ) {
            if (ruleConf.type == 'OnInit') {
              var rule = rules[ruleConf.name];
              //Call prend l'objet appelant en premier argument
              //puis prend autant d'argument que la fonction appelée.
              //Toutes les règles prenent la même série d'argument.
              if (rule != undefined)
                rule.call(this, editdescription, ruleConf, featureType, map);
            }
          }
        }
      }
      deferred.resolve('terminé !');
      //deferred.reject(data);

      return deferred.promise;
    }
    function preStartRuleIsOk(editdescription, params) {
      var ind,
        rules,
        prop,
        count = 0,
        rejected = false,
        resultToCheck = false;
      var ruleCheckCnt = 0;
      var deferred = $q.defer();

      if (!applyRules()) {
        deferred.resolve();
      } else {
        rules = editdescription.fti.rules;

        if (rules.length == 0) deferred.resolve();
        else {
          for (ind = 0; ind < rules.length; ind++) {
            if (
              rules[ind].type == 'OnInit' &&
              rules[ind].checkResult != undefined
            )
              ++ruleCheckCnt;
          }
          for (ind = 0; ind < rules.length; ind++) {
            if (
              rules[ind].type == 'OnInit' &&
              rules[ind].checkResult != undefined
            ) {
              params.rules = rules;
              resultToCheck = true;
              for (prop in rules[ind].checkResultParams)
                params[prop] = rules[ind].checkResultParams[prop];
              rules[ind].checkResult(editdescription, params, rules[ind]).then(
                function() {
                  if (!rejected && ++count == ruleCheckCnt) deferred.resolve();
                },
                function(res) {
                  rejected = true;
                  deferred.reject(res);
                }
              );
            }
          }
          if (!resultToCheck) deferred.resolve();
        }
      }
      return deferred.promise;
    }

    function executeStartRules(editdescription, featureType, map, otherParams) {
      var deferred = $q.defer();

      if (!applyRules()) {
        deferred.resolve('terminé !');
      } else {
        var rules = Object.assign({},EditRulesProvider.startRules);

        //Pour chaque config de règle de la couche 'featureType'
        var i = 0;

        preStartRuleIsOk(editdescription, otherParams).then(
          function() {
            //Pour chaque config de règle de la couche 'featureType'
            executeNextRule();
          },
          function(res) {
            deferred.reject(res);
          }
        );
      }

      function executeNextRule() {
        if (i < featureType.rules.length) {
          var ruleConf = featureType.rules[i];
          //Si la config de règle correspond au type 'onEnd' et au type d'édition
          if (
            ruleConf !== undefined && isEditType(ruleConf, editdescription.editType) &&
            ruleConf.type === 'OnStart'
          ) {
            //Récupération de l'implémentation de la règle
            var rule = rules[ruleConf.name];
            //Execution de la règle
            if (rule != undefined) {
              var promise = rule.call(
                this,
                editdescription,
                ruleConf,
                featureType,
                map
              );
              //promise2 renvoyé par 'then', n'est pas utilisée, et elle sera resolue à undefined à l'appel des callback car ceux-ci ne renvoient rien.
              promise.then(
                function() {
                  i++;
                  executeNextRule();
                },
                function(reason) {
                  //Si ce callback d'erreur est appelé, l'execution des règles suivantes est annulée.
                  deferred.reject(reason);
                }
              );
            } else {
              i++;
              executeNextRule();
            }
          } else {
            i++;
            executeNextRule();
          }
        }
        //Fin du tableau de règles à executer.
        else {
          deferred.resolve('terminé !');
        }
      }

      return deferred.promise;
    }

    /**
     * Règles métiers de type "onEnd" qui doit obligatoirement être validés pour que la session d'édition soit réussie.
     * Si l'exécution d'une de ces règles échoue alors la session d'édition doit être annulée.
     * @type {string[]}
     */
    const mandatorySuccessOnEndRules = ['CheckNbConnectionOfSnappedPoint',
      'ObjectNotAllowedAtIntersection', 'StartLineSnappedOnPoint',
      'EndLineSnappedOnPoint', 'StartLineSnappedOnLine', 'EndLineSnappedOnLine',
      'MustNotSelfIntersect', 'MustNotSelfOverlap'];

    /**
     * Exécute les règles métier après création/édition de l'objet principal de la session d'édition ("onEnd")
     * @param {object} editdescription session d'édition
     * @param {object} featureType fti de l'objet principal
     * @param {ol.Map} map objet map openlayers, carte d'une application MAP
     * @param {string[]} toBypassRules nom des règles métiers "onEnd" à ne pas exécuter
     * @return {Promise} contient la string "terminé!" si succès, sinon renvoie le nom de la règle métier qui a échoué
     */
    const executeEndRules = (editdescription, featureType, map, toBypassRules = ['']) => {
      const deferred = $q.defer();

      // empêche de renvoyer deux messages d'erreur
      let hasLogged = false;

      if (!applyRules()) {
        deferred.resolve('applyRules disabled => endRules skipped!');
      } else {

        // Exécution des règles l'une après l'autre grâce à l'index incrémenté dans les appels récursifs de executeNextRule
        let i = 0;

        //Récupération de l'implémentation des règles type 'onEnd'.
        const rules = Object.assign({}, EditRulesProvider.endRules);

        // Règles à exécuter
        const promises = [];

        // nombre de règles métiers dans le fti
        // (placer la longueur du tableau appelée plusieurs fois dans une variable = gain de temps)
        const ftiRulesCount = featureType.rules.length;

        //Pour chaque config de règle de la couche 'featureType'
        // méthode interne de executeEndRules
        const executeNextRule = (i, rules, promises, ftiRulesCount) => {
          if (i < ftiRulesCount) {
            const ruleConf = featureType.rules[i];

            if (ruleConf !== undefined && isEditType(ruleConf, editdescription.editType) &&
                ruleConf.type === 'OnEnd' && !isSetRemoteAttributeOnCreation(ruleConf, editdescription)
                && !isCutIntersectingLineInTransMoveMode(ruleConf, editdescription)
                && !bypassRule(ruleConf, toBypassRules)) {

              //Récupération de l'implémentation de la règle
              const rule = rules[ruleConf.name];

              //Execution de la règle
              if (rule !== undefined) {
                console.info('rule to perform : ' + ruleConf.name);
                const promise = rule.call(this, editdescription, ruleConf, featureType, map);
                promises.push(promise);
                promise.then(
                    () => {
                      console.info('rule success : ' + ruleConf.name);
                      executeNextRule(++i, rules, promises, ftiRulesCount);
                    },
                    reason => {
                      if (ruleConf.name === 'VerifZoneSaisie') {
                        deferred.reject({
                          type: 'zoneInterdite',
                          message: reason,
                        });
                      } else {
                        console.info('rule failed : ' + ruleConf.name);
                        if (mandatorySuccessOnEndRules.includes(ruleConf.name)) {
                          deferred.reject(reason);
                        } else {
                          executeNextRule(++i, rules, promises, ftiRulesCount);
                        }
                      }
                    }
                );
              } else {
                executeNextRule(++i, rules, promises, ftiRulesCount);
              }
            } else {
              executeNextRule(++i, rules, promises, ftiRulesCount);
            }
          }
          //Fin du tableau de règles à executer.
          if (i === ftiRulesCount) {
            deferred.resolve('terminé !');
          }
          return $q.all(promises).catch(() => {
            if (!hasLogged) {
              console.error($filter('translate')('rulecfg.performEndRulesFailed'));
              hasLogged = true;
            }
          });
        };

        // executeEndRules process
        executeNextRule(i, rules, promises, ftiRulesCount).then(() => {
          if (i === ftiRulesCount) {
            deferred.resolve('terminé !');
          }
        });
      }

      return deferred.promise;
    };

    /**
     * Execution des règles de PostValidation
     * @param editdescription
     * @param featureType
     * @param map
     * @returns {*}
     */
    function executePostValidationRules(editdescription, featureType, map) {

      /**
       * Exécution récursive des règles
       * Les suivantes attendent les précédentes
       * @param i
       */
      const executeNextRule = (i) => {
        // Promesse validée à la fin du traitement de cette règle
        const thisRuleDefer = $q.defer();

        // Lorsque toutes les règles ont été traitée, on résout la promesse
        // Cela récursivement valider toutes les promesses précédentes et ainsi le premier appel
        if (i === featureType.rules.length) {
          thisRuleDefer.resolve();
          return thisRuleDefer.promise;
        }

        // Traitement de la règle
        const rule = featureType.rules[i];
        // On n'execute uniquement les règles 'OnPostValidation'
        if (rule && isEditType(rule, editdescription.editType) &&
            (rule.type === 'OnPostValidation'
                || isSetRemoteAttributeOnCreation(rule, editdescription))) {

          //Récupération de l'implémentation de la règle
          const ruleImpl = rules[rule.name];
          if (ruleImpl && typeof ruleImpl === 'function') {
            console.info('rule to perform : ' + rule.name);
            ruleImpl.call(this, editdescription, rule, featureType, map)
            .then(
                () => { console.info('rule success : ' + rule.name); },
                () => { console.warn('rule failed : ' + rule.name); }
            ).finally(() => {
              // L'appel des fonction étant asynchrone, dans ce cas,
              // on attend que la fonction suivante soit satisfaite pour valider celle-ci
              executeNextRule(i+1).finally(() => {
                thisRuleDefer.resolve();
              });
            });
            return thisRuleDefer.promise;
          } else {
            // Dans les cas ou aucun traitement n'est effectué de manière asynchrone,
            // On peut retourner directement le promesse de la règle suivante
            return executeNextRule(i+1);
          }
        } else {
          return executeNextRule(i+1);
        }
      }

      /**
       * Exécution des traitements à faire après l'exécution des règles
       * @returns {*}
       */
      const afterRulesWork = () => {
        const afterRulesWorkDone = $q.defer();
        let msg = $filter('translate')('rulecfg.confirm.confirmation_msg');
        for (let message of editdescription.messages) {
          msg = msg + '\n' + message.attribute +
              $filter('translate')('rulecfg.confirm.old') +
              message.old_value +
              $filter('translate')('rulecfg.confirm.new') +
              message.new_value;
        }

        // Fenêtre de validation pour l'exécution de certaines règles
        const popupScope = $rootScope.$new();
        popupScope.msg = msg;
        popupScope.confirmRule = () => {
            for (let message of editdescription.messages) {
              let propertyToSet = {};
              propertyToSet[message.attribute] = message.new_value;
              editdescription.editedfeature.setProperties(propertyToSet);
            }
        };
        extendedNgDialog.open({
          template: 'js/XG/widgets/mapapp/bizedition/views/ruleConfirmPopup.html',
          className: 'ngdialog-theme-plain',
          closeByDocument: false,
          closeByEscape: false,
          scope: popupScope,
          preCloseCallback: () => {
            afterRulesWorkDone.resolve();
          }
        });

        return afterRulesWorkDone.promise;
      };

      // Traitement des règles 'OnPostValidation'
      const postValidationRulesDone = $q.defer();
      featureType.rulesVariables = {};

      if (!applyRules()) {
        postValidationRulesDone.resolve('terminé !');
      } else {
        //Récupération de l'implémentation des règles type 'OnPostValidation'.
        var rules = Object.assign({}, EditRulesProvider.postValidationRules);

        // Ajoute la règle 'onEnd' SetRemoteAttribute si le fti possède une règle SetRemoteAttribute
        // ayant la case suivante cochée 'En mode “Création”, déclencher la règle sur validation de la saisie des attributs'
        const isSetRemoteAttributeFnOnPostValidation = featureType.rules.some(
            rule => rule.name === 'SetRemoteAttribute'
                && rule.parameters.isPostValidationOnCreate);
        if (isSetRemoteAttributeFnOnPostValidation) {
          rules['SetRemoteAttribute'] = EditRulesProvider.endRules.SetRemoteAttribute;
        }

        //Pour chaque config de règle de la couche 'featureType'
        if (!featureType.rules) {
          postValidationRulesDone.resolve(
              'terminé (pas de règles de postValidation) !');
        } else {
          // Execution des règles de manière récursive
          // Quand la dernière a fini de s'executer, on resout la promesse
          // et exécute un dernier traitement d'après règles (afterRulesWork)
          executeNextRule(0).finally(() => {
            if (editdescription.messages && editdescription.messages.length) {
              afterRulesWork().finally(() => {
                postValidationRulesDone.resolve();
              });
            } else {
              postValidationRulesDone.resolve();
            }
          });
        }
      }
      return postValidationRulesDone.promise;
    }


    //--------------------------------------------------------------

    function executePostSavingRules(editdescription, map, allRecs, RecsPos) {
      var i, featureType;
      var parser = new ol.format.GeoJSON();
      var deferred = $q.defer();

      if (!applyRules()) {
        deferred.resolve('terminé !');
        return deferred.promise;
      }

      //Récupération de l'implémentation des règles type 'onEnd'.
      var rules = Object.assign({}, EditRulesProvider.postSavingRules);

      //-- save originaal editedFeature
      var savedEditedFeat = editdescription.editedfeature;

      function checkPostSavingDone(allRecs, deferred) {
        var missThingsToDo = false;
        angular.forEach(allRecs, function(recordsObject) {
          if (recordsObject.adds != undefined) {
            if (recordsObject.addsDone != recordsObject.adds.features.length) {
              missThingsToDo = true;
              return;
            }
          }
          if (recordsObject.updates != undefined) {
            if (
              recordsObject.updatesDone != recordsObject.updates.features.length
            ) {
              missThingsToDo = true;
              return;
            }
          }
        });
        if (missThingsToDo !== true) {
          //-- On arrive ici si tous les objets on été traités.
          editdescription.editedfeature = savedEditedFeat;
          deferred.resolve('terminé !');
        }
      }

      function executePostSavingRules4Feature(feat, ftiUid) {
        //Pour chaque config de règle de la couche 'featureType'
        i = 0;
        featureType = FeatureTypeFactory.getFeatureByUid(ftiUid);
        editdescription.editedfeature = feat;
        return executeNextRule();
      }

      var feat,
        somethingToDo = false;

      //-- L'exécution de règle PostSaving n'a d'intérêt
      //-- que pour les objets ajoutés ou modifiés.
      let featureIds = [];
      if (allRecs) {
        featureIds = Object.keys(allRecs);
        resolveFeaturesRules(RecsPos);
      }

      function resolveFeaturesRules(RecsPos) {
        const featureUid = editdescription.fti.uid?editdescription.fti.uid:featureIds[0];
        resolveFeatureRules(allRecs[featureUid], featureUid, RecsPos).then(() => {
          if (allRecs.length) {
            allRecs.shift();
            return resolveFeaturesRules();
          }
        });
      }

      function resolveFeatureRules(recordsObject, ftiUid, RecsPos) {
        const promises = [];
        recordsObject.addsDone = recordsObject.updatesDone = 0;
        if (recordsObject.adds != undefined) {
          if (recordsObject.adds.features.length != 0) somethingToDo = true;
          let recordsObjectAdd;
          if(angular.isDefined(RecsPos) && recordsObject.adds.features.length > 0
              && recordsObject.adds.features.length >= RecsPos) {
            recordsObjectAdd = [recordsObject.adds.features[RecsPos]];
          }else{
            recordsObjectAdd = recordsObject.adds.features;
          }
          recordsObjectAdd.forEach(feature => {
            // KIS-3298: erreur à la validation dans le cas de la mise en oeuvre de la règle CutIntersectingLine
            if (feature !== undefined) {
              feat = parser.readFeature(feature);
              ++recordsObject.addsDone;

              promises.push(executePostSavingRules4Feature(feat, ftiUid).then(
                  () => checkPostSavingDone(allRecs, deferred),
                  () => checkPostSavingDone(allRecs, deferred)
              ));
            }
          });
        }
        if (recordsObject.updates != undefined) {
          if (recordsObject.updates.features.length != 0) somethingToDo = true;
          recordsObject.updates.features.forEach(feature => {
            feat = parser.readFeature(feature);
            ++recordsObject.updatesDone;

            promises.push(executePostSavingRules4Feature(feat, ftiUid).then(
              () => checkPostSavingDone(allRecs, deferred),
              () => checkPostSavingDone(allRecs, deferred)
            ));
          });
        }
        return $q.all(promises);
      }

      if (!somethingToDo) {
        //-- Pas de règle à exécuter.
        editdescription.editedfeature = savedEditedFeat;
        deferred.reject();
      }

      function executeNextRule() {
        var execNextDef = $q.defer();
        if (i < featureType.rules.length) {
          var ruleConf = featureType.rules[i];
          // Si la config de règle correspond au type 'onEnd' et au type d'édition
          if (
            ruleConf !== undefined && isEditType(ruleConf, editdescription.editType) &&
            ruleConf.type === 'PostSave'
          ) {
            //Récupération de l'implémentation de la règle
            var rule = rules[ruleConf.name];
            //Execution de la règle
            if (rule != undefined) {
              var promise = rule.call(
                this,
                editdescription,
                ruleConf,
                featureType,
                map
              );
              //promise2 renvoyé par 'then', n'est pas utilisée, et elle sera resolue à undefined à l'appel des callback car ceux-ci ne renvoient rien.
              promise.then(
                function() {
                  i++;
                  executeNextRule();
                },
                function(reason) {
                  //Si ce callback d'erreur est appelé, l'execution des règles suivantes est annulée.
                  execNextDef.reject(reason);
                }
              );
            } else {
              i++;
              executeNextRule();
            }
          } else {
            i++;
            executeNextRule();
          }
        }
        //Fin du tableau de règles à executer.
        else {
          execNextDef.resolve('terminé !');
        }
        return execNextDef.promise;
      }

      //object saved from form and sent on save
      const mySavedObject = editdescription.editedfeature.getProperties();
      deferred.resolve(mySavedObject);

      return deferred.promise;
    }

    /**
     * Rempli le tableau features des objets graphiques encodés en json, destinés à la sauvegarde.
     * @returns {undefined}
     */
    function prepareFeatures(scope) {
      /**
       * Récupére ou crée si il n'existe pas, l'objet associé à une couche et contenant les features
       *  à ajouter, mettre à jour, supprimer.
       *  adds et updates contienent une collection de Feature en Json.
       *  deletes ne contient que les id.
       *  f_XX contient les ol.Features.
       * @param {type} ftiUid
       * @returns
       */
      function getOrCreateRecordsObject(ftiUid, historicFtiUid) {
        if (allRecordsToDo[ftiUid] == undefined) {
          allRecordsToDo[ftiUid] = {
            adds: angular.copy(templatefeatcollect),
            updates: angular.copy(templatefeatcollect),
            deletes: [],
            toHistorize: angular.copy(templatefeatcollect),
            toDepose: angular.copy(templatefeatcollect),
            historicFtiUid: historicFtiUid,
            f_adds: [],
            f_updates: [],
            f_deletes: [],
            f_toHistorize: [],
            f_toDepose: [],
          };
        }
        return allRecordsToDo[ftiUid];
      }

      //Préparation des données pour la sauvegarde de la session d'édition (scope.editdescription)
      var allRecordsToDo = {};

      // Traitement de l'objet principal //
      if (
        scope.editdescription.skipRoot != true &&
        scope.editdescription.editedfeature &&
        scope.editdescription.editedfeature.saved != true
      ) {
        var recordsObject = getOrCreateRecordsObject(
          scope.editdescription.fti.uid,
          scope.editdescription.fti.historicFtiUid
        );
        if (
          scope.editdescription.preventEditGeometryOnUpdate &&
          scope.editdescription.preventEditGeometryOnUpdate == true
        )
          recordsObject.preventEditGeometryOnUpdate = true;

        var jsoneditedfeature = format.writeFeatureObject(
          scope.editdescription.editedfeature
        );
        if (jsoneditedfeature.properties == null) {
          jsoneditedfeature.properties = {};
        }
        jsoneditedfeature['fti'] = scope.editdescription.fti.uid;
        if (
          scope.editdescription.editType == EditTypesFactory.editTypes.add.name
        ) {
          recordsObject.adds.features.push(jsoneditedfeature);
          recordsObject.f_adds.push(scope.editdescription.editedfeature);
        } else if (scope.editdescription.editType === EditTypesFactory.editTypes.update.name
            || scope.editdescription.editType === EditTypesFactory.editTypes.updateattributes.name
            || scope.editdescription.editType === EditTypesFactory.editTypes.reverse.name
        ) {
          recordsObject.updates.features.push(jsoneditedfeature);
          recordsObject.f_updates.push(scope.editdescription.editedfeature);
        }
        //Pour la suppression
        else if (
          scope.editdescription.editType ==
          EditTypesFactory.editTypes.delete.name
        ) {
          var deleteFeatureId = AdvancedEditionFactory.getObjectId(
            jsoneditedfeature
          );
          if (deleteFeatureId != -1) {
            recordsObject.deletes.push(deleteFeatureId);
            recordsObject.f_deletes.push(scope.editdescription.editedfeature);
          }
        }

        //Historisation eventuelle
        else if (
          scope.editdescription.editType ==
          EditTypesFactory.editTypes.tohistorize.name
        ) {
          recordsObject.toHistorize.features.push(jsoneditedfeature);
          recordsObject.f_toHistorize.push(scope.editdescription.editedfeature);
        } else if (
          scope.editdescription.editType ==
          EditTypesFactory.editTypes.todepose.name
        ) {
          recordsObject.toDepose.features.push(jsoneditedfeature);
          recordsObject.f_toDepose.push(scope.editdescription.editedfeature);
        }
      }
      // Traitement des objets secondaires //
      for (var i = 0; i < scope.editdescription.relatedfeatures.length; i++) {
        var related = scope.editdescription.relatedfeatures[i];
        var feature = related.feature;
        //On ne sauve pas le feature si celui-ci est marqué comme étant dejà sauvegardé.
        if (feature.saved == true) continue;

        var ftuid = related.fti.uid;
        jsoneditedfeature = format.writeFeatureObject(feature);
        jsoneditedfeature['fti'] = ftuid;
        if (jsoneditedfeature.properties == null) {
          jsoneditedfeature.properties = {};
        }
        var recordsObject = getOrCreateRecordsObject(
          ftuid,
          related.historicFtiUid
        );

        if (
          related.preventEditGeometryOnUpdate &&
          related.preventEditGeometryOnUpdate == true
        )
          recordsObject.preventEditGeometryOnUpdate = true;

        if (related.editType == EditTypesFactory.editTypes.add.name) {
          recordsObject.adds.features.push(jsoneditedfeature);
          recordsObject.f_adds.push(feature);
        } else if (related.editType == EditTypesFactory.editTypes.update.name
            || related.editType == EditTypesFactory.editTypes.updateattributes.name
            || related.editType === EditTypesFactory.editTypes.reverse.name) {
          recordsObject.updates.features.push(jsoneditedfeature);
          recordsObject.f_updates.push(feature);
        }
        //Pour la suppression, voir comment récupérer l'identifiant du feature
        else if (related.editType == EditTypesFactory.editTypes.delete.name) {
          var deleteFeatureId = AdvancedEditionFactory.getObjectId(
            jsoneditedfeature
          );
          if (deleteFeatureId != -1) {
            recordsObject.deletes.push(deleteFeatureId);
            recordsObject.f_deletes.push(feature);
          }
        }
        //Historisation eventuelle
        else if (
          related.editType == EditTypesFactory.editTypes.tohistorize.name
        ) {
          recordsObject.toHistorize.features.push(jsoneditedfeature);
          recordsObject.f_toHistorize.push(feature);
        } else if (
          related.editType == EditTypesFactory.editTypes.todepose.name
        ) {
          recordsObject.toDepose.features.push(jsoneditedfeature);
          recordsObject.f_toDepose.push(feature);
        }
      }

      return allRecordsToDo;
    }

    function simpleGetFTIApplyEditsContainer(applyEditsData, ftiUID) {
      for (var index in applyEditsData) {
        if (applyEditsData[index].fti == ftiUID) return applyEditsData[index];
      }

      return null;
    }

    function getFTIApplyEditsContainer(applyEditsData, ftiUID) {
      for (var index in applyEditsData) {
        if (applyEditsData[index].fti == ftiUID) return applyEditsData[index];
      }

      var fTIApplyEditsContainer = { fti: ftiUID };
      applyEditsData.push(fTIApplyEditsContainer);
      return fTIApplyEditsContainer;
    }


    function performApplyEdits(scope) {
      var srid = scope.map
        .getView()
        .getProjection()
        .getCode();

      scope.allRecords = prepareFeatures(scope);

      var applyEditsData = [];
      var applyEditsDataFeatures = [];

      angular.forEach(scope.allRecords, function(value, key) {
        //key => ftiUid
        //value => recordsObject
        var recordsObject = value;
        var ftiUid = key;

        var fTIApplyEditsContainer = getFTIApplyEditsContainer(
          applyEditsData,
          ftiUid
        );
        if (
          recordsObject.preventEditGeometryOnUpdate &&
          recordsObject.preventEditGeometryOnUpdate == true
        )
          fTIApplyEditsContainer.preventEditGeometryOnUpdate = true;

        var fTIApplyEditsContainerFeatures = getFTIApplyEditsContainer(
          applyEditsDataFeatures,
          ftiUid
        );
        if (
          recordsObject.adds != undefined &&
          recordsObject.adds.features.length > 0
        ) {
          fTIApplyEditsContainer.adds = recordsObject.adds;
          fTIApplyEditsContainerFeatures.f_adds = recordsObject.f_adds;
        }
        if (
          recordsObject.updates != undefined &&
          recordsObject.updates.features.length > 0
        ) {
          fTIApplyEditsContainer.updates = recordsObject.updates;
          fTIApplyEditsContainerFeatures.f_updates = recordsObject.f_updates;
        }
        if (
          recordsObject.deletes != undefined &&
          recordsObject.deletes.length > 0
        ) {
          fTIApplyEditsContainer.deletes = recordsObject.deletes;
          fTIApplyEditsContainerFeatures.f_deletes = recordsObject.f_deletes;
        }
        if (
          recordsObject.toHistorize != undefined &&
          recordsObject.toHistorize.features.length > 0
        ) {
          fTIApplyEditsContainer.archives = recordsObject.toHistorize;
          fTIApplyEditsContainerFeatures.f_archives =
            recordsObject.f_toHistorize;
        }
        if (
          recordsObject.toDepose != undefined &&
          recordsObject.toDepose.features.length > 0
        ) {
          fTIApplyEditsContainer.deposes = recordsObject.toDepose;
          fTIApplyEditsContainerFeatures.f_deposes = recordsObject.f_toDepose;
        }
      });

      var lotCallBackSuccess = getLotCallbackSuccess(
        applyEditsData,
        applyEditsDataFeatures,
        scope.fieldDatas
      );
      var lotCallBackError = getLotCallbackError(applyEditsData);
      console.log(applyEditsData);
      if (applyEditsData.length == 0) return 'EMPTY';
      var promise = AdvancedEditionFactory.applyEdits(
        { items: applyEditsData },
        srid
      ).then(lotCallBackSuccess, lotCallBackError);

      function releaseAllEdits(applyEditsData) {
        /* for(var index in applyEditsData)
                 {
                     var ftiUID = applyEditsData[index].fti.uid;
                     delete allRecordsToDo[ftiUID];
                 }*/
      }



      /**
       * Extrait le FeatureTypeInfo et la feature des données envoyées
       * lors du apply dans le cas d'une mise à jour. Ceci permet de récupérer
       * dans le cas d'un id autre que objectid de récupérer la valeur
       * à utiliser pour le document attaché.
       *
       * @param {*} applyData : opérations appliquer (sur géodb arcgis
       *                        via arcgisserver)
       * @param {*} featId : identifant au sens geotools de l'objet modifié
       *                     auquel on joint un document
       * @returns
       */
      let getFtiAndFeatFrom = (applyData, featId) => {
        let iData, iFeat;
        for (iData = 0; iData < applyData.length; iData++) {
          if (applyData[iData].updates) {
            for (iFeat = 0; iFeat < applyData[iData].updates.features.length; iFeat++) {
              if (applyData[iData].updates.features[iFeat].id == featId) {
                return {
                  feature: applyData[iData].updates.features[iFeat],
                  fti: FeatureTypeFactory.getFeatureByUid(applyData[iData].fti)
                };
              }
            }
          }
        }
        //-- Ne devrait pas se produire.
        return null;
      };


      /**
       * Ajoute les piéces attachés aux objets mmis à jour.
       * Les suppressions ne sont pas prises en comptes : on ne supprime
       * pas un document joint qui n'est plus dans le champ de type
       * gcattachment ou gcattachments !
       *
       * @param {*} applyData  : opérations appliquer (sur géodb arcgis
       *                        via arcgisserver)
       * @param {*} applyUpdates : liste des features openlayers (ou geojson,
       *                            à vérifer) envoyé dans le applyEdits
       * @param {*} respUpdate : résultat des updates pour l'apply
       *                           (liste des id dont objet mis à jour)
       */
      let attachmentFilesForUpdates = (applyData, applyUpdates, respUpdate) => {
        let ftiFeat, id;
        if (applyUpdates != undefined && applyUpdates.features.length > 0) {
          if (respUpdate.length == applyUpdates.features.length) {
            for (var i = 0; i < respUpdate.length; i++) {
              ftiFeat = getFtiAndFeatFrom(applyData, respUpdate[i].id);
              id = gaJsUtils.getIdInCaseEsriId(ftiFeat.feature, ftiFeat.fti, null);
              gceditsaveAttachmentFactory.uploadAttachmentFiles(
                scope, ftiFeat.fti.name, id
              );
            }
          }
        }
      };


      function getLotCallbackSuccess(applyEditsData, applyEditsDataFeatures, fieldDatas) {
        return function(result) {
          var data = result.data;
          if (data == null || data == '') {
            console.error('Error de sauvegarde data is null');
            return;
          }
          var realData = data.data;
          if (angular.isDefined(realData) && realData != null) {
            if (!angular.isArray(realData)) {
              if (
                angular.isDefined(realData.errors) &&
                realData.errors.length > 0
              )
                console.error(
                  'Sauvegarde de type lot non effectuée !\n Cause:' +
                    realData.errors[0]
                );
              return;
            }
            for (var index in realData) {
              var dataItem = realData[index];
              var ftiUid = dataItem.ftiUID;
              var dataItemResponse = dataItem.response;
              if (angular.isDefined(dataItemResponse)) {
                var fTIApplyEditsContainer = simpleGetFTIApplyEditsContainer(
                  applyEditsData,
                  ftiUid
                );

                var fTIApplyEditsContainerFeatures = simpleGetFTIApplyEditsContainer(
                  applyEditsDataFeatures,
                  ftiUid
                );
                if (fTIApplyEditsContainer && fTIApplyEditsContainerFeatures) {
                  if (
                    fTIApplyEditsContainer.adds != undefined &&
                    fTIApplyEditsContainer.adds.features.length > 0
                  ) {
                    if (
                      dataItemResponse.create.length === fTIApplyEditsContainer.adds.features.length
                    ) {
                      //Marquage sale de chaque ol.Feature comme enregistré avec succès.
                      // "sale" car la propriété est sur la feature et non dans l'objet "properties"
                      // de la feature car le concepteur n'a pas pris soin d'utiliser une méthode ol
                      setSaveValidatedMarker(fTIApplyEditsContainerFeatures.f_adds);

                      // KIS-2877 on subit ici une des malfaçons d'écriture initiale du code spécifique Esri:
                      // - l'enregistrement des objets édités dans EditRulesFactory au lieu de bizeditsave comme dans le cas générique postgis.
                      bizeditProvider.copyFilesFromUploadToAttachmentFolder(dataItemResponse.create, 0, fieldDatas);

                      // KIS-3135 (cas Esri):  l’enregistrement des objets associés se fait après enregistrement de l’objet édité
                      // et une fois que son identifiant est récupéré
                      // Par conception, la session de mise à jour ne concerne toujours qu'un seul objet
                      // => les objets cibles sont définis dans la session d'édition au lieu d'être définis pour chaque objet de la session
                      bizEditSaveAssociatedFeatures(dataItemResponse.create, scope.editdescription);
                    }
                  }
                  if (fTIApplyEditsContainer.hasOwnProperty('updates')
                      && Array.isArray(fTIApplyEditsContainer.updates.features)
                      && fTIApplyEditsContainer.updates.features.length > 0
                      && Array.isArray(dataItemResponse.update)
                      && dataItemResponse.update.length === fTIApplyEditsContainer.updates.features.length) {

                    // Par conception, la session de mise à jour ne concerne toujours qu'un seul objet
                    // => les objets cibles sont définis dans la session d'édition au lieu d'être définis pour chaque objet de la session
                    bizEditReplaceAssociatedFeatures(dataItemResponse.update, scope.editdescription);
                  }

                  if (
                    fTIApplyEditsContainer.archives != undefined &&
                    fTIApplyEditsContainer.archives.features.length > 0
                  ) {
                    if (
                      dataItemResponse.historic.length ==
                      fTIApplyEditsContainer.archives.features.length
                    ) {
                      setSaveValidatedMarker(
                        fTIApplyEditsContainerFeatures.f_archives
                      );
                    }
                  }

                  if (
                    fTIApplyEditsContainer.deposes != undefined &&
                    fTIApplyEditsContainer.deposes.features.length > 0
                  ) {
                    if (
                      dataItemResponse.depose.length ==
                      fTIApplyEditsContainer.deposes.features.length
                    ) {
                      setSaveValidatedMarker(
                        fTIApplyEditsContainerFeatures.f_deposes
                      );
                    }
                  }

                  if (
                    fTIApplyEditsContainer.deletes != undefined &&
                    fTIApplyEditsContainer.deletes.length > 0
                  ) {
                    if (
                      dataItemResponse.delete.length ==
                      fTIApplyEditsContainer.deletes.length
                    ) {
                      setSaveValidatedMarker(
                        fTIApplyEditsContainerFeatures.f_deletes
                      );
                    }
                  }

                  gclayers.refreshlayerByid(ftiUid, scope.map);
                  if (dataItemResponse.errors.length > 0) {
                    //require('toastr').info("Certaines sauvegardes non effectuées !\n Cause:"+ data.errors[0]);
                    console.error(
                      'Sauvegarde de type lot pour la couche uid:' +
                        ftiUid +
                        ' non effectuée !\n Cause:' +
                        dataItemResponse.errors[0]
                    );
                  }
                }
              }
            }
          }

          releaseAllEdits(applyEditsData);
        };
      }

      function getLotCallbackError(applyEditsData) {
        return function(result) {
          gcRestrictionProvider.showDetailsErrorMessage(result);
          console.error('Erreur de sauvegarde de type lot ,response:' + result);
          releaseAllEdits(applyEditsData);
        };
      }

      function setSaveValidatedMarker(features) {
        for (var i = 0; i < features.length; i++) {
          features[i].saved = true;
        }
      }

      return promise;
    }

    /**
     * [save description]
     * @return {Promise} [description]
     */
    function performSaving(scope) {
      var defer = $q.defer();
      var srid = scope.map
        .getView()
        .getProjection()
        .getCode();
      var promises = [];
      var adet = 0;

      scope.allRecords = prepareFeatures(scope);
      //Tableau de promesses permettant de savoir quand toutes les sauvegardes seront terminées.

      //Pour chaque couche, enregistrement des features ajoutés, mis à jour et supprimés
      angular.forEach(scope.allRecords, function(value, key) {
        //key => ftiUid
        //value => recordsObject
        var recordsObject = value;
        var ftiUid = key;

        if (
          recordsObject.adds != undefined &&
          recordsObject.adds.features.length > 0
        ) {
          var callBackSuccess = getCallbackSuccess(
            recordsObject,
            ftiUid,
            'adds'
          );
          var callBackError = getCallbackError(recordsObject, ftiUid, 'adds');

          var promise2 = EditFactory.add(ftiUid, recordsObject.adds, srid).then(
            callBackSuccess,
            callBackError
          );

          //promise2 sera resolue lorsque un des callBalck aura fini son traitement de la réponse.
          promises.push(promise2);
        }
        if (
          recordsObject.updates != undefined &&
          recordsObject.updates.features.length > 0
        ) {
          var callBackSuccess = getCallbackSuccess(
            recordsObject,
            ftiUid,
            'updates'
          );
          var callBackError = getCallbackError(
            recordsObject,
            ftiUid,
            'updates'
          );

          var promise2 = EditFactory.update(
            ftiUid,
            recordsObject.updates,
            srid
          ).then(callBackSuccess, callBackError);

          promises.push(promise2);
        }
        if (
          recordsObject.deletes != undefined &&
          recordsObject.deletes.length > 0
        ) {
          var callBackSuccess = getCallbackSuccess(
            recordsObject,
            ftiUid,
            'deletes'
          );
          var callBackError = getCallbackError(
            recordsObject,
            ftiUid,
            'deletes'
          );

          var promise2 = EditFactory.remove(ftiUid, recordsObject.deletes).then(
            callBackSuccess,
            callBackError
          );

          promises.push(promise2);
        }
        if (
          recordsObject.toHistorize != undefined &&
          recordsObject.toHistorize.features.length > 0
        ) {
          var callBackSuccess = getCallbackSuccess(
            recordsObject,
            ftiUid,
            'toHistorize'
          );
          var callBackError = getCallbackError(
            recordsObject,
            ftiUid,
            'toHistorize'
          );
          var promise2 = EditFactory.historicize(
            ftiUid,
            recordsObject.toHistorize,
            srid
          ).then(callBackSuccess, callBackError);

          promises.push(promise2);
        }
        if (
          recordsObject.toDepose != undefined &&
          recordsObject.toDepose.features.length > 0
        ) {
          var callBackSuccess = getCallbackSuccess(
            recordsObject,
            ftiUid,
            'toDepose'
          );
          var callBackError = getCallbackError(
            recordsObject,
            ftiUid,
            'toDepose'
          );
          var promise2 = EditFactory.depose(
            ftiUid,
            recordsObject.toDepose,
            srid,
            'depose'
          ).then(callBackSuccess, callBackError);

          promises.push(promise2);
        }
      });

      function getCallbackSuccess(recordsObject, ftiUid, recordsType) {
        return function(result) {
          var data = result.data;

          //Si la reponse n'est pas un objet du service d'édition, il y a une erreur
          if (data.create == undefined) {
            var msg =
              'Erreur serveur: ' +
              data.message +
              ', details:' +
              data.details[0];
            require('toastr').error(msg);
            console.error(msg);
            return;
          }

          if (recordsType == 'adds') {
            if (data.create.length == recordsObject.adds.features.length) {
              //Marquage de chaque ol.Feature comme enregistré avec succès.
              setSaveValidatedMarker(recordsObject.f_adds);
              //Récupération des features sauvées avec un id affecté, encodés en json.
              //Si les features ne contienent pas les id, c dernier se trouve quand meme dans data.create[i]
              recordsObject.adds.features = [];
              for (var i = 0; i < data.create.length; i++) {
                var jsonFeature = undefined;
                try {
                  jsonFeature = JSON.parse(data.create[i].json);
                } catch (e) {
                  console.error(
                    'Error de parsing de result.data.create:' + data.create[i],
                    e
                  );
                }
                recordsObject.adds.features.push(jsonFeature);

                // @RB .... upload file !
                gceditsaveAttachmentFactory.uploadAttachmentFiles(
                  scope,
                  scope.selectfti.name,
                  data.create[i]['id']
                );
              }
            }
          } else if (recordsType == 'updates') {
            if (data.update.length == recordsObject.updates.features.length) {
              setSaveValidatedMarker(recordsObject.f_updates);

              for (var i = 0; i < data.update.length; i++) {
                // @RB .... upload file !

                gceditsaveAttachmentFactory.uploadAttachmentFiles(
                  scope,
                  scope.selectfti.name,
                  data.update[i]
                );
              }
            }
          } else if (recordsType == 'deletes') {
            if (data.delete.length == recordsObject.deletes.length) {
              setSaveValidatedMarker(recordsObject.f_deletes);
            }
          } else if (recordsType == 'toHistorize') {
            if (
              data.historic.length == recordsObject.toHistorize.features.length
            ) {
              setSaveValidatedMarker(recordsObject.f_toHistorize);
            }
          } else if (recordsType == 'toDepose') {
            //Comparer les id des features pour vérifier plus précisement lesquels ont bien été sauvés.
            if (data.depose.length == recordsObject.toDepose.features.length) {
              setSaveValidatedMarker(recordsObject.f_toDepose);
            }
          }
          gclayers.refreshlayerByid(ftiUid, scope.map);
          if (data.errors.length > 0) {
            //require('toastr').info("Certaines sauvegardes non effectuées !\n Cause:"+ data.errors[0]);
            console.error(
              'Sauvegarde de type ' +
                recordsType +
                ' pour la couche uid:' +
                ftiUid +
                ' non effectuée !\n Cause:' +
                data.errors[0]
            );
          }
        };
      }

      function setSaveValidatedMarker(features) {
        for (var i = 0; i < features.length; i++) {
          features[i].saved = true;
        }
      }

      function getCallbackError(recordsObject, ftiUid, recordsType) {
        return function(result) {
          gcRestrictionProvider.showDetailsErrorMessage(result);
          console.error(
            'Erreur de sauvegarde de type ' +
              recordsType +
              ' pour la couche uid:' +
              ftiUid +
              ' ,response:' +
              result
          );
        };
      }

      //Renvoi une promesse qui sera résolue lorsque toutes les promesses du tableau seront résolues;
      return $q.all(promises);
    } //end performSaving

    function updateRelatedFeatures(scope, relationsData, data) {
      var parser = new ol.format.GeoJSON();

      if (scope.editdescription.relatedfeatures == undefined) {
        scope.editdescription.relatedfeatures = [];
        scope.editdescription.relatedfeatures0 = [];
      }

      angular.forEach(relationsData, function(relationDataContainer, key) {
        var relationName = key;
        var relationData = relationDataContainer.data;
        var relFti = FeatureTypeFactory.getFeatureByUid(
          relationDataContainer.ftiuid
        );
        relationData.forEach(function(relationDataEntry) {
          var olFeature;
          if (relFti.typeInfo == 'NG') {
            olFeature = new ol.Feature();
            olFeature.setProperties(
              relationDataEntry.relatedFeature.properties
            );
          } else olFeature = parser.readFeature(relationDataEntry.relatedFeature);

          olFeature.attrEdited = true;
          olFeature.saved = false;

          if ('HOLD' != relationDataEntry.action)
            scope.editdescription.relatedfeatures.push({
              feature: olFeature,
              editType: relationDataEntry.action.toLowerCase(),
              fti: relFti,
            });
        });
      });

      angular.forEach(data, function(dataEntry, key) {
        var ftiUid = key;
        var fti = FeatureTypeFactory.getFeatureByUid(ftiUid);
        dataEntry.forEach(function(dataItem) {
          if (fti.typeInfo == 'NG' && dataItem.feature.geometry)
            delete dataItem.feature.geometry;

          var olFeature = parser.readFeature(dataItem.feature);

          olFeature.attrEdited = true;
          olFeature.saved = false;

          scope.editdescription.relatedfeatures.push({
            feature: olFeature,
            editType: dataItem.action.toLowerCase(),
            fti: fti,
          });
        });
      });
    }
    function save(scope) {
      scope.saveinProgress = true;
      var savePromise = performSaving(scope);

      savePromise.then(
        function(res) {
          scope.saveinProgress = false;
          //Une fois les sauvegardes terminées, lancement des règles post-sauvegarde.
          var promise = executePostSavingRules(
            scope.editdescription,
            scope.map
          );
          promise.then(function() {
            //TODO: Sauvegarde des features mis à jour par les règles post-sauvegarde.
            //(reprendre les feature add et update et effectuer des updates)

            //Règles post-sauvegarde terminées.
            scope.endsave(scope.allRecords);
          });
        },
        function() {
          scope.saveinProgress = false;
        }
      );
      return savePromise;
    } // end save

    function applyEdits(scope) {
      let def = $q.defer();
      scope.saveinProgress = true;
      var applyEditsResult = performApplyEdits(scope);
      if ('EMPTY' == applyEditsResult) {
        let def = $q.defer();
        def.resolve({ status: applyEditsResult, type: 'esri' });
        return def.promise;
      }
      else {
        var applyEditsPromise = applyEditsResult;
        applyEditsPromise.then(
          () => {
            scope.saveinProgress = false;
            //-- Une fois les sauvegardes terminées,
            //-- lancement des règles post - sauvegarde.
            var promise = executePostSavingRules(
              scope.editdescription,
              scope.map, scope.allRecords
            );
            promise.then(
              () => {
                //TODO: Sauvegarde des features mis à jour
                // par les règles post - sauvegarde.
                //(reprendre les feature add et update et effectuer des updates)
                //Règles post-sauvegarde terminées.
                scope.endsave(scope.allRecords);
                def.resolve({ status: 'done' , type: 'esri'});
              },
              () => {
                def.reject();
              });
          },
          () => {
            scope.saveinProgress = false;
            def.reject();
          }
        );
        return def.promise;
      }
    } // end applyEdits

    function hasRuleMoveExtremityLines(fti) {
      var r, ind;

      r = fti.rules;
      for (ind = 0; ind < r.length; ind++)
        if (r[ind].name == 'MoveExtremityLines') return true;
      return false;
    }

    function enableRules(enable) {
      erfScope.rulesEnabled = enable;
    }

    function applyRules() {
      return erfScope.rulesEnabled;
    }

    function getRuleTypesOrder() {
      return ['OnInit', 'OnStart', 'OnPostValidation', 'PostSave', 'OnEnd'];
    }

    function getRulesVariablesErrors(rules = []) {
      const errorMessages = [];
      const definedVariables = {};
      const rulesTypesOrder = getRuleTypesOrder();
      rules.sort((a, b) => (rulesTypesOrder.indexOf(a.type) >= rulesTypesOrder.indexOf(b.type))
        ? 0
        : -1
      ).forEach(rule => {
        if (rule.parameters.variableName) {
          definedVariables[rule.parameters.variableName] = rule.editTypes.map(editType => editType.name);
        }
        const variableNames = (rule.parameters.formula && rule.parameters.formula.numerotation.parts || [])
          .filter(part => part.key === 'variable').map(part => part.format);
        return variableNames.forEach(variableName => {
          if (!definedVariables[variableName]) {
            errorMessages.push(`Variable "${variableName}" n'est pas définie au moment de l'utilisation !`);
          }
          if (
            definedVariables[variableName] &&
            rule.editTypes.filter(editType => definedVariables[variableName].indexOf(editType.name) !== -1).length !== rule.editTypes.length
          ) {
            errorMessages.push(`La variable "${variableName}" n'est pas définie pour tous les types d'édition pour lesquels elle est utilisée !`);
          }
        });
      });
      return errorMessages;
    }

    /**
     * Vérifie si le mode d'édition est la création, si la règle fournie porte le nom "SetRemoteAttribute"
     * et si la règle possède le paramètre booléen isPostValidationOnCreate égal à true
     * @param rule règle métier onEnd ou postValidation
     * @param editdescription objet de l'édition
     * @return {boolean} true si la règle fournie est "SetRemoteAttribute" en mode création avec le paramètre isPostValidationOnCreate égal à true
     */
    const isSetRemoteAttributeOnCreation = (rule, editdescription) => {
      return editdescription.editType === 'add' && rule.name === 'SetRemoteAttribute' && rule.parameters.isPostValidationOnCreate;
    };

    /**
     * Vérifie si la règle métier porte le nom de "CutIntersectingLine"
     * dans le cas d'un déplacement en mode transition.
     * En effet, dans le cas d'un déplacement en mode transition, le split (ou cutting de ligne) est déjà calculé,
     * il ne faut donc pas le refaire
     * @param {object} ruleConf configuration de la règle métier présente dans le tableau de règles du fti
     * @param {object} editdescription session d'édition
     * @return {boolean} true si la règle métier courante est "CutIntersectingLine" dans le cas d'un déplacement en mode transition
     */
    const isCutIntersectingLineInTransMoveMode = (ruleConf, editdescription) => {
      return ruleConf.name === 'CutIntersectingLine' && editdescription.hasOwnProperty(
          'currentToolName') && editdescription.currentToolName === 'transModeMove';
    };

    /**
     * Vérifie la présence de la règle métier dans un tableau de noms de règles métier à exclure de l'exécution
     * @param {object} ruleConf configuration de la règle métier présente dans le tableau de règles du fti
     * @param {string[]} toBypassRules noms des règles métiers à ne pas prendre en compte dans le traitement
     * @return {boolean} true si la règle métier est présente dans le tableau de règles à exclure.
     */
    const bypassRule = (ruleConf, toBypassRules) => {
      return Array.isArray(toBypassRules) && toBypassRules.includes(ruleConf.name);
    };

    /**
     * Vérifie que le type d'édition de la session soit inclus dans les types d'édition configurés de la règle métier
     * @param {object} ruleConf configuration de la règle métier présente dans le tableau de règles du fti
     * @param {string} editType type d'édition courant de la session d'édition (ex. "add", "update", "updateattributes", ...)
     * @return {boolean} true si le type d'édition de la session doit exécuter la règle métier
     */
    const isEditType = (ruleConf, editType) => {
      return Array.isArray(ruleConf.editTypes) && ruleConf.editTypes.some(ruleEditType => ruleEditType.name === editType);
    };

    /**
     * Sauvegarde les associations d'un objet créé à la suite d'une session d'édition métier
     * @param objectsCreated tableau des objets créés avec leur id
     * @param editDescription objet en cours d'édition
     */
    const bizEditSaveAssociatedFeatures = (objectsCreated, editDescription) => {
      if (objectsCreated.length === 1 && editDescription.objectCibleToAdd
          && Array.isArray(editDescription.objectCibleToAdd.features)
          && editDescription.objectCibleToAdd.features.length > 0
          && gaJsUtils.notNullAndDefined($rootScope.xgos, 'portal.parameters.mainDB')) {
        const mainDb = $rootScope.xgos.portal.parameters.mainDB;
        const sendData = {
          'associationToLoad': editDescription.associationToLoad,
          'objectCibleToAdd': editDescription.objectCibleToAdd.features
        };
        AssociationFactory.addObjectsCibles(mainDb, objectsCreated[0].id, sendData).then(
            res => {
              editDescription.associationToLoad = null;
              editDescription.objectCibleToAdd = null;
            }
        );
      }
    };

    /**
     * Met à jour les associations d'un objet créé à la suite d'une session d'édition métier
     * @param objectsUpdated tableau des objets mis à jour avec leur id
     * @param editDescription objet en cours d'édition
     */
    const bizEditReplaceAssociatedFeatures = (objectsUpdated, editDescription) => {
      if (Array.isArray(objectsUpdated) && objectsUpdated.length === 1 && editDescription.objectCibleToAdd !== null
          && Array.isArray(editDescription.objectCibleToAdd.features)
          && editDescription.objectCibleToAdd.features.length > 0
          && gaJsUtils.notNullAndDefined($rootScope.xgos, 'portal.parameters.mainDB')) {
        const mainDb = $rootScope.xgos.portal.parameters.mainDB;
        // Erreur de conception:
        // les objets cibles sont définis dans la session d'édition au lieu d'être définis pour chaque objet de la session
        const sendData = {
          associationToLoad: editDescription.associationToLoad,
          toAddFeatures: editDescription.objectCibleToAdd.features
        };
        AssociationFactory.replaceAllObjectCibles(mainDb, objectsUpdated[0].id, sendData).then(
            () => {
              console.info("Objets cibles de l'objet " + objectsUpdated[0].id + " mis à jour");
            }
        );
      }
    };

    return {
      Edit: Edit,
      numericPattern: numericPattern,
      executeInitRules: executeInitRules,
      executeStartRules: executeStartRules,
      executeEndRules: executeEndRules,
      executePostValidationRules: executePostValidationRules,
      executePostSavingRules: executePostSavingRules,
      performSaving: performSaving,
      updateRelatedFeatures: updateRelatedFeatures,
      save: save,
      applyEdits: applyEdits,
      hasRuleMoveExtremityLines: hasRuleMoveExtremityLines,
      enableRules: enableRules,
      applyRules:applyRules,
      getRuleTypesOrder,
      getRulesVariablesErrors,
      bizEditSaveAssociatedFeatures: bizEditSaveAssociatedFeatures
    };
  };
  EditRulesFactory.$inject = [
    '$q',
    'EditRulesProvider',
    'EditFactory',
    '$filter',
    'EditTypesFactory',
    'gclayers',
    'AdvancedEditionFactory',
    'gcRestrictionProvider',
    'FeatureTypeFactory',
    'gceditsaveAttachmentFactory',
    'gaJsUtils',
    'bizeditProvider',
    '$rootScope',
    'extendedNgDialog',
    'AssociationFactory'
  ];
  return EditRulesFactory;
});
