/*global define, CodeMirror, palette */
define(['angular', 'toastr'], function(angular, toastr) {
  'use strict';

  function asArray(obj) {
    return Array.isArray(obj) ? obj : [obj];
  }

  var styleeditor = function(
    $rootScope,
    $timeout,
    $filter,
    $q,
    QueryFactory,
    StyleFactory,
    FeatureTypeFactory,
    gaJsUtils,
    ngDialog,
    sldUtils,
    hotkeys,
    gaDomUtils,
    attributeRestrictionsUtils
  ) {
    var x2js = sldUtils.x2js;
    var rgbHex = /#[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]/gi;
    var knownTypes = ['LineSymbolizer', 'PointSymbolizer', 'PolygonSymbolizer'];
    const stringTypes = ['g2c.attachment', 'g2c.attachments', 'g2c.hyperlink', 'java.lang.String', 'string'];

    var getProp = gaJsUtils.checkNestedProperty;

    /*
     * Extract the title and preview information from a SLD category rule
     */
    function generatePreview(rule, compareObject) {
      var cssPreview = false;
      var color = '000000';

      // Extract the title and filter value
      var value;
      if (
        rule &&
        rule.kse &&
        rule.kse.value &&
        rule.kse.value != 'kis_sldeditor_default_value'
      ) {
        value = rule.kse.value;
      } else {
        value = 'kis_sldeditor_default_value';
      }
      //var value = 'kis_sldeditor_default_value';
      rule.Title = asArray(rule.Title || rule.Name);
      if (
        rule.Filter &&
        rule.Filter.length &&
        rule.Filter[0].PropertyIsEqualTo
      ) {
        value = rule.Filter[0].PropertyIsEqualTo.Literal;
        rule.Title =
          rule.Title || asArray(rule.Filter[0].PropertyIsEqualTo.Literal);
      }
      // Translate the default value
      /*rule.Title = (value === 'kis_sldeditor_default_value')
                ? [$filter('translate')('model.styles.editor.category.default_value')]
                : rule.Title || [''];*/

      var type = knownTypes.filter(function(x) {
        return rule.hasOwnProperty(x);
      })[0];

      if (type) {
        // If a rule has not been modified (except for its color), it's the same as the generated compareObject
        cssPreview =
          JSON.stringify(rule[type][0]).replace(rgbHex, '#') ===
          JSON.stringify(compareObject[type]);
        if (cssPreview) {
          // Then we can just show a CSS preview (otherwise we display a SLD legend)
          var style;
          switch (type) {
            case 'LineSymbolizer':
              style = rule[type][0].Stroke;
              break;
            case 'PointSymbolizer':
              style = rule[type][0].Graphic.Mark.Fill;
              break;
            case 'PolygonSymbolizer':
              style = rule[type][0].Fill;
              break;
          }
          color = style.CssParameter.__text.replace('#', '');
        }
      }

      rule.kse = {
        value: value,
        color: color,
        label: rule.Title,
        csspreview: cssPreview,
      };
    }

    function link(scope) {
      scope.simpleInterface = angular.isDefined(scope.simpleInterface)
        ? scope.simpleInterface
        : false;
      // ----------------------------------------------------
      // Main interface
      // ----------------------------------------------------
      scope.titleRep = $filter('translate')(
        'model.styles.editor.category.default_value'
      );
      scope.isNewStyle = true;
      scope.global = {
        mode: scope.fti !== undefined ? 'fti' : 'style',
        editmode: 'userinterface',
        opacity: 100,
      };
      //-- Permet d'arrêter le timeout pour symbologie
      //-- quand éditeur de style est fermé.
      scope.isClosed = false;

      scope.builderActions = [
        {
          value: 'savestyle',
          pre: '<em>Alt+S</em><i class="fa fa-save"></i>',
          label: 'common.save',
        },
        /* {
                value: 'savestyleas',
                pre : '<em>Alt+Maj+S</em><i class="fa fa-save"></i>',
                label: 'common.save_as'
            }, */ {
          value: 'newstyle',
          pre: '<em>Alt+N</em><i class="fa fa-plus"></i>',
          label: 'model.styles.editor.menu.new_style',
        },
        {
          value: 'linkstyle',
          pre: '<em>Alt+L</em><i class="fa fa-code-fork"></i>',
          label: 'model.styles.editor.menu.link_style.title',
        },
      ];

      scope.selectedAction = { label: '' };
      scope.builderAction = function() {
        switch (scope.selectedAction.label) {
          case 'savestyle':
            scope.saveStyleModal();
            break;
          case 'newstyle':
            scope.newStyleModal();
            break;
          case 'linkstyle':
            scope.linkStyleModal();
            break;
        }
        // reset action
        scope.selectedAction = { label: '' };
      };

      /*
       * Hotkeys
       */
      hotkeys.add({
        combo: 'alt+s',
        description: $filter('translate')('common.save'),
        allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
        callback: scope.saveStyleModal,
      });
      hotkeys.add({
        combo: 'alt+n',
        description: $filter('translate')('model.styles.editor.menu.new_style'),
        allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
        callback: scope.newStyleModal,
      });
      hotkeys.add({
        combo: 'alt+l',
        description: $filter('translate')(
          'model.styles.editor.menu.link_style'
        ),
        allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
        callback: scope.linkStyleModal,
      });

      scope.toggleCheatSheet = function() {
        hotkeys.toggleCheatSheet();
      };

      /**
       * Remove hotkeys when directive is destroyed
       */
      scope.$on('$destroy', function() {
        hotkeys.del('alt+r'); // ?? wtf
        hotkeys.del('alt+s');
        hotkeys.del('alt+n');
        hotkeys.del('alt+l');
      });

      // ------------------------------------------------------------------------------------------------
      // Save SLD
      // ------------------------------------------------------------------------------------------------

      /**
       * Save Style Modal
       */
      scope.saveStyleModal = function() {
        ngDialog.open({
          template:
            'js/XG/widgets/mapapp/style/views/modals/modal.savestyle.html',
          className: 'ngdialog-theme-plain nopadding miniclose',
          closeByDocument: false,
          scope: scope,
        });
      };
      /**
       * Save the style
       * @return {[type]} [description]
       */
      scope.validName = true;
      scope.checkName = function(name) {
        scope.validName = gaJsUtils.checkCustomRegex(name);
      };

      scope.saveStyle = function() {
        var promise = $q.defer();
        prepareSLD();
        var cleanSld = scope.sld.replace(/&#x2F;/g, '/');
        //console.log('save style');
        if (scope.isNewStyle) {
          StyleFactory.add(cleanSld, scope.currentStyle.data.name).then(
            function(res) {
              if (res.data === 'false') {
                toastr.error(
                  $filter('translate')('model.styles.editor.add_error')
                );
              } else {
                toastr.success(
                  $filter('translate')('model.styles.editor.add_ok')
                );
              }
              FeatureTypeFactory.addstyle(
                scope.fti.uid,
                scope.currentStyle.data.name
              );
              scope.fti.styles.push(scope.currentStyle.data.name);
              scope.isNewStyle = false;
              promise.resolve();
            }
          );
        } else {
          StyleFactory.update(cleanSld, scope.currentStyle.data.name).then(
            function(res) {
              if (res.data === 'false') {
                toastr.error(
                  $filter('translate')('model.styles.editor.update_error')
                );
              } else {
                toastr.success(
                  $filter('translate')('model.styles.editor.update_ok')
                );
              }
              promise.resolve();
              if (scope.simpleInterface && $rootScope.xgos.sector == 'anc') {
                $rootScope.$broadcast(
                  'updateListeStyleCana',
                  scope.jsonSLD.StyledLayerDescriptor.NamedLayer.UserStyle
                    .FeatureTypeStyle.Rule
                );
              }
              if (scope.currentStyle.data.name === 'kis_anc_efx_canalisations') {
                StyleFactory.updateJSONCanalColors(cleanSld);
              }
            }
          );
        }

        return promise.promise;
      };

      // ------------------------------------------------------------------------------------------------
      // INIT : Get layer styles list and load the default style
      // ------------------------------------------------------------------------------------------------

      // used to store all the xgos extra data which will be saved in the xml file
      scope.xgosExtraData = {};

      /**
       * Reset the JSON
       */
      var resetJsonSld = function() {
        // reset JSON SLD
        scope.jsonSLD = {
          StyledLayerDescriptor: {
            NamedLayer: {
              UserStyle: {
                FeatureTypeStyle: {
                  Rule: [],
                },
              },
            },
          },
        };
        scope.rules = [];
        scope.textSymbolizer = [];
        setLabelizedAttributeTabs();
        prepareSLD();
      };

      /**
       *      Récupére la liste des symbolizer d'une règle.
       *
       * @param {[[type]]} rule [[Description]]
       * @return {[[Array]]} [[Liste des symbolizer de la règle]]
       */
      function getSymbTypesFromRule(rule) {
        var symbTypes = [],
          iPst;
        var possibleSt = [
          'PointSymbolizer',
          'PolygonSymbolizer',
          'LineSymbolizer',
        ];
        for (iPst = 0; iPst < possibleSt.length; iPst++) {
          if (rule[possibleSt[iPst]] != undefined)
            symbTypes.push(possibleSt[iPst]);
        }

        return symbTypes;
      }

      function mergeThis2Rules(rules, rule, ps1, ps2, i2, st) {
        if (rule[st] == undefined) {
          //-- Ce symbolizer n'existe pas sur cette règle,
          //-- on le crée donc en propriété simple.
          rule[st] = ps2;
        } else if (rule[st].push == undefined) {
          //-- Ce symbolizer existe sur cette règle
          //-- en tant que propriété simple,
          //-- on le transforme en tableau pour ajouter le symbolizer ps2
          ps1 = rule[st];
          rule[st] = [ps1, ps2];
        }
        //-- Le symbolizer existe déjà en tant que tableau,
        //-- on lui ajoute le symbolizer ps2.
        else rule[st].push(ps2);
        rules.splice(i2, 1);
      }

      function rulesHaveToBeMerged(r1, r2) {
        var prop;

        if (r1.Filter == undefined || r2.Filter == undefined) {
          if (r1.Filter != undefined || r2.Filter != undefined) {
            //-- Une régle a des filtres définis et l'autre non, donc elles sont différentes.
            return false;
          } else {
            //-- Les 2 régles n'ont pas de filtre défini.
            return (
              r1.MaxScaleDenominator == r2.MaxScaleDenominator &&
              r1.MinScaleDenominator == r2.MinScaleDenominator
            );
          }
        } else {
          for (prop in r1.Filter) {
            if (r2.Filter[prop] != r1.Filter[prop]) {
              return false;
            }
          }
          return true;
        }
      }
      /**
       *     Fusion des symbologies de même filtre dans une seule règle.
       * Le fonctionnement correcte en particulier
       * pour une légende correcte, consiste à mettre dans une seule règle
       * les combinaisons de symbole.
       *
       * @param {[[type]]} fts [[Description]]
       */
      function mergeRules(fts) {
        var i1, i2, ist, st, rule, ps1, ps2, symbTypes, prop, f2, sameFilter;

        for (i1 = 0; i1 < fts.Rule.length; i1++) {
          rule = fts.Rule[i1];
          for (i2 = i1 + 1; i2 < fts.Rule.length; i2++) {
            f2 = fts.Rule[i2].Filter;
            symbTypes = getSymbTypesFromRule(fts.Rule[i2]);
            for (ist = 0; ist < symbTypes.length; ist++) {
              //-- Ex: ps2 = fts.Rule[i2].PointSymbolizer;
              st = symbTypes[ist];
              ps2 = fts.Rule[i2][st];
              if (rulesHaveToBeMerged(rule, fts.Rule[i2])) {
                mergeThis2Rules(fts.Rule, rule, ps1, ps2, i2, st);
              }
            }
          }
        }
      }

      function getTrueNgDialogData() {
        var p;
        p = scope.$parent;
        while (p != null) {
          if (p.sldEditorData != undefined) return p.sldEditorData;
          p = p.$parent;
        }
      }
      scope.trueNgDialogData = getTrueNgDialogData();

      scope.ruleMgt = {};
      scope.ruleMgt.symbolShow = function() {
        //-- Arrêt du timeout quand fenêtre fermée;
        if (scope.trueNgDialogData == undefined)
          scope.trueNgDialogData = getTrueNgDialogData();
        if (
          scope.trueNgDialogData == undefined ||
          scope.trueNgDialogData.isClosed
        )
          return;
        if (scope.rules.length == 1) scope.ruleMgt.ruleOfTheMomentIndex = 0;
        else {
          if (
            scope.ruleMgt.ruleOfTheMomentIndex == undefined ||
            scope.ruleMgt.ruleOfTheMomentIndex == scope.rules.length - 1
          )
            scope.ruleMgt.ruleOfTheMomentIndex = 0;
          // J'ai l'impression que le but ici est d'afficher tour à tour les apparences des différentes règles,
          // sauf que le rendu est dégueulasse et affiche toutes les règles de l'attribut (ce qui n'est pas bon en mode catégorisé)
          // au lieu de seulement celles de la valeur d'attribut donc je l'enlève
          // else scope.ruleMgt.ruleOfTheMomentIndex++;
          // $timeout(scope.ruleMgt.symbolShow, 4000);
        }
        scope.$broadcast('styleComponentIndexToShow', {
          ruleInd: scope.ruleMgt.ruleOfTheMomentIndex,
        });
      };

      scope.$on('styleComponentAdded', scope.ruleMgt.symbolShow);
      /*
       * Build our JSON model from the SLD
       */
      var extractSLDdata = function(data, fromEditor) {
        // Check SLD validity
        scope.jsonSLD = x2js.xml_str2json(data);
        var layer = getProp('StyledLayerDescriptor.NamedLayer', scope.jsonSLD);
        var fts = getProp('UserStyle.FeatureTypeStyle', layer);
        console.log(fts);

        if (typeof fts !== 'object') {
          alert(
            $filter('translate')('model.styles.editor.malformed') +
              (fromEditor
                ? '\n' + $filter('translate')('model.styles.editor.cancel_edit')
                : '')
          );
          resetJsonSld();
          return false; // Extraction failed
        }
        scope.sld = data;

        // Extract Rules and TextSymbolizers
        scope.textSymbolizer = [];

        //-- Fusionner les règles qui en réalité sont
        //-- les mêmes car leur filtre est identique.
        mergeRules(fts);

        scope.rules = asArray(fts.Rule).filter(function(rule) {
          if (
            angular.isDefined(scope.simpleInterface) &&
            scope.simpleInterface == true
          ) {
            if (rule.Title === 'Représentation par défaut') return false;
          }
          if (rule.TextSymbolizer) {
            scope.textSymbolizer.push(rule);
            return false; // Remove it
          }
          // Other items must be arrays
          Object.keys(rule).forEach(function(i) {
            rule[i] = asArray(rule[i]);
          });
          return true;
        });
        setLabelizedAttributeTabs();

        scope.mode.chosen =
          asArray(layer.Name).indexOf('category') !== -1
            ? 'category'
            : 'custom';

        // Category mode: generate a palette, labels, etc
        if (scope.mode.chosen === 'category') {
          scope.category = { palette: [] };
          if (getProp('xgos.category', layer)) {
            // We have category information, load it
            var xgCat = layer.xgos.category;
            scope.xgosExtraData = layer.xgos;
            scope.presymboltype = { value: xgCat.symbol };
            scope.currentPalette = xgCat.palette;
            scope.category.attr = xgCat.attribute;
          } else {
            // Alternative way to guess the attribute
            var filter = scope.rules[0].Filter[0];
            scope.category.attr = filter.hasOwnProperty('PropertyIsEqualTo')
              ? filter.PropertyIsEqualTo.PropertyName
              : filter.PropertyIsNull.PropertyName;
          }
          var compareObject = getSimpleObjectForUniqValues('');
          scope.rules.forEach(function(rule) {
            generatePreview(rule, compareObject);
            scope.category.palette.push(rule.kse.color);
          });
        }
        //scope.ruleMgt.symbolShow();
        $timeout(scope.ruleMgt.symbolShow, 0);

        return true; // Extraction successful
      };

      scope.loadSLD = function() {
        // todo... but bs-select has some weird glitches
        // if we use an alert while the list is still visible (hence the 500 ms timeout... which sucks ...*/
        /*$timeout(function(){
                 var ans = confirm("Are you sure ?\nYou will loose any changes to the current style.");
                 */ /*if(!ans) {
                 scope.currentStyle = scope.previousStyle;
                 }*/ /*
                 }, 500);
                 scope.previousStyle = scope.currentStyle;*/

        /*sldUtils.loadSLD(scope.currentStyle.name).then(function(res){*/

        StyleFactory.get(scope.currentStyle.data.name).then(function(res) {
          scope.isNewStyle = false;
          extractSLDdata(res.data);
        });
      };

      scope.layerStyles = [];
      // Load FTI and associated styles
      if (scope.global.mode === 'fti') {
        // If at least one style is associated to the layer

        if (scope.fti.styles.length > 0) {
          scope.fti.styles.forEach(function(style, i) {
            var typeOfStyle =
              scope.fti.defaultStyle !== style ? 'default' : 'primary';
            scope.layerStyles.push({
              label:
                '<span class="label label-' +
                typeOfStyle +
                '">' +
                style +
                '</span>',
              name: style,
              data: [],
            });
            if (typeOfStyle === 'primary') {
              scope.currentStyle = { data: scope.layerStyles[i] };
            }
          });
          // set default
          if (!scope.currentStyle) {
            scope.currentStyle = { data: scope.layerStyles[0] };
          }

          scope.loadSLD();
        }

        // load
      } else {
        // alert('associer style directement');
      }

      /**
       * preparethesld to be translated into xml
       */
      var prepareSLD = function() {
        if (!scope.jsonSLD || !scope.rules) {
          return;
        }

        // set the rules (force as an array)
        var datacopy = angular.copy(scope.rules);

        // add the text symbolizer if any
        if (Array.isArray(scope.textSymbolizer)) {
          datacopy = datacopy.concat(scope.textSymbolizer);
        }

        scope.jsonSLD.StyledLayerDescriptor.NamedLayer.UserStyle.FeatureTypeStyle.Rule = datacopy;

        // extraData

        if (scope.mode.chosen === 'category') {
          if (Object.keys(scope.xgosExtraData).length > 0) {
            scope.jsonSLD.StyledLayerDescriptor.NamedLayer.xgos =
              scope.xgosExtraData;
          }
        }

        sldUtils.cleanSLD(scope.jsonSLD);
        console.log(scope.jsonSLD);
        scope.sld = x2js.json2xml_str(scope.jsonSLD);
      };

      // ------------------------------------------------------------------------------------------------
      // | SYMBOLS
      // ------------------------------------------------------------------------------------------------
      // | First global tab

      /**
       *
       */
      var getAvailableStyles = function(allowAlreadyLinked) {
        return StyleFactory.getlist().then(function(res) {
          scope.fullListOfStyles = [];
          scope.fullListOfStyles = [];

          StyleFactory.resources.styles.forEach(function(style) {
            // check if the style isnt already linked to that layer
            if (scope.fti.styles.indexOf(style) === -1 || allowAlreadyLinked) {
              scope.fullListOfStyles.push(style);
            }
          });
        });
      };

      /** link style **/
      scope.linkStyleModal = function() {
        scope.styleToLink = {};

        getAvailableStyles().then(function() {
          ngDialog.open({
            template:
              'js/XG/widgets/mapapp/style/views/modals/modal.linkstyle.html',
            className: 'ngdialog-theme-plain miniclose width800 nopadding',
            closeByDocument: false,
            scope: scope,
          });
        });
      };

      /**
       * addStyle to the layer
       */
      scope.linkStyle = function() {
        FeatureTypeFactory.addstyle(scope.fti.uid, scope.styleToLink.name).then(
          function(res) {
            scope.fti.styles.push(scope.styleToLink.name);

            var newStyle = {
              label:
                '<span class="label label-default">' +
                scope.styleToLink.name +
                '</span>',
              name: scope.styleToLink.name,
            };

            scope.layerStyles.push(newStyle);
            scope.currentStyle = {
              data: newStyle,
            };

            scope.loadSLD();
          }
        );
        //
      };

      /**
       * New Style Modal
       */

      scope.newStyleModal = function() {
        scope.copyStyleFrom = { name: '' };
        scope.newStyle = { name: '' };
        scope.copySld = function() {
          StyleFactory.get(scope.copyStyleFrom.name).then(function(res) {
            scope.newStyle.data = res.data;
          });
          /*sldUtils.loadSLD(scope.newStyle.copyFrom).then(function(res){
                     scope.newStyle.copyFromSld = res.data;
                     });*/
        };

        getAvailableStyles(1).then(function() {
          ngDialog.open({
            template:
              'js/XG/widgets/mapapp/style/views/modals/modal.newstyle.html',
            className: 'ngdialog-theme-plain nopadding miniclose',
            closeByDocument: false,
            scope: scope,
          });
        });
      };

      /**
       * Add initiated style to layer
       */
      scope.addStyleToLayer = function() {
        scope.newStyle.label =
          '<span class="label label-default">' +
          scope.newStyle.name +
          '</span>';

        scope.layerStyles.push(scope.newStyle);
        scope.currentStyle = {
          data: scope.newStyle,
        };

        resetJsonSld();
        if (scope.newStyle.data) {
          extractSLDdata(scope.newStyle.data);
        }

        // reset new style object
        scope.newStyle = { name: '' };
        scope.isNewStyle = true;
      };

      // global
      scope.modes = [
        {
          key: 'custom',
          label: 'model.styles.editor.global.custom',
        },
        {
          key: 'category',
          label: 'model.styles.editor.global.category',
        } /*,{
             key : 'graduated',
             label : 'Gradué'
             }*/,
      ];
      scope.mode = {};
      scope.mode.chosen = 'custom';

      // We save the edit mode as the sld title (NamedLayer.Name)
      // so we know what mode should be used when opened again
      scope.$watch('mode.chosen', function(newMode, oldMode) {
        if (scope.jsonSLD) {
          scope.jsonSLD.StyledLayerDescriptor.NamedLayer.Name = newMode;
        }
      });

      // Save the symbolizer configuration when switching sld mode
      // so it can be set back when switching back to the same mode
      var svgSldConfig = {};
      scope.switchSldEditionMode = function() {
        var newMode = scope.mode.chosen,
          oldMode = newMode === 'custom' ? 'category' : 'custom';

        svgSldConfig[oldMode] = scope.rules;

        // set saved version if it exists, or reset
        scope.rules = svgSldConfig[newMode] || [];
      };

      // --------------------------------
      // Category
      // --------------------------------
      // default palette
      // console.log(scope.fti);
      scope.currentPalette = {
        data: {
          id: 'tol-dv',
          nb: 50,
        },
        symbolType: scope.fti.typeInfo,
      };
      scope.presymboltype = { value: scope.fti.typeInfo };

      /**
       * Return a simple object for the uniqvalues list
       * @param color
       * @returns {{}}
       */
      var getSimpleObjectForUniqValues = function(color) {
        var object = {};
        switch (scope.currentPalette.symbolType) {
          case 'POINT':
            object = x2js.xml_str2json(
              '<PointSymbolizer>' +
                '<Graphic>' +
                '<Mark>' +
                '<WellKnownName> circle </WellKnownName>' +
                '<Fill><CssParameter name="fill">#' +
                color +
                '</CssParameter></Fill>' +
                '</Mark>' +
                '</Graphic>' +
                '</PointSymbolizer>'
            );
            break;

          case 'LINE':
            object = x2js.xml_str2json(
              '<LineSymbolizer>' +
                '<Stroke>' +
                '<CssParameter name="stroke">#' +
                color +
                '</CssParameter>' +
                '</Stroke>' +
                '</LineSymbolizer>'
            );
            break;

          case 'POLYGON':
            object = x2js.xml_str2json(
              '<PolygonSymbolizer>' +
                '<Fill>' +
                '<CssParameter name="fill">#' +
                color +
                '</CssParameter>' +
                '</Fill>' +
                '</PolygonSymbolizer>'
            );
            break;
        }

        return object;
      };
      /**
       * Push a simple object to the sld when category mode is used
       * @param literal
       * @param index
       * @param forcecolor
       */
      var nullLiterals = [null, '', 'kis_sldeditor_default_value'];
      var pushSimpleObjectToUniqValues = function(obj, index, forcecolor) {
        obj.label = obj.label || obj.value;
        // Translate the default value
        if (obj.label === 'kis_sldeditor_default_value') {
          obj.label = $filter('translate')(
            'model.styles.editor.category.default_value'
          );
          addNewRule(obj, index, forcecolor);
        } else if (attributeRestrictionsUtils.attributeHasTableOrDomainRestriction(scope.fti, scope.category.attr)) {
          sldUtils.getSldCategorizedRuleLabelByValue(scope.fti, scope.category.attr, obj.value).then(
              res => {
                obj.label = res;
                addNewRule(obj, index, forcecolor);
              }
          );
        } else {
          addNewRule(obj, index, forcecolor);
        }
      };

      /**
       * Créé l'objet rule et insère l'objet dans le tableau de règles
       * @param {object} obj objet contenant les propriété "valeur" et "label" d'une règle d'un style sld catégorisé
       * @param {number} index rang de la valeur dans la liste de valeurs
       * @param {boolean} forcecolor true quand on réinitialise la catégorisation
       */
      const addNewRule = (obj, index, forcecolor) => {

        // if the color does not exist (user manually added an object)
        // we use the last color available
        const plength = scope.category.palette.length;
        const color = forcecolor || plength > index
                ? scope.category.palette[index]
                : scope.category.palette[plength - 1];
        const simpleObject = getSimpleObjectForUniqValues(color);

        // Create the rule
        const newRule = {
          Title: [obj.label],
          kse: {
            // Internal properties
            color: color,
            value: obj.value,
            csspreview: true,
          },
        };
        const objectType = Object.keys(simpleObject)[0];
        newRule[objectType] = [simpleObject[objectType]];
        if (!newRule.Title) {
          delete newRule.Title;
        }

        // add the filter

        // Ajoute un critère qui accepte une valeur vide pour l'attribut texte de classification
        // dans la règle par défaut d'un style catégorisé (règle ayant pour valeur "Représentation par défaut")
        const attribute = scope.fti.attributes.find(attr => attr.name === scope.category.attr);

        if (stringTypes.includes(attribute.type) && obj.value === 'kis_sldeditor_default_value') {
          newRule.Filter = {
            Or:{
              PropertyIsNull: {
                PropertyName: scope.category.attr,
              },
              PropertyIsEqualTo: {
                PropertyName: scope.category.attr,
                Literal: '',
              }
            }
          };
        } else if (obj.value && !nullLiterals.includes(obj.value)) {
          newRule.Filter = {
            PropertyIsEqualTo: {
              PropertyName: scope.category.attr,
              Literal: obj.value,
            },
          };
        } else {
          newRule.Filter = {
            PropertyIsNull: {
              PropertyName: scope.category.attr,
            },
          };
        }
        scope.rules.push(newRule);
      };

      scope.category = {};

      scope.translateAlias = function(x) {
        var aliasTrans = $filter('translate')(
          'features.' + scope.fti.name + '.properties.' + x
        );
        if (aliasTrans.indexOf('features.') === -1) {
          return aliasTrans;
        }
        return x.alias;
      };

      /**
       * When the category Attribute is changed
       * @param confirmAction true quand il s'agit d'une demande de réinitialisation, false lors d'un changment du select d'attribut
       * @param resetRules true quand on veut réinitialiser toutes les valeurs et les styles associés,
       * sinon false quand on veut mettre à jour la liste des valeurs uniques seulement, sans modifier les styles définis
       */
      scope.setCategoryAttribute = function(confirmAction, resetRules) {
        var confirmReset = 'model.styles.editor.category.reset.confirm';
        if (!confirmAction && !confirm($filter('translate')(confirmReset))) {
          return;
        }

        gaDomUtils.showLocalLoader('.uniqvalues');

        // get list of uniques attrs
        QueryFactory.dataattribute(scope.fti.uid, scope.category.attr).then(
          function(res) {
            gaDomUtils.removeLocalLoader('.uniqvalues');

            // We need to know if the attribute is numeric to sort it
            var attrType = 'string';
            var numTypes = [
              'java.lang.Double',
              'java.lang.Float',
              'java.lang.Integer',
              'java.math.BigDecimal',
            ];
            scope.fti.attributes.some(function(attr) {
              if (attr.name === scope.category.attr) {
                if (numTypes.indexOf(attr.type) !== -1) {
                  attrType = 'number';
                  return true;
                }
              }
            });

            var uniqvalues = gaJsUtils
              .arrayUnique(res.data)
              .filter(function(x) {
                // Remove null literals
                return nullLiterals.indexOf(x) === -1;
              });

            if (attrType === 'number') {
              uniqvalues.sort(function(a, b) {
                return a - b;
              });
            } else {
              uniqvalues.sort();
            }

            // Stop there if there is no data to use
            if (uniqvalues.length === 0) {
              scope.rules = [];
              return false;
            }

            // Generate palette with default style
            uniqvalues.unshift('kis_sldeditor_default_value');
            scope.currentPalette.data.nb = uniqvalues.length;
            scope.currentPalette.symbolType = scope.presymboltype.value;
            scope.category.palette = StyleFactory.paletteGenerator(
              scope.currentPalette.data
            );

            // Reset data if needed
            if (resetRules) {
              scope.rules = [];
              uniqvalues.forEach(function(literal, index) {
                pushSimpleObjectToUniqValues(
                  {
                    label: literal,
                    value: literal,
                  },
                  index
                );
              });
            } else {
              var existing = scope.rules.map(function(r) {
                return String(r.kse.value);
              });
              uniqvalues = uniqvalues.filter(function(x) {
                return existing.indexOf(String(x)) === -1;
              });
              var usedcolors = scope.rules.map(function(r) {
                return r.kse.color;
              });
              var unusedcolors = scope.category.palette.filter(function(i) {
                return usedcolors.indexOf(i) < 0;
              });

              // add only new distinct values
              uniqvalues.forEach(function(literal) {
                existing.push(String(literal));
                pushSimpleObjectToUniqValues(
                  {
                    label: String(literal),
                    value: literal,
                  },
                  scope.rules.length,
                  unusedcolors.shift()
                );
              });
            }

            // Ajoute un critère qui accepte une valeur vide pour l'attribut texte de classification
            // dans la règle par défaut d'un style catégorisé (règle ayant pour valeur "Représentation par défaut")
            const attribute = scope.fti.attributes.find(attr => attr.name === scope.category.attr);
            if (stringTypes.includes(attribute.type)) {
              sldUtils.addCategorizedDefaultRuleEmptyFilter(scope.rules, scope.category.attr);
            }

            // save category informations
            scope.jsonSLD.StyledLayerDescriptor.NamedLayer.Name = 'category';
            scope.xgosExtraData = {
              category: {
                symbol: scope.presymboltype.value,
                palette: scope.currentPalette,
                attribute: scope.category.attr,
              },
            };
          },
          function fail() {
            gaDomUtils.removeLocalLoader('.uniqvalues');
          }
        ).finally(
            () => {
              document.getElementById('sld-category-attrpicker').blur();
            }
        );
      };

      let setOrResetModal;
      scope.setorreset = { value: 'set' };
      scope.setOrResetCategoryAttributeModal = function() {
        setOrResetModal = ngDialog.open({
          template:
            'js/XG/widgets/mapapp/style/views/modals/modal.set_or_reset_category.html',
          className: 'ngdialog-theme-plain width600 nopadding miniclose',
          closeByDocument: false,
          scope: scope,
        });
      };

      scope.setOrResetCategoryAttribute = function() {
        scope.setCategoryAttribute(true, scope.setorreset.value === 'reset');
        setOrResetModal.close();
      };

      scope.newUniqValue = {};

      scope.resetCategoryColors = function() {
        alert('todo : reset colors');
      };
      /**
       * Add an unique Value to category
       */
      scope.addUniqValueToCategory = function() {
        if (scope.simpleInterface) {
          scope.newUniqValue.value = scope.newUniqValue.label;
        }
        var length = scope.rules.length;
        // empty category
        if (length === 0) {
          scope.rules = [];
          scope.currentPalette.data.nb = 1;
          scope.category.palette = StyleFactory.paletteGenerator(
            scope.currentPalette.data
          );
        }
        pushSimpleObjectToUniqValues(scope.newUniqValue, length);
        scope.newUniqValue = {};
      };

      /**
       * Remove from the category and the sld
       * @param index
       */
      scope.removeItemFromCategory = function(index) {
        scope.rules.splice(index, 1);
      };

      /*
       * Transform a category rule (one rule, multiple symbolizer + 1 filter)
       * to an array of rules compatible with the sldeditor + a save of the filter to add it
       * again after the rule is modified.
       *
       * See reverse method: joinCategoryRule
       */
      var splitCategoryRule = function(rule) {
        var flatRule = [];
        flatRule.save = {};
        rule = angular.copy(rule);

        Object.keys(rule).forEach(function (type) {
          if (type.toLowerCase().indexOf('symbolizer') === -1) {
            // Not a symbolizer: save it somewhere else
            flatRule.save[type] = rule[type];
          } else {
            Object.keys(rule[type]).forEach(function (i) {
              var obj = {};
              obj[type] = [rule[type][i]];
              if (Array.isArray(rule.MaxScaleDenominator)) {
                obj.MaxScaleDenominator = rule.MaxScaleDenominator[0];
              }
              if (Array.isArray(rule.MinScaleDenominator)) {
                obj.MinScaleDenominator = rule.MinScaleDenominator[0];
              }
              flatRule.push(obj);
            });
          }
        });
        return flatRule;
      };


      /*
       * When the user submits its modification to the categorised item,
       * we join the multiple rules from slduniqueeditor to form a single
       * rule element.
       */
      let joinCategoryRule = (flatRule) => {
        var newCategoryItem = {};

        // Push saved data back into the item
        Object.keys(flatRule.save).forEach(function(type) {
          newCategoryItem[type] = flatRule.save[type];
        });

        for(let numRule=0; numRule<flatRule.length; numRule++) {
          let prop = flatRule[numRule];
          Object.keys(prop).forEach(function(key) {
            if (!newCategoryItem.hasOwnProperty(key)) {
              newCategoryItem[key] = [];
            }
            /* newCategoryItem[key].push(Array.isArray(prop[key])
                            ? prop[key][0]
                            : prop[key]);*/
            if (Array.isArray(prop[key])) {
              for (var i = 0; i < prop[key].length; i++) {
                newCategoryItem[key].push(prop[key][i]);
              }
            } else {
              if (key === 'MaxScaleDenominator' || key === 'MinScaleDenominator') {
                newCategoryItem[key][0] = prop[key];
              } else {
                newCategoryItem[key].push(prop[key]);
              }
            }
          });
        }
        return newCategoryItem;
      };


      /**
       * User applied their change to the current categorised item
       */
      scope.applyCategoryItemChanges = function() {
        var index = scope.categoryItem.index;
        var newItem = joinCategoryRule(scope.categoryItem);

        var compareObject = getSimpleObjectForUniqValues('');
        generatePreview(newItem, compareObject);

        scope.rules[index] = newItem;
      };

      /**
       * Edit category item
       * @param index
       */
      scope.editCategoryItem = function(index) {
        scope.categoryItem = splitCategoryRule(scope.rules[index]);
        scope.categoryItem.index = index;

        ngDialog.open({
          template:
            'js/XG/widgets/mapapp/style/views/modals/modal.categoryitem.html',
          className: 'ngdialog-theme-plain width800 nopadding miniclose',
          closeByDocument: false,
          scope: scope,
        });
      };

      // test palette
      scope.newPalette = { number: 5 };
      scope.$watch('newPalette.number', function(nb) {
        if (angular.isNumber(nb) && nb > 0) {
          scope.newPalette.palette = palette('sequential', nb);
        }
      });

      /**
       * Open Palette Library
       */
      let paletteLibrary;
      scope.openPalettesLibrary = () => {
        let preCloseExecuted = false;
        paletteLibrary = ngDialog.open({
          template:
            'js/XG/widgets/mapapp/style/views/modals/modal.palettelibrary.html',
          className: 'ngdialog-theme-plain miniclose width600 nopadding',
          closeByDocument: false,
          scope: scope,
          preCloseCallback: () => {
            if (!preCloseExecuted) {
              preCloseExecuted = true;
              paletteLibrary = null;
              if(scope.category.attr){
                scope.setOrResetCategoryAttributeModal();
              }
            }
          }
        });
      };
      //Fonction pour fermer paletteLibrary
      scope.closePalette = () => {
        if (paletteLibrary) {
          paletteLibrary.close();
        }
      }

      /**
       * [isInArray check if value is in array]
       * @param  {[type]}  value [description]
       * @param  {[type]}  array [description]
       * @return {Boolean}       [description]
       */
      function isInArray(value, array) {
        return array.indexOf(value) > -1;
      }
      function verifyTextSymbolizerByAttribute() {
        var attributes = scope.fti.attributes;
        var attNames = [];
        for (var k = 0; k < attributes.length; k++) {
          attNames.push(attributes[k].name);
        }
        for (var i = 0; i < scope.textSymbolizer.length; i++) {
          var att = scope.textSymbolizer[i].TextSymbolizer.Label.PropertyName;
          if (!isInArray(att, attNames)) {
            scope.textSymbolizer.splice(i, 1);
          }
        }
      }

      // ----------------------------------------------------
      // LABELS
      // ----------------------------------------------------

      scope.labelizedAttributesTabs = [];

      /**
       * set the labelizedAttributes tab
       * @param currentTab
       */
      var setLabelizedAttributeTabs = function(currentTab) {
        currentTab = currentTab || 0;

        verifyTextSymbolizerByAttribute();
        scope.labelizedAttributesTabs = scope.textSymbolizer.map(function(
          text
        ) {
          var attribute = text.TextSymbolizer.Label.PropertyName;
          var title = scope.translateAlias(attribute);
          if (!title) {
            title = attribute;
          }
          return { title: title };
        });

        $timeout(function() {
          scope.labelizedAttributesTabs.activeTab = currentTab;
        }, 0);
      };

      /**
       * Select the attributes that need to get a label
       */
      scope.labelizedAttributesModal = function() {
        scope.labelattr = {};
        scope.textSymbolizer.forEach(function(text) {
          scope.labelattr[text.TextSymbolizer.Label.PropertyName] = true;
        });

        ngDialog.open({
          template:
            'js/XG/widgets/mapapp/style/views/modals/modal.labelizedattributes.html',
          className: 'ngdialog-theme-plain',
          closeByDocument: false,
          scope: scope,
        });
      };

      scope.removeLabelizedAttributesModel = function() {
        var ans = confirm(
          $filter('translate')('model.featuretypes.actions.confirmSuppression')
        );
        if (ans) {
          scope.textSymbolizer = [];
          scope.labelattr = {};
          scope.labelizedAttributesTabs.length = 0;
          scope.labelizedAttributesTabs.activeTab = 0;
          scope.setLabelizedAttributes();
        }
      };

      /**
       * Select the attributes that need to get a label
       */

      scope.setLabelizedAttributes = function() {
        // check every element of the textSymbolizer
        var modified = false,
          currentTab = scope.labelizedAttributesTabs.activeTab;

        if (!scope.textSymbolizer) {
          scope.textSymbolizer = [];
        }

        scope.textSymbolizer = scope.textSymbolizer.filter(function(text, i) {
          var attr = text.TextSymbolizer.Label.PropertyName;

          // Delete the key
          var checked = scope.labelattr[attr];
          delete scope.labelattr[attr];
          if (checked) {
            return true;
          }

          // Attr was unchecked, remove it (return false)
          modified = true;
          if (i === currentTab) {
            currentTab = 0;
          }
        });

        // the only elements still in that array are new attributes to add to the textSymbolizer
        if (Object.keys(scope.labelattr).length > 0) {
          for (var i in scope.labelattr) {
            // add the element to the textSymbolizer
            if (scope.labelattr[i]) {
              scope.textSymbolizer.push({
                TextSymbolizer: {
                  Label: { PropertyName: i },
                  Font: {
                    CssParameter: [
                      {
                        __text: 'Arial',
                        _name: 'font-family',
                      },
                      {
                        __text: 'normal',
                        _name: 'font-style',
                      },
                      {
                        __text: 'normal',
                        _name: 'font-weight',
                      },
                      {
                        __text: '15',
                        _name: 'font-size',
                      },
                    ],
                  },
                  Fill: {
                    CssParameter: [
                      {
                        __text: '#000000',
                        _name: 'fill',
                      },
                      // {__text: "100", _name: "fill-opacity"}
                    ],
                  },
                  VendorOption : [
                    {__text: 5, _name: 'autoWrap'},
                    {__text: 'mbr', _name: 'polygonAlign'},
                    {__text: 'false', _name: 'conflictResolution'},
                    {__text: 'true', _name: 'partials'}
                  ]
                },
              });
              modified = true;
            }
          }
        }
        if (modified) {
          setLabelizedAttributeTabs(currentTab);
        }
      };

      // Global Tabs
      scope.globaltabs = [
        {
          title: 'model.styles.editor.symbols',
          content: 'js/XG/widgets/mapapp/style/views/styleeditor.symbols.html',
        },
        {
          title: 'model.styles.editor.labels',
          content: 'js/XG/widgets/mapapp/style/views/styleeditor.labels.html',
        },
      ];
      scope.globaltabs.activeTab = 0;

      // --------------------------------------
      // Map interactions
      // --------------------------------------
      //var svgTmp = false;
      scope.applyToMap = function() {
        /*if(!svgTmp){
                    var timestamp = new Date().getUTCMilliseconds();
                    svgTmp = 'tmpStyle'+timestamp;
                    var cleanSld = scope.sld.replace(/&#x2F;/g,'/');
                    StyleFactory.add(cleanSld,svgTmp).then(function(){
                        scope.applystyle = svgTmp;
                        svgTmp = false;
                    });

                }*/
        scope.saveStyle().then(function() {
          // apply
          $rootScope.$broadcast('gcOperationalLayerChange', '', 'applyall');
        });
      };

      // ----------------------------------------------------
      // SLD Editor
      // ----------------------------------------------------

      /*
       * Feed XML editor
       */
      var feedXmlEditor = function() {
        prepareSLD();
        scope.formattedSLDforEditor = scope.sld.replace(
          /(>)(<)(\/*)/g,
          '$1\r\n$2$3'
        );
        scope.codeEditor.getDoc().setValue(scope.formattedSLDforEditor);
        scope.codeEditor.refresh();
        scope.codeEditor.execCommand('selectAll');
        scope.codeEditor.execCommand('indentAuto');
        scope.codeEditor.setCursor(0, 0);
      };

      /*
       * Update our model from the XML editor (CodeMirror)
       */
      scope.saveSLDEditor = function() {
        var sld = scope.codeEditor
          .getDoc()
          .getValue()
          .replace(/\n/g, '')
          .replace(/\t/g, '');

        if (!extractSLDdata(sld, true)) {
          $timeout(feedXmlEditor, 100);
        }
      };

      /*
       * Switch from GUI to XML editor (CodeMirror)
       */
      scope.changeEditor = function(mode) {
        scope.global.editmode = mode;
        if (mode === 'sldeditor' && scope.sld) {
          var textarea = document.getElementById('editSldAsText');
          if (!scope.codeEditor && textarea) {
            scope.codeEditor = CodeMirror.fromTextArea(textarea, {
              lineNumbers: true,
              mode: 'xml',
            });
          }
          $timeout(feedXmlEditor);
        }
      };

      scope.filterFunc = gaJsUtils.filterFunc;
      scope.filterWidgets = '';
    }

    // Angular directive configuration
    return {
      templateUrl: 'js/XG/widgets/mapapp/style/views/styleeditor.html',
      restrict: 'A',
      scope: {
        sld: '=res',
        fti: '=',
        map: '=?map',
        applystyle: '=',
        simpleInterface: '=?',
      },
      link: link,
    };
  };

  styleeditor.$inject = [
    '$rootScope',
    '$timeout',
    '$filter',
    '$q',
    'QueryFactory',
    'StyleFactory',
    'FeatureTypeFactory',
    'gaJsUtils',
    'ngDialog',
    'sldUtils',
    'hotkeys',
    'gaDomUtils',
    'attributeRestrictionsUtils'
  ];

  return styleeditor;
});
