/**
 *
 */
'use strict';
define(function() {
  var gcformfunction = function() {
    this.$get = function(
      $timeout,
      ngDialog,
      extendedNgDialog,
      ConfigFactory,
      $rootScope,
      FunctionFactory,
      EditFactory,
      gclayers,
      $sce,
      $q,
      QueryFactory,
      gaJsUtils,
      gaDomUtils,
      mapJsUtils,
      dateUtils,
      DocumentFactory,
      $filter,
      kisGeocodageFactory,
      SelectManager,
      GeometryFactory,
      $http,
      CalendarFactory,
      x2js,
      dolibarrFactory,
      AncAppFactory,
      BacAppFactory,
      defaultFiltersFactory,
      ParametersFactory,
      FeatureTypeFactory,
      networkfunctions,
      editRulesFunctions,
      snapshotfunction,
      setDataFromCollectionFunction,
      getFeaturesIntersectingFunction,
      gcPopup,
      setCountFromCollectionFunction,
      attributeRestrictionsUtils,
      transferLayerFunctions,
      setCurrentDateFunction,
      maintaSave2Function,
      formFunctionUtils, ogcFactory
    ) {
      let marker;

      let removeMarker = (map) => {
        if (marker !== null) {
          map.removeLayer(marker);
          marker = null;
        }
      };

      /**
       *
       * @param res
       * @param object
       * @param variable_destination
       * @param object_destination
       */
      var getAdresseGeocoder = function(
        res,
        object,
        variable_destination,
        object_destination
      ) {
        if (res.data.features.length == 0) {
          require('toastr').error('Rien trouvé !');
        } else {
          if (object_destination.attributes == 'unique') {
            var attname = object_destination.value.attribute.replace(/'/g, '');
            object[variable_destination].properties[attname] =
              res.data.features[0].properties.label;
          } else if (object_destination.attributes == 'multiple') {
            var atts = ['housenumber', 'street', 'city', 'postcode'];
            atts.forEach(function(att) {
              if (object_destination.value.attributes[att].checked) {
                var value = object_destination.value.attributes[
                  att
                ].value.replace(/'/g, '');
                if (res.data.features[0].properties[att]) {
                  object[variable_destination].properties[value] =
                    res.data.features[0].properties[att];
                }
              }
            });
          }
        }
      };

      /**
       *
       * @param objects
       * @param idgeocoder
       * @param source
       * @param variable_destination
       * @param attribut_destination
       * @returns {*}
       */
      var kisgeocodagereversegeometry = function(
        objects,
        idgeocoder,
        source,
        variable_destination,
        attribut_destination,
        def
      ) {
        if (
          !objects.res[source] ||
          !objects.res[source].geometry ||
          !objects.res[source].geometry.coordinates
        ) {
          require('toastr').error(
            $filter('translate')('kis_geocoder_picker.vide')
          );
          return false;
        }
        var coords = objects.res[source].geometry.coordinates;
        var feature = new ol.Feature({});
        var geom;
        if (objects.res[source].geometry.type == 'Point') {
          geom = new ol.geom.Point(coords);
        } else if (objects.res[source].geometry.type == 'LineString') {
          geom = new ol.geom.LineString(coords);
        } else if (objects.res[source].geometry.type == 'Polygon') {
          geom = new ol.geom.Polygon(coords);
        }
        feature.setGeometry(geom);
        var geometry = feature.getGeometry();
        var extent = geometry.getExtent();
        var center = ol.extent.getCenter(extent);

        center = ol.proj.transform(
          center,
          objects.map
            .getView()
            .getProjection()
            .getCode(),
          'EPSG:4326'
        );

        return kisGeocodageFactory
          .reverse(
            kisGeocodageFactory.getGeocoderConfigFromId(idgeocoder).url,
            center
          )
          .then(function(res) {
            console.log(res.data);
            def.resolve();
            getAdresseGeocoder(
              res,
              objects.res,
              variable_destination,
              attribut_destination
            );
          });
      };

      /**
       *
       * @param object
       * @param idgeocoder
       * @param variable_source
       * @param attribut_source_lon
       * @param attribut_source_lat
       * @param variable_destination
       * @param object_destination
       * @returns {*}
       */
      var kisgeocodagereverse = function(
        object,
        idgeocoder,
        variable_source,
        attribut_source_lon,
        attribut_source_lat,
        variable_destination,
        object_destination,
        def
      ) {
        attribut_source_lon = 'properties.' + attribut_source_lon;
        attribut_source_lat = 'properties.' + attribut_source_lat;

        var lon = gaJsUtils.checkNestedProperty(
          attribut_source_lon,
          object[variable_source]
        );
        var lat = gaJsUtils.checkNestedProperty(
          attribut_source_lat,
          object[variable_source]
        );
        if (lon == false || lat == false) {
          require('toastr').error(
            $filter('translate')('kis_geocoder_picker.vide')
          );
        }
        return kisGeocodageFactory
          .reverse(
            kisGeocodageFactory.getGeocoderConfigFromId(idgeocoder).url,
            [lon, lat]
          )
          .then(function(res) {
            def.resolve();
            getAdresseGeocoder(
              res,
              object,
              variable_destination,
              object_destination
            );
          });
      };

      function buildMailDescription(recipient, subject, message, res) {
        var mailDesc = [];

        mailDesc.push({ name: 'mailTo', type: 'cst', value: recipient });
        mailDesc.push({ name: 'mailSubject', type: 'cst', value: subject });
        var strmess = message.split('$');
        var newmess = '';
        for (var i = 0; i < strmess.length; i++) {
          if (strmess[i].indexOf('[') == 0) {
            var strrep = gaJsUtils.checkNestedProperty(
              strmess[i].replace('[', ''),
              res
            );
            newmess += strrep;
          } else {
            if (strmess[i].indexOf(']') == 0) {
              strmess[i] = strmess[i].replace(']', '');
            }

            newmess += strmess[i];
          }
        }
        mailDesc.push({ name: 'mailContent', type: 'cst', value: newmess });

        return mailDesc;
      }

      /**
      *  Construit un tableau de paramétre qui ressemble à:
          [
              {
                title: px + 'addRelation.p.0.title',
                desc: px + 'addRelation.p.0.desc',
                type: 'string',
              },
              ...
              {
                title: px + 'addRelation.p.3.title',
                desc: px + 'addRelation.p.3.desc',
                type: 'string',
              },
          ],

          où px est px, elementName est 'addRelation' et count vaut 4
      */
      function buildParameters(px, elementName, count, indexes) {
        if (indexes != undefined) {
          count = indexes.length;
        }
        let parameters = [];
        var ind, id, paramType;
        for (ind = 0; ind < count; ind++) {
          // options correspond aux options d'un paramètre de type select
          let hasOptions = false;
          if (indexes != undefined) {
            id = px + elementName + '.p.' + indexes[ind][0] + '.';
            paramType = indexes[ind][1];
            hasOptions = indexes[ind][2] && Array.isArray(indexes[ind][2]);
          }
          else {
            id = px + elementName + '.p.' + ind + '.';
            paramType = 'string';
          }
          parameters.push({
            title: id + 'title',
            desc: id + 'desc',
            type: paramType,
            options: hasOptions? indexes[ind][2] : undefined
          });
          // la propriété options existe uniquement si elle est définie
          if (!parameters.options){
            delete parameters.options;
          }
        }
        return parameters;
      }

      // TODO could be moved to formFunctionUtils
      function setDataValueByKey(data, key, value, typeFunction) {
        var keys = key.split('.');
        var lastKey = null;
        var dataHolder;
        var incrementKey = '';
        for (var i = 0; i < keys.length; i++) {
          if (lastKey) {
            if (dataHolder && formFunctionUtils.hasTabIndex(lastKey)) {
              const tabIndex = formFunctionUtils.getTabIndex(lastKey);
              const realTabHolder = formFunctionUtils.getTabHolder(lastKey);
              if (!dataHolder[realTabHolder]) {
                dataHolder[realTabHolder] = [];
              }
              dataHolder = dataHolder
                ? dataHolder[realTabHolder][tabIndex]
                : data[realTabHolder][tabIndex];
            } else {
              dataHolder = dataHolder ? dataHolder[lastKey] : data[lastKey];
            }

            if (!dataHolder) {
              dataHolder = setDataValueByKey(data, incrementKey, {});
            }
          }
          lastKey = keys[i];
          if (incrementKey.length > 0) incrementKey += '.';

          incrementKey += lastKey;
        }

        if (incrementKey.indexOf('.') == -1)
          return getDataHolderValue(data, lastKey, value, typeFunction);
        else
          return getDataHolderValue(dataHolder, lastKey, value, typeFunction);
      }

      function getDataHolderValue(dataHolder, lastKey, value, typeFunction) {
        if (angular.isDefined(typeFunction)) {
          let d;
          typeFunction = typeFunction.trim();
          switch (typeFunction.trim()) {
            case 'getYear':
              d = new Date(value);
              if (!isNaN(d.getTime()) && value != null) value = d.getFullYear();
              else value = dataHolder[lastKey];
              break;
            case 'getMonth':
              d = new Date(value);
              if (!isNaN(d.getTime()) && value != null)
                value = d.getMonth() + 1;
              else value = dataHolder[lastKey];

              break;
            case 'getDay':
              d = new Date(value);
              if (!isNaN(d.getTime()) && value != null) value = d.getDate();
              else value = dataHolder[lastKey];
              break;
            case 'concat':
              value = dataHolder[lastKey] + value;
              break;
            default:
              if (typeFunction.includes('substr')) {
                try {
                  typeFunction = typeFunction
                    .replace('[', ';')
                    .replace(']', '');
                  var functionSplited = typeFunction.split(';');
                  var db = parseInt(functionSplited[1]);
                  var leng = parseInt(functionSplited[2]);
                  if (
                    functionSplited.length == 3 &&
                    !isNaN(db) &&
                    !isNaN(leng)
                  ) {
                    value = value.substr(db, leng);
                  }
                } catch (error) {}
              } else if (typeFunction.includes('concat')) {
                typeFunction = typeFunction.replace(']', '');
                functionSplited = typeFunction.split('[');
                var sep = functionSplited[1];
                value = dataHolder[lastKey] + sep + value;
              }
              break;
          }
        }
        if (formFunctionUtils.hasTabIndex(lastKey)) {
          const tabIndex = formFunctionUtils.getTabIndex(lastKey);
          const realTabHolder = formFunctionUtils.getTabHolder(lastKey);
          if (!dataHolder[realTabHolder]) {
            dataHolder[realTabHolder] = [];
          }
          dataHolder[realTabHolder][tabIndex] = value;
          return dataHolder[realTabHolder][tabIndex];
        } else {
          dataHolder[lastKey] = value;
          return dataHolder[lastKey];
        }
      }

      let getCoordinates = (dataHolder,longitudeKey, latitudeKey)=>{
        return {
          longitude: formFunctionUtils.getDataValueByKey(dataHolder, longitudeKey),
          latitude: formFunctionUtils.getDataValueByKey(dataHolder, latitudeKey)
        };
      };


      /**
       * Transform a coordinates object [[a b]..[c d]]
       * in a String 'a b,..,c d'
       * @param coordinates
       * @returns {string}
       */
      let coordinatesToString = (coordinates) => {
        let coordinatesString = '';
        for (const onePoint of coordinates) {
          coordinatesString += onePoint[0] + ' ' + onePoint[1] + ', ';
        }
        coordinatesString = coordinatesString.substring(
          0,
          coordinatesString.length - 2
        );
        return coordinatesString;
      };
      return {
        resetMap: function () {
          var def = $q.defer();
          alert(message);
          def.resolve();
          return def.promise;
        },
        /**
         * Alert a message
         *
         * @param message
         */
        alertMessage: function (message) {
          var def = $q.defer();
          alert(message);
          def.resolve();
          return def.promise;
        },

        /**
         * Exports dossier through dolibarr
         *
         * @param objects
         */
        exportPesv2: function (objects, args) {
          let appName = args[0];
          let fti = args[1];
          var def = $q.defer();
          const showErrorAndQuit = (
            message = 'Aucun dossier trouvé pour l\'export !'
          ) => {
            require('toastr').error(message);
            def.resolve();
            gaDomUtils.hideGlobalLoader();
            return def.promise;
          };
          const CurrentAppFactory =
            fti.indexOf('kis_anc') === 0
              ? AncAppFactory
              : fti.indexOf('kis_bac') === 0
                ? BacAppFactory
                : undefined;
          if (!CurrentAppFactory) {
            return showErrorAndQuit(
              'Aucune configuration Dolibarr pour le fti sélectionné'
            );
          }
          if (!objects || !Object.values(objects) || !CurrentAppFactory) {
            return showErrorAndQuit();
          }

          const values = Object.values(objects)[0];
          if (!Array.isArray(values) || !values.length) {
            return showErrorAndQuit();
          }

          const dossierIds = values
            .filter((value) => value && value.id.indexOf(fti + '.') === 0)
            .map((value) => value.id);
          if (!dossierIds.length) {
            return showErrorAndQuit();
          }

          gaDomUtils.showGlobalLoader();
          CurrentAppFactory.getAppCfg(false, true, appName)
            .then(() => {
              const dolibarrConnectionConfig = angular.copy(
                CurrentAppFactory.appCfg.main.properties.dolibarr
              );
              dolibarrFactory.setDolibarrConnectionCfg(dolibarrConnectionConfig);
              dolibarrFactory.setDolibarrConnection(true).then(() => {
                gaDomUtils.showGlobalLoader();
                return dolibarrFactory.exportPesv2(dossierIds)
                  .then((res) => {
                    dolibarrFactory.downloadPesv2File(res.data);
                    def.resolve(res);
                    return res;
                  })
                  .catch(() => require('toastr').error('L\'export a échoué !'));
              }).finally(() => gaDomUtils.hideGlobalLoader());
            })
            .catch(() => def.resolve())
            .finally(() => gaDomUtils.hideGlobalLoader());
          return def.promise;
        },

        /**
         *
         * @param object
         * @param idgeocoder
         * @param variable_source
         * @param attribut_source
         * @returns {*}
         */
        geocodagesearch: function (
          object,
          idgeocoder,
          variable_source,
          attribut_source,
          variable_destination
        ) {
          attribut_source = 'properties.' + attribut_source;
          let adresse = gaJsUtils.checkNestedProperty(
            attribut_source,
            object[variable_source]
          );
          if (adresse == false) {
            require('toastr').error(
              $filter('translate')('kis_geocoder_picker.vide')
            );
          }
          return kisGeocodageFactory
            .search(
              kisGeocodageFactory.getGeocoderConfigFromId(idgeocoder).url,
              adresse
            )
            .then((res) => {
              if (res.data.features.length > 0) {
                setDataValueByKey(
                  object,
                  variable_destination,
                  res.data.features[0].geometry
                );
              }
              console.log(object);
            });
        },

        // @TODO remplacer tous les CADAGE par codage
        geocodagereverse: function (objects, args) {
          var def = $q.defer();
          if (!args[2].type || args[2].type == 'geometry') {
            kisgeocodagereversegeometry(
              objects,
              args[0],
              args[1],
              args[3],
              args[4],
              def
            );
          } else if (args[2].type == 'formulaire') {
            kisgeocodagereverse(
              objects.res,
              args[0],
              args[1],
              args[2].value.lon.replace(/'/g, ''),
              args[2].value.lat.replace(/'/g, ''),
              args[3],
              args[4],
              def
            );
          }
          return def.promise;
        },

        /**
         * [replaceCurrentBySelection replace current object by selected
         * object]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            source [description]
         * @param {[type]}
         *            destination [description]
         * @return {[type]} [description]
         */
        replaceCurrentBySelection: function (res, source, destination) {
          var def = $q.defer();
          if (res[destination] == undefined) {
            res[destination] = {};
          }
          res[destination] = res[source][0];

          def.resolve();
          return def.promise;
        },

        /**
         * confirm dialog
         *
         * @param message
         */
        confirmDialog: function (message) {
          var def = $q.defer();
          if (confirm(message)) {
            def.resolve();
          } else {
            console.log('refus');
            def.reject();
          }
          return def.promise;
        },

        /**
         * Open a new form in a seperate dialog
         * @param {*} data
         * @param {string} form
         * @param {string} source
         * @param {boolean} shouldMinimizePopup
         * @returns
         */
        openForm: function (
          data,
          form,
          source,
          shouldMinimizePopup
        ) {
          let def = $q.defer();

          // get the variable we need
          let resource;
          if (source) {

            // KIS-3578: supprime les quotes inutiles d'une variable source
            if (source.startsWith('\'') && source.endsWith('\'')) {
              source = source.slice(1, -1);
            }

            // passage d'une variable qui n'est pas encore instancié
            let unsetVar = source.slice();
            if (formFunctionUtils.hasTabIndex(unsetVar)) {
              unsetVar = formFunctionUtils.getTabHolder(unsetVar);
            }
            // KIS-3578: vérification correcte qd source contient un index
            if (gaJsUtils.checkNestedProperty(unsetVar, data.res) === false) {
              data.res[unsetVar] = {};
            }
            resource = formFunctionUtils.getDataValueByKey(data.res, source);
          } else if (data.hasOwnProperty('selectedObject')) {
            // special case for when 'openForm' is called from a gcDatatable
            resource = data.selectedObject;
          }

          let formData = form.split('.');

          // KIS-3578: supprime les quotes dans le chemin du formulaire à ouvrir
          const formPath = formData[0].startsWith('\'') ? formData[0].slice(1)
              : formData[0];
          const formName = formData[1] + '.' + (formData[2].endsWith('\'')
              ? formData[2].slice(0, -1) : formData[2]);
          ConfigFactory.get(formPath, formName, undefined).then(
            function (res) {
              var mscope = $rootScope.$new(true);
              mscope.ftemplatedata = res.data;

              if (res.data == '') {
                console.log('form does not exist');
                def.reject();
                return;
              }

              mscope.newobj = {};
              if (resource.length == 1) {
                mscope.newobj.current = angular.copy(resource[0]);
              } else {
                mscope.newobj.current = angular.copy(resource);
              }

              mscope.map = data.map;

              var dialogCfg = {
                template:
                  'js/XG/modules/model/views/modals/modal.featuretypes.render.html',
                className:
                  'ngdialog-theme-plain width800 nopadding miniclose close-form-class',
                formOptions: res.data.options,
                closeByDocument: false,
                scope: mscope,
                scrollable: true,
                minWidth: '570px',
                minHeight: '400px',
                data: {
                  minimizeProperty: shouldMinimizePopup === 'tab-true',
                },
                minimizeMaximize: true,
                resizable: true,
                draggable: true,
              };
              mscope.dlgPopup = extendedNgDialog.open(dialogCfg);

              //-- Attendre la fin de l'instanciation du formulaire, sinon
              //-- avec un broadcast à l'ouvertue, il ne sepassera rien car
              //-- l'écoute des événements n'est mise en place
              //-- qu'en fin d'instanciation du formulaire.
              mscope.$on('formRenderLoaded', function () {
                def.resolve();
              });
            },
            function () {
              console.log('Form does not exist');
              def.reject();
            }
          );

          return def.promise;
        },

        closeForm: function () {
          var def = $q.defer();

          // Ferme la dernière fenêtre ayant l'association de classes
          // .close-form-class.ngdialog-overlay OU
          // .close-form-class.ngdialog-no-overlay
          // la classe close-form-class n'étant que pour les formulaires
          const dialogId = $('.close-form-class.ngdialog-overlay:last, .close-form-class.ngdialog-no-overlay:last').attr('id');
          if (dialogId) {
            ngDialog.close(dialogId);
          }

          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param destination
         * @param id
         * @returns {*}
         */
        getObjectById: function () {
          var def = $q.defer();
          console.log('TODO' + 'getObjectById');
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        addObject: function () {
          var def = $q.defer();
          console.log('TODO' + 'addObject');
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        removeObject: function () {
          var def = $q.defer();
          console.log('TODO' + 'removeObject');
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        updateObject: function (res, source, fti, srid) {
          var def = $q.defer();
          if (res && res[source]) {
            var ft = {
              type: 'FeatureCollection',
              features: [res[source]],
            };
          }

          EditFactory.update(fti, ft, srid).then(
            function () {
              def.resolve();
            },
            function () {
              def.reject();
            }
          );

          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        getObjectRelated: function () {
          var def = $q.defer();
          console.log('TODO' + 'getObjectRelated');
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        uploadFile: function () {
          var def = $q.defer();
          console.log('TODO' + 'upload');
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        removeRelation: function () {
          var def = $q.defer();
          console.log('TODO' + 'removeRelation');
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        addRelation: function (res,objectsrc,lsobjectsdestination,fid,relname) {
          var def = $q.defer();
          console.log(res[objectsrc]);
          console.log(fid);
          console.log(relname);
          console.log(res[lsobjectsdestination]);
          if (res[lsobjectsdestination]) {
            var templatefeatcollect = { type: 'FeatureCollection' };
            if (res[lsobjectsdestination] instanceof Array) {
              templatefeatcollect.features = res[lsobjectsdestination];
            } else {
              templatefeatcollect.features = [];
              templatefeatcollect.features.push(res[lsobjectsdestination]);
            }
            // templatefeatcollect.features.push(res[lsobjectsdestination]);
            let fti = FeatureTypeFactory.getFeatureByUid(fid);
            let id = gaJsUtils.getIdInCaseEsriId(res[objectsrc],fti);
            EditFactory.r_add(templatefeatcollect, fid, id, relname).then(
              (res) => {
                console.log(res);
              },
              (res) => {
                require('toastr').error(res.data);
              }
            );
          }
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        generateFormMobileObject: function (
          objects,
          objectsrc,
          fid,
          nom_formulaire,
          date,
          id,
          userAgent
        ) {
          var def = $q.defer();
          var current = objects.res[objectsrc];
          var dateValue0 = current.properties[date];
          var dateValue = $filter('date')(dateValue0, 'yyyy-MM-dd');
          var idValue;
          if (id !== 'undefined') {
            idValue = current.properties[id];
          } else {
            idValue = current.id;
          }
          var agentArray = userAgent.split('/');
          var agentVariable = agentArray[0];
          var agentAtt = agentArray[1];
          var agent = objects.res[agentVariable];
          var user = $rootScope.xgos.user;
          var agentName = user.name;
          if (agent && agent[0].properties[agentAtt] != undefined) {
            agentName = agent[0].properties[agentAtt];
          }
          var specificName = nom_formulaire + '_' + idValue;
          var sendata = {
            fid: fid,
            current: current.id,
            genericName: nom_formulaire,
            specificName: specificName,
            agent: agentName,
            date: dateValue,
          };
          var promise = $http.post(
            '/services/{portalid}/odk/generateformmobileobject?f=json',
            sendata
          );
          promise.then(function () {
            $rootScope.$broadcast('refreshDatatable', { uid: fid });
          });
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        deleteFormMobileObject: function (
          objects,
          objectsrc,
          fid,
          nom_formulaire,
          date,
          id,
          userAgent
        ) {
          var def = $q.defer();
          var current = objects.res[objectsrc];
          if (Array.isArray(current)) {
            current = current[0];
          }
          var idValue;
          if (id !== 'undefined') {
            idValue = current.properties[id];
          } else {
            idValue = current.id;
          }
          var specificName = nom_formulaire + '_' + idValue;
          //alert('delete forms');
          var promise = $http.get(
            '/services/{portalid}/odk/deleteformmobileobject?specificName=' +
              specificName
          );
          def.resolve();
          return def.promise;
        },

        addEventCalendar: function (
          res,
          objectsrc,
          datedebut,
          datefin,
          adresse,
          agent,
          type_event,
          title,
          fidcurrent
        ) {
          var def = $q.defer();
          var dateDebut0 = res[objectsrc].properties[datedebut];
          var dateFin0 = res[objectsrc].properties[datefin];
          var adress = res[objectsrc].properties[adresse];
          var user = res[objectsrc].properties[agent];
          var pad = function (n) {
            return n < 10 ? '0' + n : n;
          };
          // var adress = res[objectsrc].properties[adresse];
          // console.log(res, dateDebut, dateFin);
          // //////////////////////
          var calendarEventTypes = [];
          calendarEventTypes = CalendarFactory.resources.event_types;

          var color = 'e74c3c';
          var type_evenement = 0;
          /* var title = "intervention"; */
          calendarEventTypes.forEach(function (eventType) {
            if (eventType.properties.type == type_event) {
              title = eventType.properties.type;
              color = eventType.properties.default_color;
              type_evenement = eventType.properties.type_id;
            }
          });
          var dateD = new Date(dateDebut0);
          var dd1 = pad(dateD.getDate());
          var mm1 = pad(dateD.getMonth() + 1);
          var yyyy1 = dateD.getFullYear();
          var dateDebut = yyyy1 + '-' + mm1 + '-' + dd1 + 'T08:00:00.000+0100';
          var dateF = new Date(dateFin0);
          var dd2 = pad(dateF.getDate());
          var mm2 = pad(dateF.getMonth() + 1);
          var yyyy2 = dateF.getFullYear();
          var dateFin = yyyy2 + '-' + mm2 + '-' + dd2 + 'T18:00:00.000+0100';

          var newEvent = {
            collection: {
              type: 'FeatureCollection',
              features: [
                {
                  type: 'Feature',
                  properties: {
                    color: color,
                    title: title,
                    type_evenement: type_evenement,
                    start: dateDebut,
                    end: dateFin,
                    description: '',
                    place: adress,
                  },
                },
              ],
            },
            linked_objects: [
              {
                type: 'FeatureCollection',
                features: [
                  {
                    type: 'Feature',
                    properties: {
                      feature_id: res[objectsrc].id,
                      feature_uid: fidcurrent,
                    },
                  },
                ],
              },
            ],
            users: [
              [
                {
                  login: user,
                  type: 'concerne',
                },
              ],
            ],
          };

          /*
           * linked_objects: [ { "type": "FeatureCollection",
           * "features": [{ "type": "Feature", "properties": {
           * feature_id: scope.currentAnomalie.id, feature_uid:
           * scope.ftis['anomalie'].uid } }, { "type": "Feature",
           * "properties": { feature_id: scope.currentIR.id,
           * feature_uid: fidInt } }] } ]
           */

          CalendarFactory.addevent(newEvent).then(
            function () {
              require('toastr').success('ok');
            },
            function () {
              require('toastr').error('error');
            }
          );

          def.resolve();
          return def.promise;
        },

        /**
         * [generateBuffer description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            source [description]
         * @param {[type]}
         *            destination [description]
         * @return {[type]} [description]
         */
        generateBuffer: function (
          res,
          source,
          destination,
          bufferType,
          bufferSize
        ) {
          var def = $q.defer();
          console.log(res[source]);
          var geom = [],
            features;
          if (res[source].features == undefined) features = res[source];
          else features = res[source].features;
          features.forEach(function(f) {
            if(f){
              geom.push(f.geometry);
            }
          });
          // -- En cas de mise à jour, le current est renseigné.
          if (
            res[destination] == undefined ||
            res[destination].id == undefined
          ) {
            // -- Le current n'est pas renseigné => Création.
            res[destination] = {};
            res[destination].geometry = {};
          }
          // En cas d'ancienne configuration, on parse le string au cas ou il y ai une virgule
          if (typeof bufferSize === 'string') {
            bufferSize = parseFloat(bufferSize.replace(',', '.'));
          }
          GeometryFactory.unionandbuffer(geom, bufferType, bufferSize).then(
            function (res1) {
              res[destination].geometry = res1.data;
              def.resolve();
            }
          );

          return def.promise;
        },

        /**
         * generateUnion: select all objects intersecting our source(s)
         * @param objects
         * @param source
         * @param destination1
         * @param destination2
         * @param fids
         * @param spatialOperator
         * @returns {*}
         */
        generateUnion: (
          objects,
          fti,
          source,
          destination1,
          destination2,
          fids,
          spatialOperator,
          attributeName,
          value1,
          value2
        ) => {
          // Initialisation
          let def = $q.defer();
          let geometries = [];
          objects.res[destination1] = {};
          objects.res[destination1].geometry = {};
          if(!spatialOperator) {
            spatialOperator = { operator: 'INTERSECTS' };
          }

          // If the source is not filled correctly
          if (!objects.res[source]) {
            require('toastr').error($filter('translate')(
              'tools.builder.fields.functions_list.noSourceForGenerateUnion'));
            console.error('No source for generateUnion function, empty variable: '
                + source);
            return;
          }

          // Test if we have a featureCollection or just a feature
          if(objects.res[source].type === 'FeatureCollection') {
            for (const feature of objects.res[source].features) {
              geometries.push(feature.geometry);
            }
          } else { // Polygon, Line or Point
            geometries.push(objects.res[source].geometry);
          }

          // Use the union of this geometries
          GeometryFactory.union(geometries).then(function (res1) {
            const unionGeometries = res1.data;
            objects.res[destination1].geometry = unionGeometries;
            if (fids !== '' && unionGeometries) {
              let fidX = fids.replaceAll('/', ',') + ',';

              // Create the cql_filter depending on the union output type
              let cql_filter = '';
              let additional_filters = '';
              if (unionGeometries.type === 'MultiPolygon') {
                for (let i = 0; i < unionGeometries.coordinates.length; i++) {
                  if (i > 0) {
                    additional_filters += ' OR ';
                  }
                  additional_filters +=
                      spatialOperator.operator +
                      '(geom, POLYGON((' +
                      coordinatesToString(unionGeometries.coordinates[i][0]) +
                      ')))';
                }
              } else if (unionGeometries.type === 'Polygon') {
                cql_filter = spatialOperator.operator + '(geom, POLYGON(('
                    + coordinatesToString(unionGeometries.coordinates[0])
                    + ')))';
              } else if (unionGeometries.type === 'LineString') {
                cql_filter = spatialOperator.operator + '(geom, LINESTRING('
                    +  coordinatesToString(unionGeometries.coordinates)
                    + '))';
              } else if (unionGeometries.type === 'Point') {
                cql_filter = spatialOperator.operator + '(geom, POINT('
                    + unionGeometries.coordinates[0] + ' '
                    + unionGeometries.coordinates[1]
                    + '))';
              } else {
                console.error('union output not recognized in generateUnion function');
              }

              // Create the url for the intersecting object(s) request
              ogcFactory.getfeatures( 'GetFeature', 'WFS', '1.0.0', fidX, 'json',
                objects.map.getView().getProjection().getCode(),
                cql_filter, undefined, undefined, undefined, 'query', additional_filters
              ).then(function (res2) {
                const correctValue = (value, attributeType) => {
                  switch (attributeType) {
                    case 'java.lang.Integer':
                    //match digits only
                      const isInterger = value.match(/^\d+$/);
                      const parsedInt = parseInt(value);
                      if (isInterger && parsedInt != undefined) {
                        value = parsedInt;
                      } else {
                        value = undefined;
                      }
                      break;
                    case 'java.lang.Double':
                      value = value.replace(',', '.');
                      //match digits with one '.'
                      const isDouble = value.match(/^\d+\.?\d*$/);
                      const parsedFloat = parseFloat(value);
                      if (isDouble && parsedFloat != undefined) {
                        value = parsedFloat;
                      } else {
                        value = undefined;
                      }
                      break;
                    case 'java.lang.Boolean':
                      value = value.toLowerCase();
                      if (['1', 'true', 'vrai'].indexOf(value) > -1) {
                        value = true;
                      } else if (['0', 'false', 'faux'].indexOf(value) > -1) {
                        value = false;
                      } else {
                        value = undefined;
                      }
                      break;
                    default:
                    // we don't modify the value
                      break;
                  }
                  return value;
                };
                console.log('generateUnion result:');
                console.log(res2.data);
                objects.res[destination2] = res2.data;
                if (fti && fti.attributes) {
                  if (attributeName && attributeName.length > 0) {
                    const attributeInfo = fti.attributes.find(ftiAttribute => ftiAttribute.name === attributeName);
                    if (attributeInfo) {
                      //the attribute exists
                      //value1 and value2 are always strings at this point

                      //correct value1 and value2 on special cases
                      const correctedValue1  = correctValue(value1, attributeInfo.type);
                      const correctedValue2 = correctValue(value2, attributeInfo.type);
                      if (correctedValue1 != undefined && correctedValue2 != undefined
                        && objects.res.current && objects.res.current.properties) {
                        //save the data
                        if (res2.data && res2.data.totalFeatures > 0) {
                          objects.res.current.properties[attributeName] = correctedValue2;
                        } else {
                          objects.res.current.properties[attributeName] = correctedValue1;
                        }
                      } else {
                        if (correctedValue1 === undefined) {
                          require('toastr').warning(value1 + $filter('translate')(
                            'tools.builder.fields.functions_list.isNotCompatibleWithAttribute') + attributeName);
                        }
                        if (correctedValue2 === undefined) {
                          require('toastr').warning(value2 + $filter('translate')(
                            'tools.builder.fields.functions_list.isNotCompatibleWithAttribute') + attributeName);
                        }
                      }
                    } else {
                      require('toastr').warning(attributeName + $filter('translate')(
                        'tools.builder.fields.functions_list.isNotAValidAttribute'));
                    }
                  } else {
                    console.log('attribute not set');
                  }
                } else {
                  console.error('the fti of \'current\' is not provided');
                }
                def.resolve();
              });
            }
          });
          return def.promise;
        },


        /**
         * [getElementsNetwork description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            source [description]
         * @param {[type]}
         *            destination [description]
         * @return {[type]} [description]
         */
        getElementsNetwork: function (res, source, destination) {
          var def = $q.defer();
          res[destination] = res[source].path.features;
          SelectManager.addFeaturesFromGeojson(res[destination]);
          def.resolve();
          return def.promise;
        },

        /**
         * [getFirstElementNetwork description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            source [description]
         * @param {[type]}
         *            destination [description]
         * @return {[type]} [description]
         */
        getFirstElementNetwork: function (res, source, destination, type) {
          var def = $q.defer();
          if (type == 'ponctuel') {
            res[destination] = res[source].path.features.features[0];
          }
          if (type == 'lineaire') {
            res[destination] = res[source].path.features.features[1];
          }
          def.resolve();
          return def.promise;
        },

        /**
         * [getLastElementNetwork description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            source [description]
         * @param {[type]}
         *            destination [description]
         * @return {[type]} [description]
         */
        getLastElementNetwork: function (res, source, destination, type) {
          var def = $q.defer();
          var length = res[source].path.elements.length;
          if (type == 'ponctuel') {
            res[destination] = res[source].path.features.features[length - 1];
          }
          if (type == 'lineaire') {
            res[destination] = res[source].path.features.features[length - 2];
          }
          def.resolve();
          return def.promise;
        },

        /**
         * [getCurrentClause description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            source [description]
         * @param {[type]}
         *            destination [description]
         * @return {[type]} [description]
         */
        getCurrentClause: function (res, source, destination) {
          var def = $q.defer();
          if (res[source] == '1=10') {
            res[destination] = '1=1';
          } else {
            res[destination] = res[source];
          }
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        delObject: function (
          res,
          fti,
          variableName,
          sendRefreshDataTable,
          confirmMessage
        ) {
          var def = $q.defer();

          if (confirmMessage != undefined && confirmMessage != '') {
            var r = confirm(confirmMessage);
            if (r != true) return def.promise;
          }

          $rootScope.$broadcast('showDatatableLoader');

          // '[0]' is useless now
          variableName = variableName.replace('[0]', '');

          const variableFeatures = formFunctionUtils.getDataValueByKey(res, variableName);
          console.log(variableFeatures);
          console.log(res);

          let featureIdToDelete;
          if (variableFeatures.id) {
            // variableFeatures is 1 feature
            featureIdToDelete = variableFeatures.id;
          } else if (Array.isArray(variableFeatures)) {
            // variableFeatures is an array
            featureIdToDelete = variableFeatures[0].id;
          } else {
            // variableFeatures is a featureCollection
            featureIdToDelete = variableFeatures.features[0].id;
          }
          const iDot = featureIdToDelete.indexOf('.');
          if (iDot > -1) {
            featureIdToDelete = featureIdToDelete.substr(iDot + 1);
          }
          EditFactory.remove(fti, featureIdToDelete).then(() => {
            // le cas 'true' permet de gérer la retricompatilité avec les anciennes configs
            if (sendRefreshDataTable === 'true'
            || sendRefreshDataTable === '\'tab-true\'') {
              $rootScope.$broadcast('refreshDatatable', { uid: fti });
            }
            def.resolve();
          }).finally(() => {
            $rootScope.$broadcast('hideDatatableLoader');
          });
          return def.promise;
        },

        /**
         *
         * @param res
         * @param source
         * @returns {*}
         */
        delRelation: function (
          res,
          objectsrc,
          lsobjectsdestination,
          fid,
          relname,
          confirmMessage
        ) {
          var def = $q.defer();

          if (confirmMessage != undefined && confirmMessage != '') {
            var r = confirm(confirmMessage);
            if (r != true) return def.promise;
          }

          console.log(res[objectsrc]);
          console.log(fid);
          console.log(relname);
          console.log(res[lsobjectsdestination]);
          var templatefeatcollect = {
            type: 'FeatureCollection',
            features: [],
          };
          templatefeatcollect.features.push(res[lsobjectsdestination]);
          const obj = formFunctionUtils.getDataValueByKey(res, objectsrc);
          var ids = '';
          if (lsobjectsdestination == '*') {
            EditFactory.r_delete(fid, relname, obj.id, false).then(function (
              res
            ) {
              console.log(res);
              def.resolve();
            });
          } else {
            for (var iObj = 0; iObj < res[lsobjectsdestination].length; iObj++)
              ids += res[lsobjectsdestination][iObj].id + ';';
            ids = ids.substr(0, ids.length - 1);
            EditFactory.r_delete_1_nm(fid, relname, obj.id, ids, false).then(
              function (res) {
                console.log(res);
                def.resolve();
              }
            );
          }
          return def.promise;
        },

        /**
         *
         * @returns {*}
         */
        clearDrawLayer: function () {
          var def = $q.defer();
          gclayers.getDrawLayer().getSource().clear();
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @returns {*}
         */
        clearHighLayer: function () {
          var def = $q.defer();
          gclayers.clearhighLightFeatures();
          SelectManager.clear();
          def.resolve();
          return def.promise;
        },

        /**
         * setData
         *
         * @param {object} res objet contenant toutes les variables du formulaire
         * @param {object} ftiOfCurrent fti of the variable 'current'
         * @param {string} destination chemin de l'attribut destination dans l'objet res (ex res.current.properties.effluent)
         * @param {string} source chemin de l'attribut source dans l'objet res
         * @param {string} typeFunction
         * @param {string} decodeTableRestriction est égal à 'tab-true' si l'attribut source possède une table ou un domaine de restriction
         */
        setData: function (res, ftiOfCurrent, destination, source, typeFunction, decodeTableRestriction) {
          const def = $q.defer();
          let dataToSet;
          if (decodeTableRestriction === 'tab-true') {

            if (!ftiOfCurrent || (ftiOfCurrent && !Array.isArray(ftiOfCurrent.attributes))) {
              // si le fti fourni par formFieldRender est vide
              // alors on ne peut pas décoder la table de restriction ou le domaine de valeur
              console.error('setData : ftiOfCurrent est vide ou null. ftiOfCurrent = ', ftiOfCurrent);
              def.resolve();
            }

            // Si on souhaite récupérer la valeur à partir d'une clé stockée dans un attribut lié à une table de restriction (decodeTableRestriction === 'tab-true')
            // on doit vérifier si l'attribut source possède bien une table de restriction
            const sourceAttHasRestriction = attributeRestrictionsUtils.attributeHasTableOrDomainRestriction(ftiOfCurrent, source);
            // et on doit vérifier si l'attribut de destination est un "champ libre" (sans table de restriction)
            const destAttHasRestriction = attributeRestrictionsUtils.attributeHasRestriction(ftiOfCurrent, destination);

            if (sourceAttHasRestriction && !destAttHasRestriction) {
              // l'attribut source possède une table ou un domaine de restriction et l'attribut de destination est un champ libre
              const restrictionKey = formFunctionUtils.getDataValueByKey(res, source);

              if (restrictionKey) {
                // l'attribut source existe bien dans l'objet sélectionné
                if (attributeRestrictionsUtils.attributeHasRestrictionType(ftiOfCurrent, source, attributeRestrictionsUtils.TABLE)) {
                  // on décode une table de restriction
                  QueryFactory.getValueRestrictionTable(ftiOfCurrent.uid, source.slice().split('.').pop(), restrictionKey).then(
                    response => {
                      if (response.data && response.data.value) {
                        dataToSet = response.data.value;
                        setDataValueByKey(
                          res,
                          destination,
                          dataToSet,
                          typeFunction
                        );
                      } else {
                        console.info(
                            `setData : aucune valeur de restriction associée à la clé ${restrictionKey}
                            dans la table ${attributeRestrictionsUtils.TABLE} pour le composant ${ftiOfCurrent.alias} et l'attribut ${source}`);
                      }
                      def.resolve();
                    },
                    err => {
                      console.error('setData : ', err.data);
                      def.resolve();
                    }
                  );
                } else {
                  // on décode un domaine de restriction
                  const sourceAttributeName = source.slice().split('.').pop();
                  const sourceAttribute = ftiOfCurrent.attributes.find(attr => attr.name === sourceAttributeName);
                  if (sourceAttribute) {
                    dataToSet = angular.copy(sourceAttribute.restrictions[0].listofValues[restrictionKey]);
                    setDataValueByKey(
                      res,
                      destination,
                      dataToSet,
                      typeFunction
                    );
                  }
                  def.resolve();
                }
              } else {
                def.resolve();
              }
            } else {
              console.error(
                'setData : l\'attribut source ne possède pas une table ou un domaine'
                  + ' de restriction ou l\'attribut de destination n\'est un champ libre');
              def.resolve();
            }
          } else {
            dataToSet = formFunctionUtils.getDataValueByKey(res, source);
            setDataValueByKey(
              res,
              destination,
              dataToSet,
              typeFunction
            );
            def.resolve();
          }
          return def.promise;
        },

        /**
         * appendData
         *
         * @param res
         * @param destination
         * @param source
         */
        appendData: function (res, destination, source) {
          var def = $q.defer();
          res[destination] = angular.isDefined(res[destination])
            ? res[destination]
            : [];
          if (angular.isDefined(res[source])) {
            res[destination].push(res[source]);
          }
          def.resolve();
          return def.promise;
        },
        /**
         * controlTotalFeatures
         *
         * @param res
         * @param source
         * @param maximum
         * @param message
         */
        controlTotalFeatures: function (res, source, maximum, message) {
          var def = $q.defer();
          if(!angular.isDefined(res[source]) && maximum ==0){
            swal(
              {
                title: '',
                text: message,
                type: 'error',
                showCancelButton: false,
                confirmButtonText: 'ok',
                closeOnConfirm: true,
              }
            );
            def.reject();
          } else if(angular.isDefined(res[source]) && !angular.isDefined(res[source].features)){
            swal(
              {
                title: '',
                text: 'La fonction controlTotalFeatures ne peut pas traiter la variable ' + source,
                type: 'error',
                showCancelButton: false,
                confirmButtonText: 'ok',
                closeOnConfirm: true,
              }
            );
            def.reject();
          } else if(angular.isDefined(res[source]) && res[source].features.length>=maximum){
            swal(
              {
                title: '',
                text: message,
                type: 'error',
                showCancelButton: false,
                confirmButtonText: 'ok',
                closeOnConfirm: true,
              }
            );
            def.reject();
          } else {
            def.resolve();
          }
          return def.promise;
        },

        /**
         * setClauseWhere
         *
         * @param res
         * @param destination
         * @param source
         */
        setClauseWhere: function (res, destination, source) {
          var re = /(?:^|[ ])\$([a-zA-Z_\.]+)/gm;
          var m;
          var copy = angular.copy(source);

          while ((m = re.exec(source)) != null) {
            if (m.index === re.lastIndex) {
              re.lastIndex++;
            }

            // console.log(m)
            let _val = formFunctionUtils.getDataValueByKey(res, m[1]);
            if (typeof _val == 'string') _val = `'${_val}'`;

            copy = copy.replace(m[0], _val);
          }

          // console.log(copy);

          var def = $q.defer();

          setDataValueByKey(res, destination, copy);
          def.resolve();
          return def.promise;
        },

        /**
         * setStaticData
         *
         * @param res
         * @param fti fti maître du formulaire
         * @param args
         */
        setStaticData: function (res, fti, args) {
          var def = $q.defer();
          var destination = args[0] + '.properties.' + args[1];
          let staticValue = args[2];

          // KIS 3051: Si la valeur saisie dans le paramétrage de la fonction n’est pas de type numérique
          // alors que l’attribut est de type numérique alors l'exécution doit provoquer l'affichage d'un toaster d’avertissement
          if (fti && Array.isArray(fti.attributes)) {
            const numericTypes =  ['java.lang.Double', 'java.lang.Integer'];
            const destAttrib = fti.attributes.find(attr => attr.name === args[1]);
            if (destAttrib !== undefined && numericTypes.includes(destAttrib.type) && typeof staticValue === 'string') {
              const isInteger = destAttrib.type === 'java.lang.Integer';
              staticValue = isInteger ? Number.parseInt(staticValue) : Number.parseFloat(staticValue);
              if (Number.isNaN(staticValue)) {
                require('toastr').warning($filter('translate')('functions_browser.functions.setStaticData.destTypeError') + args[1]);
                def.resolve();
                return def.promise;
              }
            }
          }
          setDataValueByKey(res, destination, staticValue);
          def.resolve();
          return def.promise;
        },

        /**
         * [concatAttributFromFeatures description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            variable source [description]
         * @param {[type]}
         *            attribut attsource utilisé pour la concaténation
         *            [description]
         * @param {[type]}
         *            separator de concaténation [description]
         * @param {[type]}
         *            variable destination [description]
         * @param {[type]}
         *            attribut attdestination [description]
         */
        concatAttributFromFeatures: function (
          res,
          source,
          attsource,
          separator,
          destination,
          attdestination
        ) {
          var def = $q.defer();

          var valdestination = '';

          for (var i = 0; i < res[source].length; i++) {
            if (res[source][i]['properties'][attsource] != undefined) {
              if (valdestination != '') {
                valdestination =
                  valdestination +
                  separator +
                  res[source][i]['properties'][attsource];
              } else {
                valdestination = res[source][i]['properties'][attsource];
              }
            }
          }

          try {
            res[destination]['properties'][attdestination] = valdestination;
            def.resolve();
          } catch (error) {
            def.reject();
          }

          return def.promise;
        },

        /**
         * [sumAttributFromFeatures description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            variable source [description]
         * @param {[type]}
         *            attribut attsource utilisé pour la concaténation
         *            [description]
         * @param {[type]}
         *            variable destination [description]
         * @param {[type]}
         *            attribut attdestination [description]
         */
        sumAttributFromFeatures: function (
          res,
          source,
          attsource,
          destination,
          attdestination
        ) {
          var def = $q.defer();

          var valdestination = 0;

          for (var i = 0; i < res[source].length; i++) {
            if (res[source][i]['properties'][attsource] != undefined) {
              if (valdestination != 0) {
                valdestination =
                  Number(valdestination) +
                  Number(res[source][i]['properties'][attsource]);
              } else {
                valdestination = Number(
                  res[source][i]['properties'][attsource]
                );
              }
            }
          }

          try {
            res[destination]['properties'][attdestination] = valdestination;
            def.resolve();
          } catch (error) {
            def.reject();
          }

          return def.promise;
        },

        /**
         * [calculExpression description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            variable source [description]
         * @param {[type]}
         *            variable destination [description]
         * @param {[type]}
         *            attribut destination [description]
         * @param {[type]}
         *            expression arithmétique combinant des attributs et
         *            des constantes [description]
         */
        calculExpression: function (
          res,
          source,
          destination,
          attdestination,
          expression
        ) {
          var def = $q.defer();

          let tabexpression = [];
          if (expression) {
            tabexpression = expression.split('$');
          }
          var newexpression = '';
          var valexpression = 1;

          for (var i = 0; i < tabexpression.length; i++) {
            if (
              tabexpression[i] == '+' ||
              tabexpression[i] == '-' ||
              tabexpression[i] == '*' ||
              tabexpression[i] == '/' ||
              tabexpression[i] == '+' ||
              tabexpression[i] == '[' ||
              tabexpression[i] == ']' ||
              tabexpression[i] == ']*' ||
              tabexpression[i] == '*[' ||
              tabexpression[i] == ']/' ||
              tabexpression[i] == '/[' ||
              tabexpression[i] == ''
            ) {
              newexpression = newexpression + tabexpression[i];
            } else {
              if (res[source]['properties'][tabexpression[i]] != undefined) {
                newexpression =
                  newexpression + res[source]['properties'][tabexpression[i]];
              } else {
                newexpression = newexpression + tabexpression[i];
              }
            }
          }

          try {
            newexpression = newexpression.replace('[', '(').replace(']', ')');
            valexpression = eval(newexpression);
            res[destination]['properties'][attdestination] = valexpression;
            def.resolve();
          } catch (error) {
            def.reject();
          }

          return def.promise;
        },
        /**
         * Calcule l'écart entre deux dates en jours ou mois ou années
         * @param res
         * @param source1 date de début
         * @param source2 date de fin
         * @param destination variable de destination du calcul
         * @param unit unité du calcul (heure, jour, mois ou année)
         * @param strict est true si calcul strict de l'écart
         * @return {Promise}
         */
        calculInterval: (res, source1, source2, destination, unit, strict) => {
          let def = $q.defer();

          let date_debut = formFunctionUtils.getDataValueByKey(res, source1);
          let date_fin = formFunctionUtils.getDataValueByKey(res, source2);
          let result = null;

          if(date_debut && date_fin){

            const dateFin = new Date(date_fin);
            const dateDebut = new Date(date_debut);

            if (dateFin.getTime() > 0 && dateDebut.getTime() > 0) {
              if (unit === 'hour') {
                // hour is not rounded -> always 'strict' mode
                strict = true;
              }
              result = strict ?
                dateUtils.intervalBetween2Dates(dateDebut, dateFin, unit) :
                dateUtils.intervalBetween2Dates(dateDebut, dateFin, unit) + 1;
            }
          }
          setDataValueByKey(res, destination, result);
          def.resolve();
          return def.promise;
        },
        /**
         * [setUUIDtoAttribute description]
         *
         * @param {[type]}
         *            res [description]
         * @param {[type]}
         *            cancatchaine [description]
         * @param {[type]}
         *            source [description]
         * @param {[type]}
         *            attdestination [description]
         */
        setUUIDtoAttribute: function (
          res,
          concatchaine,
          source,
          attdestination,
          replace
        ) {
          var def = $q.defer();
          var atttocaoncat = concatchaine.split('-');
          var uuid = '';
          var today = new Date();

          if (
            replace == 0 &&
            res[source]['properties'][attdestination] != undefined
          ) {
            def.resolve();
            return def.promise;
          }
          for (var i = 0; i < atttocaoncat.length; i++) {
            if (atttocaoncat[i].indexOf('$') == 0) {
              var attr = atttocaoncat[i].replace('$', '');
              if (attr == 'id') {
                var str = gaJsUtils.checkNestedProperty(source + '.id', res);
                var d = str.indexOf('.');

                uuid = uuid + str.substr(d + 1) + '-';
              } else {
                uuid =
                  uuid +
                  gaJsUtils.checkNestedProperty(
                    source + '.properties.' + attr,
                    res
                  ) +
                  '-';
              }
            } else if (atttocaoncat[i] == 'sysDate') {
              uuid = uuid + today + '-';
            } else if (atttocaoncat[i] == 'getDate') {
              uuid = uuid + today.getDate() + '-';
            } else if (atttocaoncat[i] == 'getMonth') {
              var jour = today.getMonth() + 1;
              uuid = uuid + jour + '-';
            } else if (atttocaoncat[i] == 'getUniqueId') {
              uuid = uuid + today.getTime() + '-';
            } else if (atttocaoncat[i] == 'getYear') {
              uuid = uuid + today.getFullYear() + '-';
            } else {
              uuid = uuid + atttocaoncat[i] + '-';
            }
          }
          uuid = uuid.substr(0, uuid.length - 1);
          // setDataValueByKey(res,
          // source['properties'][attdestination],uuid);
          res[source]['properties'][attdestination] = uuid;
          def.resolve();
          return def.promise;
        },

        /**
         * loadRelations
         *
         * @param res
         * @param origineUid
         * @param relationName
         * @param destination
         * @param loadAll
         */
        loadRelations: function (res, origineUid, relationName, destination,
          loadAll) {
          let def = $q.defer();

          if (gaJsUtils.checkNestedProperty('current.id', res) !== false) {
            gaDomUtils.showGlobalLoader('loadRelations');
            let fti = FeatureTypeFactory.getFeatureByUid(origineUid);
            var currentId = gaJsUtils.getIdInCaseEsriId(res.current,fti);
            let srid;
            if ($rootScope.xgos && $rootScope.xgos.mouseposition && $rootScope.xgos.mouseposition.srid) {
              srid = $rootScope.xgos.mouseposition.srid.name;
            }
            QueryFactory.relation(origineUid,relationName,currentId,
              '',undefined,undefined, srid
            ).then((relations) => {
              if (loadAll) {
                const features = {};
                features.type = 'FeatureCollection';
                features.features = relations.data.features;
                setDataValueByKey(res, destination, features);
              } else {
                setDataValueByKey(res, destination, relations.data.features[0]);
              }

              // ajouter un timeout pour éviter le clignotement du
              // globalLoader lors de plusieurs executions consecutive
              gaDomUtils.hideGlobalLoaderAfterTimeout(500, 'loadRelations');
              def.resolve();
            },
            () => {
              gaDomUtils.hideGlobalLoaderAfterTimeout(500, 'loadRelations');
            }
            );
          } else {
            def.reject();
          }
          return def.promise;
        },

        selectfromGeometry: function () {
          var def = $q.defer();

          def.resolve();
          return def.promise;
        },

        addBranchTree: function (tree, label, template) {
          var b;
          b = tree.get_selected_branch();
          return tree.add_branch(b, {
            label: label,
            template: template,
          });
        },
        /*
         * addBranchTree: function( tree , label , template ) { var b; b =
         * tree.get_selected_branch(); return tree.add_branch(b, {
         * label: label, template: template });
         *  },
         */

        _getTreeBranch: function (tree, branchNumber) {
          var selectedBranch;

          /*
           * var goto = []; for(var i in branchNumber){
           * goto.push(parseInt(branchNumber[i],10)); }
           */
          var goto = branchNumber.split('#');

          if (angular.isDefined(tree)) {
            selectedBranch = tree.get_first_branch();
            for (var g in goto) {
              for (var i = 0; i < goto[g]; i++) {
                selectedBranch = tree.get_next_sibling(selectedBranch);
              }
              // except for last element
              if (g < goto.length - 1) {
                selectedBranch = tree.get_next_branch(selectedBranch);
              }
            }
          }

          return selectedBranch;
        },

        _showTreeLeaf: function (leaf) {
          leaf.classes.splice(leaf.classes.indexOf('hiddenTreeBranch'));
        },

        _hideTreeLeaf: function (leaf) {
          if (leaf.classes.indexOf('hiddenTreeBranch') == -1) {
            leaf.classes.push('hiddenTreeBranch');
          }
        },

        /**
         *
         * @param treeBranch
         */
        _handleBranchChildren: function (treeBranch, action) {
          var self = this;
          if (action == 'show') this._showTreeLeaf(treeBranch);
          if (action == 'hide') this._hideTreeLeaf(treeBranch);

          treeBranch.children.forEach(function (c) {
            self._handleBranchChildren(c, action);
          });
        },

        /**
         *
         * @param tree
         * @param branchNumber
         */
        showTreeBranch: function (tree, branchNumber) {
          var def = $q.defer();
          var treeBranch = this._getTreeBranch(tree, branchNumber);
          this._handleBranchChildren(treeBranch, 'show');
          def.resolve();
          return def.promise;
        },

        /**
         *
         * @param tree
         * @param branchNumber
         */
        hideTreeBranch: function (tree, branchNumber) {
          var def = $q.defer();
          var treeBranch = this._getTreeBranch(tree, branchNumber);
          this._handleBranchChildren(treeBranch, 'hide');
          def.resolve();
          return def.promise;
        },

        /**
         * broadcastMessage
         *
         * @param data
         * @param message
         */
        broadcastMessage: function (data, message, templatefields) {
          var def = $q.defer();

          $rootScope.$on(message + '#ended', function () {
            console.log('broadcastMessage (' + message + ') ended');
            def.resolve();
          });
          $rootScope.$broadcast(message, data, templatefields);
          console.log('broadcastMessage (' + message + ') started');

          return def.promise;
        },

        /**
         * goToTreeBranch
         *
         * @param tree
         * @param branchNumber
         */
        goToTreeBranch: function (tree, branchNumber) {
          var def = $q.defer();
          var treeBranch = this._getTreeBranch(tree, branchNumber);
          tree.select_branch(treeBranch);
          def.resolve();
          return def.promise;
        },
        deleteBranchTree: function () {},
        geometryBufferise: function (
          res,
          typebuffer,
          distance,
          FeaturCollection
        ) {
          var geom = [];
          FeaturCollection.features.forEach(function (f) {
            geom.push(f.geometry);
          });

          GeometryFactory.unionandbuffer(geom, typebuffer, distance).then(
            function (res) {
              res.geometry = res.data;
            }
          );
        },

        printDocXForm: function (res, docname) {
          var def = $q.defer();
          var sendata = angular.copy(res.res);
          for (var key in sendata) {
            if (
              key &&
              sendata.hasOwnProperty(key) &&
              sendata[key] &&
              sendata[key].type == 'FeatureCollection' &&
              sendata[key].features
            )
              sendata[key] = sendata[key].features;
          }
          DocumentFactory.generatedocx(
            sendata,
            docname,
            undefined,
            false,
            res.map.getView().getProjection().getCode()
          ).then(function (doto) {
            window.open(
              '/services/' +
                doto.data.portalid +
                '/documents/downloaddocx?' +
                '&name=' +
                doto.data.name
            );
          });
          def.resolve();
          return def.promise;
        },

        executeWS: function (res, source, url, statistic, keysAtt, valuesAtt) {
          var def = $q.defer();
          if (res[source] && res[source].id == undefined) {
            var strStat = statistic.replace('/', ',');
            var objStat = JSON.parse(strStat);
            var keyAttS = keysAtt.replace('/', ',');
            var valuesAttS = valuesAtt.replace('/', ',');
            var keysAttT = keyAttS.split(',');
            var valuesAttT = valuesAttS.split(',');
            // tableau de valeurs des attributs en fonction des noms des
            // attributs donnés en entrée
            var tabAtt = [];
            for (var k = 0; k < valuesAttT.length; k++) {
              tabAtt.push(res[source].properties[valuesAttT[k]]);
            }
            // construction de l'objet json en fonction des clefs, valeurs
            // données
            var objAtt = {};
            for (var i = 0; i < tabAtt.length; i++) {
              objAtt[keysAttT[i]] = tabAtt[i];
            }
            // construction de l'objet json en fonction des parametres
            // statiques et attributs
            var result = {};
            /*
             * Object.keys(objStat).forEach((key) => result[key] =
             * objStat[key]); Object.keys(objAtt).forEach((key) =>
             * result[key] = objAtt[key]);
             */

            Object.keys(objStat).forEach(function (key) {
              result[key] = objStat[key];
            });
            Object.keys(objAtt).forEach(function (key) {
              result[key] = objAtt[key];
            });

            var promise = $http.post('http://' + url, result);
            promise.then(
              function (res) {
                if (res.data == 'NO') {
                  require('toastr').error('erreur');
                  def.reject();
                } else {
                  def.resolve();
                }
              },
              function (err) {
                console.log(err);
              }
            );

            return def.promise;
          } else {
            def.resolve();
            return def.promise;
          }
        },

        /**
         * Recherche des points de consommations Omega .
         */
        //omegaRecherchePointConsommation : function(client,queryParamRechercheOmega,rechercheType)
        omegaRecherchePointConsommation: function (res, variable, filters) {
          var def = $q.defer();

          // creation de scope
          var mscope = $rootScope.$new(true);

          // alimentation de la liste des filtres
          var filtersTab = filters.split('~');
          mscope.omegaFilters = {};
          mscope.res = res;
          var possibleFilters = [
            'nom_prenom',
            'adresse_complete',
            'commune',
            'numero_compteur',
            'ref_point_conso',
            'multiple_selection',
            'show_table',
            'mode_Widget',
            'num_contrat',
          ];
          possibleFilters.forEach(function (x) {
            mscope.omegaFilters[x] = filtersTab.indexOf(x) !== -1;
          });
          mscope.omegaVariable = variable;
          // pointConso Selectionne
          mscope.pointConsoSelectionne = function (selection) {
            if (!angular.isDefined(res[variable])) res[variable] = {};
            res[variable] = selection;
            recherchepointconsommation.close();
          };
          mscope.recherchepointconsommation = ngDialog.open({
            template:
              'js/XG/widgets/ancapp/main/views/modals/modal.recherchepointconsommation.html',
            className: 'ngdialog-theme-plain width800 nopadding miniclose',
            closeByDocument: false,
            scope: mscope,
          });

          def.resolve();
          return def.promise;
        },

        /**
         * display the objects on the map (red selection)
         * + if shouldDisplayInformation is true objects's data will be shown in an info-box
         * @param {*} objects
         * @param {boolean} shouldDisplayInformation if true, we open an info-box containing features data
         * @returns
         */
        LocaliseAndDisplayInformation: function (objects, shouldDisplayInformation) {
          var def = $q.defer();

          if (!angular.isDefined(objects.map)) {
            console.log('pas de map !');
            def.resolve();
            return;
          }

          // localise
          const feature = objects.selectedObject || objects.res.current;
          const copy = mapJsUtils.localiseData(feature, objects.map);


          // open infobox if needed
          if (shouldDisplayInformation === true) {
            // // creation de featurecollection
            const featuresToDisplay = gaJsUtils.setNewFeatureCollection(copy);
            SelectManager.addFeaturesFromGeojson(angular.copy(featuresToDisplay));
            const mscope = $rootScope.$new(true);
            mscope.map = objects.map;

            gcPopup.open({
              scope: mscope,
              title: 'Informations : ',
              content: '<div selectfeaturetreewidget></div>',
              showClose: true,
              onclose: function () {
                SelectManager.clear();
              },
            });
          }

          def.resolve();
          return def.promise;
        },

        /**
         * Intervention simple
         * EditFeatureFromList
         */
        iSimple_EditFeatureFromList: (objects, callbacks) => {
          if (callbacks && typeof callbacks.reloadActions === 'function') {
            callbacks.reloadActions();
          }
          var def = $q.defer();
          if (callbacks && typeof callbacks.openEditionTabWith === 'function') {
            callbacks.openEditionTabWith(objects.selectedObject);
          }
          def.resolve();
          return def.promise;
        },

        interventionSimple_DeleteFeatureFromList: function (objects, fti) {
          let def = $q.defer();

          // delete feature on DB
          let isConfirmed = confirm(
            $filter('translate')('datatable.confirm_delete')
          );
          if (isConfirmed == true) {

            $rootScope.$broadcast('showDatatableLoader');

            let idsToDelete = [];
            // get the id number out of "anciennes_filieres_point.5574"
            let indexDot = objects.selectedObject.id.indexOf('.');
            if (indexDot != -1) {
              idsToDelete[0] = objects.selectedObject.id.substr(indexDot + 1);
            }

            EditFactory.remove(fti.uid, idsToDelete).then(
              function (res) {
                // success
                $rootScope.$broadcast('elasticFilterDeleteItems', {
                  uid: fti.uid,
                  name: fti.name,
                  delete: res.data.delete,
                });

                def.resolve();
              },
              function (res) {
                require('toastr').error(
                  $filter('translate')('datatable.unable_to_delete')
                );
                console.log(res.data.errors);
              }
            ).finally(() => {
              $rootScope.$broadcast('hideDatatableLoader');
            });
          }

          return def.promise;
        },

        /**
         * Intervention simple
         * Action du bouton de reprogrammation
         */
        iSimple_ReprogramFeatureFromList: (objects, callbacks) => {
          const def = $q.defer();
          if (callbacks && typeof callbacks.reloadActions === 'function') {
            callbacks.reloadActions();
          }
          if (callbacks && typeof callbacks.reprogramEditionTabWith === 'function') {
            callbacks.reprogramEditionTabWith(objects.selectedObject);
          }
          def.resolve();
          return def.promise;
        },

        MaintaSave: function (objects, source, urlToken, urlSetXY, param) {
          var def = $q.defer();
          var coords = objects.res[source].geometry.coordinates;
          // convertir les coordonnees de la geometrie de l'objet
          // current en 4326
          var coords2 = ol.proj.transform(
            coords,
            objects.map.getView().getProjection().getCode(),
            'EPSG:4326'
          );
          var x = coords2[0];
          var y = coords2[1];
          var promise = $http.get(urlToken);
          promise.then(function (res) {
            var obj = x2js.xml_str2json(res.data);
            // var secu = obj['MOS_UID'];
            var secu = obj.document.MOS_UID;
            // var urlWS
            // ='http://'+nomDomaine+'/'+nomAlias+'/'+url2+nomWS+'&ID_NUMB=1000'+'&X='+x+'&Y='+y+'&MOS_UID='+secu+'&ID_X7=10';
            param = param.replace('{x}', x);
            param = param.replace('{y}', y);
            param = param.replace('{cle}', secu);
            var props = objects.res[source].properties;
            var att = null;
            Object.keys(props).forEach(function (key) {
              if (param.indexOf(key) != -1) {
                att = key;
              }
            });
            if (att != null) {
              param = param.replace(
                '{' + att + '}',
                att + '=' + objects.res[source].properties[att]
              );
            } else {
              param = param.replace('{' + att + '}', null);
            }
            var urlWS = urlSetXY + param;
            var promise2 = $http.get(urlWS);
            promise2.then(function (res) {
              console.log(res);
            });
          });
          def.resolve();
          return def.promise;
        },

        /*
         * , executeWS :
         * function(res,source,url,statistic,keysAtt,valuesAtt) { var
         * def = $q.defer(); if (res[source] && res[source].id ==
         * undefined){ var strStat = statistic.replace('/',','); var
         * objStat = JSON.parse(strStat); var keyAttS =
         * keysAtt.replace('/',','); var valuesAttS =
         * valuesAtt.replace('/',','); var keysAttT =keyAttS.split(',') ;
         * var valuesAttT =valuesAttS.split(','); // tableau de valeurs
         * des attributs en fonction des noms des attributs donnés en
         * entrée var tabAtt =[] for (var k=0; k<valuesAttT.length;
         * k++){ tabAtt.push(res[source].properties[valuesAttT[k]]); }
         * //construction de l'objet json en fonction des clefs, valeurs
         * données var objAtt = {}; for(var i=0; i<tabAtt.length; i++){
         * objAtt[keysAttT[i]] = tabAtt[i]; } //construction de l'objet
         * json en fonction des parametres statiques et attributs var
         * result = {}; Object.keys(objStat).forEach((key) =>
         * result[key] = objStat[key]);
         * Object.keys(objAtt).forEach((key) => result[key] =
         * objAtt[key]);
         *
         *
         *
         * var promise = $http.post("http://" + url,result);
         * promise.then(function(res){ if(res.data=="NO"){
         * require('toastr').error("erreur"); def.reject(); }else{
         * def.resolve(); }
         *  }, function(err){ console.log(err); });
         *
         * return def.promise; } else { def.resolve(); return
         * def.promise; } }
         */
        openURL: function (res, url) {
          /* var def = $q.defer(); */

          console.log('openURL (url)' + url);
          var strurl = url.split('$');
          var newurl = '';

          for (var i = 0; i < strurl.length; i++) {
            if (strurl[i].indexOf('[') == 0) {
              var strrep = gaJsUtils.checkNestedProperty(
                strurl[i].replace('[', ''),
                res
              );
              newurl = newurl + strrep;
            } else {
              if (strurl[i].indexOf(']') == 0) {
                strurl[i] = strurl[i].replace(']', '');
              }
              newurl = newurl + strurl[i];
            }
          }

          window.open(
            newurl,
            'openFromKIS',
            'menubar=no, top=100, left=100, width=300, height=200'
          );
          /*
           * def.resolve(); return def.promise;
           */
        },

        sendEmailtoUser: function (res, login, message, subject, variable) {
          var def = $q.defer();
          console.log(
            'sendEmailtoUser (user , message , from )' +
              message +
              subject +
              variable
          );
          if (angular.isDefined(variable)) {
            const varlogin = formFunctionUtils.getDataValueByKey(res, variable);
            if (angular.isDefined(varlogin)) {
              login = [login, varlogin].join(';');
            }
          }
          FunctionFactory.execute(
            {
              parameters: buildMailDescription(
                login.split(';'),
                subject,
                message,
                res
              ),
            },
            'sendMailToUser',
            'ja'
          ).then(function (res) {
            // -- If mail correctly sent then store it
            // so that we'll have
            // -- the trace of the sent mail.
            if (res.data) {
              require('toastr').success(
                $filter('translate')(
                  'functions_browser.functions.sendEmailtoUser.success'
                )
              );
            } else {
              require('toastr').error(
                $filter('translate')(
                  'functions_browser.functions.sendEmailtoUser.error'
                )
              );
            }
          });
          console.log('sendEmailtoUser (user , message , from )');
          def.resolve();
          return def.promise;
        },

        sendEmailtoGroup: function (res, group, message, subject, variable) {
          var def = $q.defer();
          console.log(
            'sendEmailtoGroup (user , message , from )' + message + subject
          );
          if (angular.isDefined(variable)) {
            const varlogin = formFunctionUtils.getDataValueByKey(res, variable);
            if (angular.isDefined(varlogin)) {
              group = [group, varlogin].join(';');
            }
          }
          FunctionFactory.execute(
            {
              parameters: buildMailDescription(
                group.split(';'),
                subject,
                message,
                res
              ),
            },
            'sendMailToGroup',
            'ja'
          ).then(function (res) {
            // -- If mail correctly sent then store it so that
            // we'll have
            // -- the trace of the sent mail.
            if (res.data === 'true') {
              require('toastr').success(
                $filter('translate')(
                  'functions_browser.functions.sendEmailtoGroup.success'
                )
              );
            } else {
              require('toastr').error(
                $filter('translate')(
                  'functions_browser.functions.sendEmailtoGroup.error'
                )
              );
            }
          });
          console.log('sendEmailtoGroup (user , message , from )');
          def.resolve();
          return def.promise;
        },

        getFeatureFromDb: function (fti, feature, variable) {
          var def = $q.defer();
          QueryFactory.get(fti, feature.id).then(
            function (res) {
              if (res.data && res.data.features) {
                $rootScope.$broadcast('refreshFeature', {
                  variable: variable,
                  refreshedFeature: res.data.features[0],
                });
                def.resolve();
              }
            },
            function () {
              def.resolve();
            }
          );
          return def.promise;
        },
        removeMarker(map) {
          removeMarker(map);
        },

        locateGPScoord: function (longitude, latitude, map) {
          removeMarker(map);
          if (!angular.isDefined(longitude) || !angular.isDefined(latitude))
          {
            require('toastr').error(
              $filter('translate')(
                'geolocalisation.locateGPSCoord_invalid_coordinates'
              )
            );
            return;
          }
          let projectedCoord;
          let mapCode = map.getView().getProjection().getCode();
          if (mapCode === 'EPSG:4326') {
            projectedCoord = [longitude, latitude];
          } else {
            projectedCoord = ol.proj.transform(
              [longitude, latitude],
              'EPSG:4326',
              mapCode
            );
          }

          if (!longitude || !latitude || isNaN(projectedCoord[0])
            || isNaN(projectedCoord[1])) {
            require('toastr').error(
              $filter('translate')('geolocalisation.invalid_address')
            );
          } else {
            marker = kisGeocodageFactory.getMarkerLayer();
            map.addLayer(marker);
            let vectorSource = marker.getSource();
            vectorSource.clear(true);
            map.getView().setCenter(projectedCoord);
            let centerFeature = new ol.Feature({ name: '' });
            centerFeature.setGeometry(new ol.geom.Point(projectedCoord));
            vectorSource.addFeature(centerFeature);
          }
        },
        getFilter: (res, source, destination) => {
          let def = $q.defer();
          let filter;
          console.log(
            'setFilter: {"res": "' +
              JSON.stringify(res) +
              '", "source": "' +
              source +
              '", "destination": "' +
              destination +
              '"}'
          );
          const mapLayers = gclayers.getOperationalLayer();
          for (const layer of mapLayers) {
            if (layer.fti.uid === source) {
              filter = layer.cql_filter ? layer.cql_filter : '';
              break;
            }
          }
          res[destination] = filter;
          def.resolve();
          return def.promise;
        },
        buildFilter: (res, variable, destination) => {
          console.log(
            'setFilter: {"res": "' +
              JSON.stringify(res) +
              '", "variable": "' +
              variable +
              '", "destination": "' +
              destination +
              '"}'
          );
          let def = $q.defer();
          let filter = '';
          const source = res[variable];
          // quel est le type de la source? FeatureCollection? Feature? Array?
          if (source) {
            if (source.type === 'FeatureCollection') {
              if (source.features && source.features.length > 0) {
                const features = source.features;
                const idFeatures = features.map((f) => f.id);
                for (let i = 0; i < idFeatures.length; i++) {
                  if (idFeatures[i].split('.').length > 1) {
                    const length = idFeatures[i].split('.').length;
                    if (i === 0) {
                      filter = 'fid=' + idFeatures[i].split('.')[length - 1];
                    } else {
                      filter +=
                        ' OR fid=' + idFeatures[i].split('.')[length - 1];
                    }
                  }
                }
              }
            } else if (source.type === 'Feature') {
              if (source.id.split('.').length > 1) {
                filter = 'fid=' + source.id.split('.')[1];
              }
            } else if (Array.isArray(source) && !source.type) {
              if (source.length > 0) {
                const idFeatures = source.map((f) => f.id);
                for (let i = 0; i < idFeatures.length; i++) {
                  if (idFeatures[i].split('.').length > 1) {
                    const length = idFeatures[i].split('.').length;
                    if (i === 0) {
                      filter = 'fid=' + idFeatures[i].split('.')[length - 1];
                    } else {
                      filter +=
                        ' OR fid=' + idFeatures[i].split('.')[length - 1];
                    }
                  }
                }
              }
            } else if (!Array.isArray(source) && !source.type) {
              if (source.id.split('.').length > 1) {
                const length = source.id.split('.').length;
                filter = 'fid=' + source.id.split('.')[length - 1];
              }
            }
          }
          res[destination] = filter;
          def.resolve();
          return def.promise;
        },
        setFilter: (res, target, filter, addFilter) => {
          console.log(
            'setFilter: {"target": "' +
              target +
              '", "filter": "' +
              res[filter] +
              '", "addFilter": "' +
              addFilter +
              '"}'
          );
          let def = $q.defer();
          const mapLayers = gclayers.getOperationalLayer();
          for (const layer of mapLayers) {
            if (layer.fti.uid === target) {
              let filterBuilded = res[filter];

              // réinitialisation du filtre
              layer.cql_filter = layer.cql_filter ? layer.cql_filter : '';

              // ajout du nouveau filtre (ajoute ou remplace)
              const ajouteAuFiltreExistant =
                addFilter.includes('true') &&
                layer.cql_filter.length > 0 &&
                !layer.cql_filter.includes(filterBuilded);
              filterBuilded = ajouteAuFiltreExistant
                ? layer.cql_filter + ' AND ' + filterBuilded
                : filterBuilded;
              layer.cql_filter = filterBuilded.replace('1=1 AND ', '');
              // rechargement de la carte
              $rootScope.$broadcast('gcOperationalLayerChange', layer, 'cql_filter');
            }
          }
          def.resolve();
          return def.promise;
        },
        controlDate: (res, dateDeDebut, dateDeFin, regleControle) => {
          //KIS-3647| On parse la chaîne en JSON si regleControle est de type string.
          if(typeof regleControle === 'string'){
            try {
              regleControle = JSON.parse(regleControle);
            } catch (error) {
              console.log(error);
            }
          }
          // Fonction imbriquée uniquement utilisée ci-dessous
          // Récupère la propriété date dans les imbrications d'objets (ex. current.properties.date_debut)
          const getNestedAttribute = (object, attributePath) => {
            let result = object;
            if (attributePath.includes('.')) {
              const nestedArray = attributePath.split('.');
              for (const property of nestedArray) {
                result = result && result.hasOwnProperty(property) ? result[property] : null;
              }
            }
            return result;
          };

          let def = $q.defer();

          // détecte si l'argument est une date saisie (01-01-2021) ou un attribut (ex. current.properties.date_debut)
          const isdateDebutTypeOfDate = moment(
            dateDeDebut,
            'DD/MM/YYYY',
            true
          ).isValid();
          const isdateFinTypeOfDate = moment(
            dateDeFin,
            'DD/MM/YYYY',
            true
          ).isValid();

          // récupère la date
          // soit dans l'argument (si l'argument est une date valide)
          // soit dans l'objet
          let dateDebut = isdateDebutTypeOfDate
            ? new Date(moment(dateDeDebut,'DD/MM/YYYY').format('YYYY-MM-DD'))
            : getNestedAttribute(res, dateDeDebut);
          let dateFin = isdateFinTypeOfDate
            ? new Date(moment(dateDeFin,'DD/MM/YYYY').format('YYYY-MM-DD'))
            : getNestedAttribute(res, dateDeFin);

          // on passe le contrôle de date si l'une des dates n'est pas renseignée
          if (!dateDebut || !dateFin){
            const doesNotExist = $filter('translate')('functions_browser.functions.controlDate.dateAbsent');
            if (!dateDebut){
              require('toastr').warning($filter('translate')(
                'functions_browser.functions.controlDate.beginDate') + doesNotExist);
            }else{
              require('toastr').warning($filter('translate')(
                'functions_browser.functions.controlDate.endDate') + doesNotExist);
            }
            def.resolve();
            return def.promise;
          }

          console.log(
            'controlDate: {"date de Début": "' +
              moment(dateDebut).format('DD/MM/YYYY') +
              '"(' +
              (isdateDebutTypeOfDate ? 'date' : 'attribut') +
              '), "date de Fin": "' +
              moment(dateFin).format('DD/MM/YYYY') +
              '"(' +
              (isdateFinTypeOfDate ? 'date' : 'attribut') +
              '), "règle": "' +
              regleControle.label +
              '"}'
          );

          // vérifie la cohérence des dates
          // suivant la règle choisie dans le select
          let isRuleChecked = false;
          if (typeof dateDebut === 'string'){
            dateDebut = moment(dateDebut,moment.ISO_8601,true).toDate();
          }
          if (typeof dateFin === 'string'){
            dateFin = moment(dateFin,moment.ISO_8601,true).toDate();
          }

          switch (parseInt(regleControle.id)) {
            case 0:
              isRuleChecked = dateDebut < dateFin;
              break;
            case 1:
            // comparaison de date en en ignorant le temps
            // 86400000 est le nombre de ms dans un jour
              isRuleChecked = dateDebut < dateFin || Math.floor(
                Date.parse(dateDebut) / 86400000) === Math.floor(
                Date.parse(dateFin) / 86400000);
              break;
            case 2:
              isRuleChecked = dateFin > dateDebut;
              break;
            case 3:
              isRuleChecked = dateFin > dateDebut || Math.floor(
                Date.parse(dateDebut) / 86400000) === Math.floor(
                Date.parse(dateFin) / 86400000);
              break;
          }

          // si la règle est vérifiée on poursuit l'action
          if (isRuleChecked) {
            console.log('controlDate: test réussi');
            def.resolve();
            return def.promise;

          } else {
            // si la règle n'est pas vérifiée on arrête l'action
            // on affiche une alerte d'erreur
            const debutLog = moment(dateDebut).format('DD/MM/YYYY');
            const finLog = moment(dateFin).format('DD/MM/YYYY');
            swal({
              title:'',
              text:
                  $filter('translate')(
                    'functions_browser.functions.controlDate.nonCompliance'
                  ) + regleControle.label + '\n' + 'Date début = ' + debutLog
                  + '\n' + 'Date fin = ' + finLog,
              type: 'warning',
              showCancelButton: false,
              confirmButtonColor: '#DD6B55',
              confirmButtonText: $filter('translate')('common.ok'),
              closeOnConfirm: true,
            });
            def.reject('rejected');
            return def.promise;
          }
        },
        deactivateFilter: (res, filter) => {
          let def = $q.defer();
          ParametersFactory.getDefaultFilterByTitle(filter).then(
            result => {
              defaultFiltersFactory.removeFilter(result.data);
              def.resolve();
              $rootScope.$emit('removeFilterToolBarAction');
            },
            (error) => {
              require('toastr').error(
                $filter('translate')('default_filters.config.titleerror')
                    + '\n' + filter);
              console.error('deactivateFilter : ', error);
              def.reject('rejected');
            }
          );
          return def.promise;
        },
        getfunctions: function () {
          var px = 'functions_browser.functions.';
          var functions = [
            {
              label: 'alertMessage',
              template: 'alertMessage(\'message\')',
              parameters: buildParameters(px, 'alertMessage', 1),
              description: px + 'alertMessage.description',
            },
            {
              label: 'exportPesv2',
              template: 'exportPesv2(\'app name\',\'fti\')',
              parameters: buildParameters(px, 'exportPesv2', 2),
              description: px + 'exportPesv2.description',
            },
            {
              label: 'geocoderSearch',
              template:
                'kisgeocodagesearch(\'ip\',\'source\',\'attsource\',\'destination\')',
              parameters: buildParameters(px, 'kisgeocodagesearch', 4),
              description: px + 'kisgeocodagesearch.description',
            },
            {
              label: 'geocoderReverseGeometry',
              template:
                'kisgeocodagereverse(\'ip\',\'source\',\'destination\',\'attribut_source\',\'attribut_destination\')',
              parameters: buildParameters(px, 'kisgeocodagereverse', -1, [
                [0, 'string'],
                [1, 'string'],
                [4, 'object'],
                [2, 'string'],
                [3, 'object'],
              ]),
              description: px + 'kisgeocodagereverse.description',
            },
            {
              label: 'confirmDialog',
              template: 'confirmDialog(\'message\')',
              parameters: buildParameters(px, 'confirmDialog', 1),
              description: px + 'confirmDialog.description',
            },
            {
              label: 'replaceCurrentBySelection',
              template: 'replaceCurrentBySelection(source, destination)',
              parameters: buildParameters(px, 'replaceCurrentBySelection', 2),
              description: px + 'replaceCurrentBySelection.description',
            },
            {
              label: 'openForm',
              template: 'openForm(\'form\',\'ressource\',\'tab-false\')',
              parameters: buildParameters(px, 'openForm', -1, [
                [0, 'string'],
                [1, 'string'],
                [2, 'boolean'],
              ]),
              description: px + 'openForm.description',
            },
            {
              label: 'closeForm',
              template: 'closeForm()',
              description: px + 'closeForm.description',
            },
            {
              label: 'clearDrawLayer',
              template: 'clearDrawLayer()',
              description: px + 'clearDrawLayer.description',
            },
            {
              label: 'clearHighLayer',
              template: 'clearHighLayer()',
              description: px + 'clearHighLayer.description',
            },
            /*
             * { label:"addObject", "template":"addObject ( source)" }, {
             * label:"removeObject", "template":"removeObject (
             * source)" },
             */ {
              label: 'updateObject',
              template: 'updateObject (\'source\',\'\')',
              parameters: buildParameters(px, 'updateObject', -1, [
                [0, 'variable'],
                [1, 'fti'],
              ]),
              description: px + 'updateObject.description',
            },
            /*
             * { label:"getObjectRelated",
             * "template":"getObjectRelated ( source)" }, {
             * label:"removeRelation", "template":"removeRelation (
             * source)" },
             */ {
              label: 'addRelation',
              template:
                'addRelation(objectsrc, lsobjectsdestination,fid,relname)',

              parameters: buildParameters(px, 'addRelation', 4),
              description: px + 'addRelation.description',
            },
            {
              label: 'generateFormMobileObject',
              template:
                'generateFormMobileObject(objectsrc,fid,nom_formulaire,date,id,agent_attribut)',

              parameters: buildParameters(px, 'generateFormMobileObject', 6),
              description: px + 'generateFormMobileObject.description',
            },
            {
              label: 'deleteFormMobileObject',
              template:
                'deleteFormMobileObject(objectsrc,fid,nom_formulaire,date,id,agent_attribut)',

              parameters: buildParameters(px, 'deleteFormMobileObject', 6),
              description: px + 'deleteFormMobileObject.description',
            },
            {
              label: 'addEventCalendar',
              template:
                'addEventCalendar(objectsrc,datedebut,datefin,adresse,agent,type_event,title,fidcurrent)',
              parameters: buildParameters(px, 'addEventCalendar', 8),
              description: px + 'addEventCalendar.description',
            },
            {
              label: 'generateBuffer',
              template:
                'generateBuffer(source,destination,bufferType, bufferSize)',
              parameters: buildParameters(px, 'generateBuffer', -1, [
                [0, 'variable'],
                [1, 'variable'],
                [2, 'selectString', [
                  {
                    value: "'ROUND'",
                    label: $filter('translate')(
                      'functions_browser.functions.generateBuffer.ROUND')
                  }, {
                    value: "'SQUARE'",
                    label: $filter('translate')(
                      'functions_browser.functions.generateBuffer.SQUARE')
                  }, {
                    value: "'FLAT'",
                    label: $filter('translate')(
                      'functions_browser.functions.generateBuffer.FLAT')
                  }
                ]
                ],
                [3, 'double positif']]),
              description: px + 'generateBuffer.description',
            },
            {
              label: 'generateUnion',
              template: 'generateUnion(source,destination1,destination2,,0,\'\',\'false\',\'true\')',
              parameters: buildParameters(px, 'generateUnion', -1, [
                [0, 'string'],
                [1, 'string'],
                [2, 'string'],
                [3, 'string'],
                [4, 'select', [
                  {
                    id: '0',
                    label: $filter('translate')(
                      'functions_browser.functions.generateUnion.INTERSECTS'),
                    operator: 'INTERSECTS'
                  }, {
                    id: '1',
                    label: $filter('translate')(
                      'functions_browser.functions.generateUnion.WITHIN'),
                    operator: 'WITHIN'
                  }
                ]],
                [5, 'string'],
                [6, 'string'],
                [7, 'string']
              ]),
              description: px + 'generateUnion.description',
            },
            {
              label: 'setDataFromCollection',
              template: 'setDataFromCollection(TypeRecherche,\'Destination\',\'Source\',Composants,\'tab-false\')',
              parameters: buildParameters(px, 'setDataFromCollection', -1, setDataFromCollectionFunction.getParametersDesc()),
              description: px + 'setDataFromCollection.description',
            },
            {
              label: 'getFeaturesIntersectingPoint',
              template: 'getFeaturesIntersectingPoint(\'Source\',,\'tab-false\',\'Destination\')',
              parameters: buildParameters(px, 'getFeaturesIntersectingPoint', -1, getFeaturesIntersectingFunction.getParametersDesc('Point')),
              description: px + 'getFeaturesIntersectingPoint.description',
            },
            {
              label: 'getFeaturesIntersectingLine',
              template: 'getFeaturesIntersectingLine(\'Source\',,\'tab-false\',\'tab-false\',\'Destination\')',
              parameters: buildParameters(px, 'getFeaturesIntersectingLine', -1, getFeaturesIntersectingFunction.getParametersDesc('Line')),
              description: px + 'getFeaturesIntersectingLine.description',
            },
            {
              label: 'getFeaturesIntersectingPolygon',
              template: 'getFeaturesIntersectingPolygon(\'Source\',,\'tab-false\',0,\'Destination\')',
              parameters: buildParameters(px, 'getFeaturesIntersectingPolygon', -1, getFeaturesIntersectingFunction.getParametersDesc('Polygon')),
              description: px + 'getFeaturesIntersectingPolygon.description',
            },
            {
              label: 'setCountFromCollection',
              template: "setCountFromCollection('Destination','Source',Composant)",
              parameters: buildParameters(px, 'setCountFromCollection', -1, setCountFromCollectionFunction.getParametersDesc()),
              description: px + 'setCountFromCollection.description',
            },
            {
              label: 'getCurrentClause',
              template: 'getCurrentClause(source,destination)',
              parameters: buildParameters(px, 'getCurrentClause', 2),
              description: px + 'getCurrentClause.description',
            },
            {
              label: 'getElementsNetwork',
              template: 'getElementsNetwork(source,destination)',
              parameters: buildParameters(px, 'getElementsNetwork', 2),
              description: px + 'getElementsNetwork.description',
            },
            {
              label: 'getFirstElementNetwork',
              template: 'getFirstElementNetwork(source,destination,type)',
              parameters: buildParameters(px, 'getFirstElementNetwork', 3),
              description: px + 'getFirstElementNetwork.description',
            },
            {
              label: 'getLastElementNetwork',
              template: 'getLastElementNetwork(source,destination,type)',
              parameters: buildParameters(px, 'getLastElementNetwork', 3),
              description: px + 'getLastElementNetwork.description',
            },
            {
              label: 'delObject',
              template: 'delObject(\'\',\'object\',\'tab-true\', \'\')',
              parameters: buildParameters(px, 'delObject', -1, [
                [0, 'fti'],
                [1, 'variable'],
                [2, 'boolean'],
                [3, 'string'],
              ]),
              description: px + 'delObject.description',
            },
            {
              label: 'delRelation',
              template:
                'delRelation(objectsrc,lsobjectsdestination,fid,relname,messageConfirmation)',
              parameters: buildParameters(px, 'delRelation', 5),
              description: px + 'delRelation.description',
            },
            {
              label: 'setData',
              template: 'setData(\'destination\',\'source\',\'\',\'tab-false\')',
              parameters: buildParameters(px, 'setData', -1,
                [[0, 'variable'],
                  [1, 'variable'],
                  [2, 'string'],
                  [3, 'boolean']]
              ),
              description: px + 'setData.description',
            },
            {
              label: 'appendData',
              template: 'appendData(\'destination\',\'source\')',
              parameters: buildParameters(px, 'appendData', 2),
              description: px + 'appendData.description',
            },
            {
              label: 'controlTotalFeatures',
              template: 'controlTotalFeatures(\'source\',\'maximum\',\'message\')',
              parameters: buildParameters(px, 'controlTotalFeatures', -1,
                [[0, 'string'],
                  [1, 'entier'],
                  [2, 'string']]),
              description: px + 'controlTotalFeatures.description',
            },
            {
              label: 'setClauseWhere',
              template: 'setClauseWhere(\'destination\',\'source\')',
              parameters: buildParameters(px, 'setClauseWhere', -1, [[0, 'variable'], [1, 'string']]),
              description: px + 'setClauseWhere.description',
            },
            {
              label: 'setStaticData',
              template: 'setStaticData(\'destination\',\'source\',\'value\')',
              parameters: buildParameters(px, 'setStaticData', 3),
              description: px + 'setStaticData.description',
            },
            {
              label: 'getObjectById',
              template: 'getObjectById(\'destination\',\'id\')',
              description: px + 'getObjectById.description',
            },
            {
              label: 'showTreeBranch',
              template: 'showTreeBranch(\'path\')',
              parameters: buildParameters(px, 'showTreeBranch', 1),
              description: px + 'showTreeBranch.description',
              /* configurationTemplate : "<tree-branch-picker></tree-branch-picker>" */
            },
            {
              label: 'hideTreeBranch',
              template: 'hideTreeBranch(\'path\')',
              parameters: buildParameters(px, 'hideTreeBranch', 1),
              description: px + 'hideTreeBranch.description',
            },
            {
              label: 'goToTreeBranch',
              template: 'goToTreeBranch(\'path\')',
              parameters: buildParameters(px, 'goToTreeBranch', 1),
              description: px + 'goToTreeBranch.description',
            },
            {
              label: 'concatAttributFromFeatures',
              template:
                'concatAttributFromFeatures(\'source\',\'attsource\',\'separator\',\'destination\',\'attdestination\')',
              parameters: buildParameters(px, 'concatAttributFromFeatures', 5),
              description: px + 'concatAttributFromFeatures.description',
            },
            {
              label: 'sumAttributFromFeatures',
              template:
                'sumAttributFromFeatures(\'source\',\'attsource\',\'destination\',\'attdestination\')',
              parameters: [
                {
                  title: px + 'sumAttributFromFeatures.p.0.title',
                  desc: px + 'sumAttributFromFeatures.p.0.desc',
                  type: 'string',
                },
                {
                  title: px + 'sumAttributFromFeatures.p.1.title',
                  desc: px + 'sumAttributFromFeatures.p.1.desc',
                  type: 'string',
                },
                {
                  title: px + 'sumAttributFromFeatures.p.2.title',
                  desc: px + 'sumAttributFromFeatures.p.2.desc',
                  type: 'string',
                },
                {
                  title: px + 'sumAttributFromFeatures.p.3.title',
                  desc: px + 'sumAttributFromFeatures.p.3.desc',
                  type: 'string',
                },
              ],
              description: px + 'sumAttributFromFeatures.description',
            },
            {
              label: 'calculExpression',
              template: 'calculExpression(\'source\',\'destination\',\'expression\')',
              parameters: buildParameters(px, 'calculExpression', 4),
              description: px + 'calculExpression.description',
            },

            {
              label: 'calculInterval',
              template: 'calculInterval(\'source1\',\'source2\',\'destination\',\'unite\',\'strict\')',
              parameters: buildParameters(px, 'calculInterval', 4, [
                [0, 'variable'],
                [1, 'variable'],
                [2, 'variable'],
                [3, 'radioCalculInterval',
                  [{
                    id: 10,
                    label:'heure',
                    value:`'hour'`
                  },
                  {
                    id: 0,
                    label:'jour',
                    value: '\'day\''
                  },
                  {
                    id: 1,
                    label:'mois',
                    value: '\'month\''
                  },
                  {
                    id: 2,
                    label:'année',
                    value: '\'year\''
                  }]
                ],
                [4, 'radioCalculInterval',
                  [{
                    id: 0,
                    label:'Calcul strict de l\'écart',
                    value: true
                  },
                  {id: 1,
                    label:'Calcul strict de l\'écart + 1 (la journée, le mois ou l\'année commencée compte pour 1)',
                    value: false
                  }]
                ]
              ]),
              description: px + 'calculInterval.description',
            },

            {
              label: 'rechercheAmontAval',
              template: 'rechercheAmontAval(300,300,\'resultat\')',
              parameters: buildParameters(px, 'rechercheAmontAval', -1, networkfunctions.getParametersDesc()),
              description: px + 'rechercheAmontAval.description',
            },

            {
              label: 'snapShot',
              template: 'snapshot(\'resultat\')',
              parameters: buildParameters(px, 'snapshot', -1, snapshotfunction.getParametersDesc()),
              description: px + 'snapshot.description',
            },
            {
              label: 'copyDataFromWebService',
              template: 'copyDataFromWebService(\'\',\'\')',
              parameters: buildParameters(px, 'copyDataFromWebService', -1, transferLayerFunctions.getParametersDesc()),
              description: px + 'copyDataFromWebService.description',
            },
            {
              label: 'setUUIDtoAttribute',
              template:
                'setUUIDtoAttribute(\'concat\',\'source\',\'attdestination\',\'replace\')',
              parameters: buildParameters(px, 'setUUIDtoAttribute', 4),
              description: px + 'setUUIDtoAttribute.description',
            },
            {
              label: 'printDocXForm',
              template: 'printDocXForm(\'docname\')',
              parameters: buildParameters(px, 'printDocXForm', 1),
              description: px + 'printDocXForm.description',
            },
            {
              label: 'executeWS',
              template:
                'executeWS(source,url,{keyStat1:valueStat1/keyStat2:valueStat2},keyAtt1/keyAtt2,valueAtt1/valueAtt2)',
              parameters: buildParameters(px, 'executeWS', 5),
              description: px + 'executeWS.description',
            },
            {
              label: 'omegaRecherchePointConsommation',
              template: 'omegaRecherchePointConsommation(res,\'filtres\')',
              parameters: buildParameters(
                px,
                'omegaRecherchePointConsommation',
                3
              ),
              description: px + 'omegaRecherchePointConsommation.description',
            },
            {
              label: 'LocaliseAndDisplayInformation',
              template: 'LocaliseAndDisplayInformation(true)',
              parameters: buildParameters(px, 'LocaliseAndDisplayInformation', -1, [
                [0, 'switch'],
              ]),
              description: px + 'LocaliseAndDisplayInformation.description',
            },
            {
              label: 'MaintaSave',
              template: 'MaintaSave(source,urlToken,urlSetXY,param)',
              parameters: buildParameters(px, 'MaintaSave', 4),
              description: px + 'MaintaSave.description',
            },
            {
              label: 'maintaSave2',
              template: 'maintaSave2(source,urlSetXY,xApiKey)',
              parameters: buildParameters(px, 'maintaSave2', -1, maintaSave2Function.getParametersDesc()),
              description: px + 'maintaSave2.description',
            },
            {
              label: 'openURL',
              template: 'openURL(\'url\')',
              parameters: buildParameters(px, 'openURL', 1),
              description: px + 'openURL.description',
            },
            {
              label: 'sendEmailtoUser',
              template: 'sendEmailtoUser(\'users\',\'message\',\'subject\',\'\')',
              parameters: buildParameters(px, 'sendEmailtoUser', 4),
              description: px + 'sendEmailtoUser.description',
            },
            {
              label: 'sendEmailtoGroup',
              template: 'sendEmailtoUser(\'users\',\'message\',\'subject\',\'\')',
              parameters: buildParameters(px, 'sendEmailtoUser', 4),
              description: px + 'sendEmailtoGroup.description',
            },
            {
              label: 'broadcastMessage',
              parameters: buildParameters(px, 'broadcastMessage', 1),
              template: 'broadcast(\'message\')',
              description: px + 'broadcastMessage.description',
            },
            {
              label: 'loadRelation',
              parameters: buildParameters(px, 'loadRelation',  -1, [
                [0, 'fti'],
                [1, 'relation'],
                [2, 'variable']
              ]),
              template:
                'loadRelation(\'featureId\',\'relation_name\',\'destination\')',
              description: px + 'loadRelation.description',
            },
            {
              label: 'loadRelations',
              parameters: buildParameters(px, 'loadRelations',  -1, [
                [0, 'fti'],
                [1, 'relation'],
                [2, 'variable']
              ]),
              template:
                'loadRelations(\'featureId\',\'relation_name\',\'destination\')',
              description: px + 'loadRelations.description',
            },
            {
              label: 'getFeatureFromDb',
              parameters: buildParameters(px, 'getFeatureFromDb', -1, [
                [0, 'fti'],
                [1, 'variable'],
              ]),
              template: 'getFeatureFromDb(\'fti\',\'featureVariable\')',
              description: px + 'getFeatureFromDb.description',
            },
            {
              label: 'locateGPScoord',
              parameters: buildParameters(px, 'locateGPScoord', -1, [
                [0, 'string'],
                [1, 'string'],
              ]),
              template: 'locateGPScoord(\'longitude\', \'latitude\')',
              description: px + 'locateGPScoord.description',
            },
            {
              label: 'controlDate',
              template: 'controlDate("Date de début", "Date de fin")',
              parameters: buildParameters(px, 'controlDate', -1, [
                [0, 'dateSwitch'],
                [1, 'dateSwitch'],
                [2, 'select', this.getControlDateOptions()],
              ]),
              description: px + 'controlDate.description',
            },
            {
              label: 'getFilter',
              template: 'getFilter(\'featureId\',\'destination\')',
              parameters: buildParameters(px, 'getFilter', 2),
              description: px + 'getFilter.description',
            },
            {
              label: 'buildFilter',
              template: 'buildFilter(\'source\',\'destination\',\'\')',
              parameters: buildParameters(px, 'buildFilter', 2),
              description: px + 'buildFilter.description',
            },
            {
              label: 'setFilter',
              template: 'setFilter(\'source\',\'destination\',\'tab-false\')',
              // on appelle l'objet 'setFilterMsg' pour ne pas faire doublon de clé dans la locale
              parameters: buildParameters(px, 'setFilterMsg', 3),
              description: px + 'setFilter.description',
            },
            {
              label: 'deactivateFilter',
              template: 'deactivateFilter(\'source\')',
              parameters: buildParameters(px, 'deactivateFilter', 1,[
                [0, 'string']
              ]),
              description: px + 'deactivateFilter.description',
            },
            {
              label: 'executeSetDefaultValueByRole',
              template: 'executeSetDefaultValueByRole()',
              description: px + 'executeSetDefaultValueByRole.description',
            },
            {
              label: 'setCurrentDate',
              template: `setCurrentDate('Destination','Attribut','Remplacer une date existante')`,
              parameters: buildParameters(px, 'setCurrentDate', -1, setCurrentDateFunction.getParametersDesc()),
              description: px + 'setCurrentDate.description',
            }
          ];

          for (var i in functions) {
            if (functions[i].hasOwnProperty('description')) {
              functions[i].description = $sce.trustAsHtml(
                $filter('translate')(functions[i].description)
              );
            }
          }
          return functions.sort((a, b) => a.label.localeCompare(b.label));
        },

        /**
         *
         * @param {string} expressions the function to call
         * @param {*} objects
         * @param {*} templatefields
         * @param {object|[object]} ftis fti of the current object.
         * ftis est un tableau quand scope.fti de la directive parent est vide
         * @param {*} eventOrigin
         * @param {*} callbacks you can pass callbacks through this parameter
         *        format: { myCallBack1: () =>{code}, myCallBack2: () =>{code} }
         * @return
         */
        evaluateExpression: function (expressions, objects, templatefields,
          ftis, eventOrigin, callbacks) {
          console.log(arguments);
          var evaluateExpressionPromise = $q.defer();

          if (!angular.isDefined(expressions)) {
            return evaluateExpressionPromise.promise;
          }
          var self = this;

          var extractArguments = function () {
            var res = [];
            const args = formFunctionUtils.deleteSurrondingQuotes(arguments);
            res.push(...args);
            return res;
          };

          /**
           * exececute next functions from expressions array
           *
           * @param index
           */
          var execNext = function (index) {
            // all functions were executed
            if (!angular.isDefined(expressions[index])) {
              evaluateExpressionPromise.resolve();
              return;
            }

            var exp = expressions[index];
            let fti;
            if (Array.isArray(ftis) && ftis.length > index) {
              // cas où l'objet n'est pas enregistré (scope.fti = {} dans formFieldRender)
              // ftis[index] est le fti de "current" de la fonction expressions[index]
              fti = ftis[index];
            } else {
              fti = ftis;
            }

            var pos_ouv = exp.indexOf('(');
            var functionName = exp.substring(0, pos_ouv).trim();
            let pos_fer = exp.lastIndexOf(')');
            let argsStr = exp.substring(pos_ouv + 1,pos_fer);

            var arrs = argsStr.split(',');


            for (let i = 0; i < arrs.length; i++) {
              // kisbuilderobj
              if (arrs[i].indexOf('kisbuilderobj#') == 0) {
                arrs[i] = arrs[i]
                  .replace(/;/g, ',')
                  .replace('kisbuilderobj#', '');
                argsStr = arrs.join(',');
              }
              // date
              // la date doit être englobée dans des quotes comme une propriété
              // pour ne pas lever d'erreur sur eval()
              const isDate = moment(arrs[i],'DD/MM/YYYY',true).isValid();
              if (isDate){
                arrs[i] = '\'' + arrs[i] + '\'';
                argsStr = arrs.join(',');
              }
            }
            //KIS-3647: Évite de diviser sur les virgules à l'intérieur des objets JSON grâce à la regex /,(?![^{]*})/.
            var args = extractArguments.apply(null, argsStr.split(/,(?![^{]*})/));
            // args = Function('"use strict";return (extractArguments (' + argsStr + ')')();


            var gonext = function () {
              execNext(index + 1);
            };

            // We need the timeout to make the evaluateExpression
            // happen at the end of the main digest cycle
            $timeout(function () {
              switch (functionName.trim()) {
                case 'alertMessage':
                  self.alertMessage(args[0]).then(gonext);
                  break;
                case 'exportPesv2':
                  self.exportPesv2(objects.res, args).then(gonext);
                  break;
                case 'geocoderSearch':
                  self
                    .geocodagesearch(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'geocoderReverseGeometry':
                  self.geocodagereverse(objects, args).then(gonext);
                  break;
                case 'confirmDialog':
                  self.confirmDialog(args[0]).then(gonext);
                  break;
                case 'replaceCurrentBySelection':
                  self
                    .replaceCurrentBySelection(objects.res, args[0], args[1])
                    .then(gonext);
                  break;
                case 'clearDrawLayer':
                  self.clearDrawLayer().then(gonext);
                  break;
                case 'clearHighLayer':
                  self.clearHighLayer().then(gonext);
                  break;
                case 'closeForm':
                  self.closeForm().then(gonext);
                  break;
                case 'openForm':
                  self
                    .openForm(
                      objects,
                      args[0], args[1], args[2]
                    ).then(gonext);
                  break;
                case 'setData':
                  self
                    .setData(objects.res, fti, args[0], args[1], args[2], args[3])
                    .then(gonext);
                  break;
                case 'appendData':
                  self
                    .appendData(objects.res, args[0], args[1], args[2])
                    .then(gonext);
                  break;
                case 'controlTotalFeatures':
                  self
                    .controlTotalFeatures(objects.res, args[0], args[1], args[2])
                    .then(gonext);
                  break;
                case 'setClauseWhere':
                  self
                    .setClauseWhere(objects.res, args[0], args[1])
                    .then(gonext);
                  break;
                case 'setStaticData':
                  self.setStaticData(objects.res, fti, args).then(gonext);
                  break;
                case 'loadRelation':
                  self
                    .loadRelations(objects.res, args[0], args[1], args[2], 0)
                    .then(gonext);
                  break;
                case 'loadRelations':
                  self
                    .loadRelations(objects.res, args[0], args[1], args[2], 1)
                    .then(gonext);
                  break;
                case 'sendEmail':
                  break;
                case 'addBranchTree':
                  self
                    .addBranchTree(objects.feattree, args[0], args[1])
                    .then(gonext);
                  break;
                case 'delObject':
                  self
                    .delObject(objects.res, args[0], args[1], args[2], args[3])
                    .then(gonext);
                  break;
                case 'updateObject':
                  self
                    .updateObject(
                      objects.res,
                      args[0],
                      args[1],
                      objects.map.getView().getProjection().getCode()
                    )
                    .then(gonext);
                  break;
                case 'delRelation':
                  if (objects.res[args[1]] != null) {
                    self
                      .delRelation(
                        objects.res,
                        args[0],
                        args[1],
                        args[2],
                        args[3],
                        args[4]
                      )
                      .then(gonext);
                  }
                  break;
                case 'addRelation':
                  self
                    .addRelation(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'generateFormMobileObject':
                  self
                    .generateFormMobileObject(
                      objects,
                      args[0],
                      args[1],
                      args[2],
                      args[3],
                      args[4],
                      args[5]
                    )
                    .then(gonext);
                  break;
                case 'deleteFormMobileObject':
                  self
                    .deleteFormMobileObject(
                      objects,
                      args[0],
                      args[1],
                      args[2],
                      args[3],
                      args[4],
                      args[5]
                    )
                    .then(gonext);
                  break;
                case 'addEventCalendar':
                  self
                    .addEventCalendar(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3],
                      args[4],
                      args[5],
                      args[6],
                      args[7]
                    )
                    .then(gonext);
                  break;
                case 'generateBuffer':
                  self
                    .generateBuffer(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'generateUnion':
                  self
                    .generateUnion(
                      objects,
                      fti,
                      args[0],
                      args[1],
                      args[2],
                      args[3],
                      args[4],
                      args[5],
                      args[6],
                      args[7])
                    .then(gonext);
                  break;
                case 'setDataFromCollection':
                  setDataFromCollectionFunction.setDataFromCollection(
                    objects.res,
                    fti,
                    args[0],
                    args[1],
                    args[2],
                    args[3],
                    args[4])
                    .then(gonext);
                  break;
                case 'getFeaturesIntersectingPoint':
                  getFeaturesIntersectingFunction.getFeaturesIntersecting(
                    objects.res,
                    args[0],
                    args[1],
                    args[2] === 'tab-true' || args[2] === '\'tab-true\'',
                    undefined,
                    args[3],
                    'Point')
                    .then(gonext);
                  break;
                case 'getFeaturesIntersectingLine':
                  getFeaturesIntersectingFunction.getFeaturesIntersecting(
                    objects.res,
                    args[0],
                    args[1],
                    args[2] === 'tab-true' || args[2] === '\'tab-true\'',
                    args[3] === 'tab-true' || args[3] === '\'tab-true\'',
                    args[4],
                    'LineString')
                    .then(gonext);
                  break;
                case 'getFeaturesIntersectingPolygon':
                  getFeaturesIntersectingFunction.getFeaturesIntersecting(
                    objects.res,
                    args[0],
                    args[1],
                    args[2] === 'tab-true' || args[2] === '\'tab-true\'',
                    undefined,
                    args[4],
                    'Polygon',
                    args[3])
                    .then(gonext);
                  break;
                case 'setCountFromCollection':
                  setCountFromCollectionFunction.countDataFromCollection(
                    objects.res,
                    fti,
                    args[0],
                    args[1],
                    args[2])
                    .then(gonext);
                  break;
                case 'getElementsNetwork':
                  self
                    .getElementsNetwork(objects.res, args[0], args[1])
                    .then(gonext);
                  break;
                case 'getFirstElementNetwork':
                  self
                    .getFirstElementNetwork(
                      objects.res,
                      args[0],
                      args[1],
                      args[2]
                    )
                    .then(gonext);
                  break;
                case 'getLastElementNetwork':
                  self
                    .getLastElementNetwork(
                      objects.res,
                      args[0],
                      args[1],
                      args[2]
                    )
                    .then(gonext);
                  break;
                case 'getCurrentClause':
                  self
                    .getCurrentClause(objects.res, args[0], args[1])
                    .then(gonext);
                  break;
                case 'showTreeBranch':
                  self.showTreeBranch(objects.feattree, args[0]).then(gonext);
                  break;
                case 'printDocXForm':
                  self.printDocXForm(objects, args[0]).then(gonext);
                  break;
                case 'executeWS':
                  self
                    .executeWS(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3],
                      args[4]
                    )
                    .then(gonext);
                  break;
                case 'omegaRecherchePointConsommation':
                  self
                    .omegaRecherchePointConsommation(
                      objects.res,
                      args[0],
                      args[1]
                    )
                    .then(gonext);
                  break;
                case 'LocaliseAndDisplayInformation':
                  self.LocaliseAndDisplayInformation(objects, args[0]).then(gonext);
                  break;
                case 'iSimple_EditFeatureFromList':
                  self.iSimple_EditFeatureFromList(objects, callbacks).then(gonext);
                  break;
                case 'interventionSimple_DeleteFeatureFromList':
                  self
                    .interventionSimple_DeleteFeatureFromList(objects, fti)
                    .then(gonext);
                  break;
                case 'iSimple_ReprogramFeatureFromList':
                  self.iSimple_ReprogramFeatureFromList(objects, callbacks).then(gonext);
                  break;
                case 'MaintaSave':
                  self
                    .MaintaSave(objects, args[0], args[1], args[2], args[3])
                    .then(gonext);
                  break;
                case 'maintaSave2':
                  $timeout(() => {
                    maintaSave2Function.maintaSave2(objects, args[0], args[1], args[2])
                      .then(gonext);
                  });
                  break;
                case 'sendEmailtoUser':
                  self
                    .sendEmailtoUser(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'sendEmailtoGroup':
                  self
                    .sendEmailtoGroup(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'getFeatureFromDb':
                  self
                    .getFeatureFromDb(args[0], objects.res[args[1]], args[1])
                    .then(gonext);
                  break;
                case 'hideTreeBranch':
                  self.hideTreeBranch(objects.feattree, args[0]).then(gonext);
                  break;
                case 'goToTreeBranch':
                  self.goToTreeBranch(objects.feattree, args[0]).then(gonext);
                  break;
                case 'broadcastMessage':
                  var data = objects.res;
                  if (
                    $rootScope.kisws &&
                    $rootScope.kisws.gcdtActionCurObj &&
                    data == undefined
                  )
                    data = $rootScope.kisws.gcdtActionCurObj;
                  self
                    .broadcastMessage(data, args[0], templatefields)
                    .then(gonext);
                  break;
                case 'setUUIDtoAttribute':
                  // setUUIDtoAttribute: function(res,
                  // cancatchaine,
                  // source,attdestination,replace)
                  self
                    .setUUIDtoAttribute(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'openURL':
                  self.openURL(objects.res, args[0]).then(gonext);
                  break;
                case 'calculExpression':
                  self
                    .calculExpression(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'calculInterval':
                  self
                    .calculInterval(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3],
                      args[4]
                    )
                    .then(gonext);
                  break;
                case 'concatAttributFromFeatures':
                  self
                    .concatAttributFromFeatures(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3],
                      args[4]
                    )
                    .then(gonext);
                  break;
                case 'sumAttributFromFeatures':
                  self
                    .sumAttributFromFeatures(
                      objects.res,
                      args[0],
                      args[1],
                      args[2],
                      args[3]
                    )
                    .then(gonext);
                  break;
                case 'locateGPScoord': {
                  const coordinates = getCoordinates(
                    objects.res,
                    args[0],
                    args[1]
                  );
                  self.locateGPScoord(
                    coordinates.longitude,
                    coordinates.latitude,
                    objects.map,
                    expressions
                  );
                  break;
                }
                case 'controlDate':
                  self
                    .controlDate(objects.res, args[0], args[1], args[2])
                    .then(gonext)
                    .catch(() => {
                      console.log(
                        'les dates ne respectent pas la règle définie dans la fonction'
                      );
                    });
                  break;
                case 'getFilter':
                  self.getFilter(objects.res, args[0], args[1]).then(gonext);
                  break;
                case 'buildFilter':
                  self.buildFilter(objects.res, args[0], args[1]).then(gonext);
                  break;
                case 'setFilter':
                  self
                    .setFilter(objects.res, args[0], args[1], args[2])
                    .then(gonext);
                  break;
                case 'deactivateFilter':
                  self
                    .deactivateFilter(objects.res, args[0])
                    .then(gonext)
                    .catch(() => {
                      console.log(
                        'deactivateFilter: Impossible de trouver le filtre portant le titre: '
                        + args[0]
                      );
                    });
                  break;
                case 'rechercheAmontAval':
                  networkfunctions.searchUpDownStream(objects.res, args[0],
                    args[1], args[2], args[3], objects.map.getView().getProjection().getCode()).then(gonext);
                  break;
                case 'executeSetDefaultValueByRole':
                  editRulesFunctions.executeSetDefaultValueByRole(objects.res, fti, eventOrigin)
                    .then(gonext);
                  break;
                case 'snapShot':
                  snapshotfunction.openSnapshotPopUp(objects.res, objects.map,
                    objects.dlgPopupContainer, args[0]).then(
                    gonext);
                  break;
                case 'copyDataFromWebService':
                  transferLayerFunctions.confirmTransferLayer(objects.res, args[0], args[1])
                    .then(gonext);
                  break;
                case 'setCurrentDate':
                  $timeout(() => {
                    setCurrentDateFunction.setCurrentDate(objects.res, args[0], args[1], args[2])
                      .then(gonext);
                  });
                  break;
                default:
                  break;
              }
            }, 0);
          };

          execNext(0);
          return evaluateExpressionPromise.promise;
        },

        getControlDateOptions() {
          const options = [];
          const dd = $filter('translate')(
            'functions_browser.functions.controlDate.beginDate'
          );
          const df = $filter('translate')(
            'functions_browser.functions.controlDate.endDate'
          );
          const mustBeInf = $filter('translate')(
            'functions_browser.functions.controlDate.mustBeInferior'
          );
          const mustBeSup = $filter('translate')(
            'functions_browser.functions.controlDate.mustBeSuperior'
          );
          const orEq = $filter('translate')(
            'functions_browser.functions.controlDate.orEquals'
          );
          const to = $filter('translate')(
            'functions_browser.functions.controlDate.to'
          );
          options.push({
            id: '0',
            label: dd + mustBeInf + to + df.toLowerCase(),
          });
          options.push({
            id: '1',
            label: dd + mustBeInf + orEq + to + df.toLowerCase(),
          });
          options.push({
            id: '2',
            label: df + mustBeSup + to + dd.toLowerCase(),
          });
          options.push({
            id: '3',
            label: df + mustBeSup + orEq + to + dd.toLowerCase(),
          });
          return options;
        },
      };
    };
    this.$get.$inject = [
      '$timeout',
      'ngDialog',
      'extendedNgDialog',
      'ConfigFactory',
      '$rootScope',
      'FunctionFactory',
      'EditFactory',
      'gclayers',
      '$sce',
      '$q',
      'QueryFactory',
      'gaJsUtils',
      'gaDomUtils',
      'mapJsUtils',
      'dateUtils',
      'DocumentFactory',
      '$filter',
      'kisGeocodageFactory',
      'SelectManager',
      'GeometryFactory',
      '$http',
      'CalendarFactory',
      'x2js',
      'dolibarrFactory',
      'AncAppFactory',
      'BacAppFactory',
      'defaultFiltersFactory',
      'ParametersFactory',
      'FeatureTypeFactory',
      'networkfunctions',
      'editRulesFunctions',
      'snapshotfunction',
      'setDataFromCollectionFunction',
      'getFeaturesIntersectingFunction',
      'gcPopup',
      'setCountFromCollectionFunction',
      'attributeRestrictionsUtils',
      'transferLayerFunctions',
      'setCurrentDateFunction',
      'maintaSave2Function',
      'formFunctionUtils', 'ogcFactory'
    ];
  };
  return gcformfunction;
});
