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

  var styleeditorunique = function(ngDialog, sldUtils, $filter, $timeout) {
    return {
      templateUrl: 'js/XG/widgets/mapapp/style/views/unique_tpl.html',
      restrict: 'A',
      scope: {
        fti: '=fti',
        rules: '=rules',
        simpleEditor: '=',
        applyChanges: '&?',
        editionMode: '=?' // catégorisé || custom
      },
      link: function(scope) {
        scope.symbolizerName = sldUtils.getSymbolizerName(scope.fti.typeInfo);

        scope.selectedComponent = { index: -1 };

        /**
         * Move the currentComponent component in @direction
         * @param direction
         */
        scope.moveComponent = function(direction) {
          let newIndex;

          if (scope.selectSymbol.type === 'rule') {
            const index = scope.selectedComponent.index;
            if ((index === 0 && direction === 'up') ||
              (index === scope.rules.length - 1 && direction === 'down')) {
              return false;
            }
            newIndex = direction === 'up' ? index - 1 : index + 1;
            scope.rules.splice(index, 0, scope.rules.splice(newIndex, 1)[0]);
            scope.selectedComponent.index = newIndex;
          } else if (scope.selectSymbol.type === 'PointSymbolizer') {
            const indexes = scope.selectedComponent.index.split('|');
            const indexSymb = parseInt(indexes[2]);
            //-- Désormais indexRule devrait
            //-- tout le temps être 0, mais sait-on jamais.
            const indexRule = indexes[0];
            let symbols;
            switch(scope.selectSymbol.type) {
              case 'PointSymbolizer':
                symbols = scope.rules[indexRule].PointSymbolizer;
                break;
              case 'LineSymbolizer':
                symbols = scope.rules[indexRule].LineSymbolizer;
                break;
              case 'PolygonSymbolizer':
                symbols = scope.rules[indexRule].PolygonSymbolizer;
                break;
              default:
                symbols = scope.rules[indexRule].PolygonSymbolizer;
            }
            if ((indexSymb === 0 && direction === 'up') ||
              (indexSymb === symbols.length - 1 && direction === 'down')) {
              return false;
            }
            newIndex = direction === 'up' ? indexSymb - 1 : indexSymb + 1;
            symbols.splice(indexSymb, 0, symbols.splice(newIndex, 1)[0]);
            scope.selectedComponent.index = indexes[0] + '|' + indexes[1] + '|' + newIndex;
          }
        };

        /**
         * Select the symbolizer component to edit
         * @param coord
         */
        scope.selectEditComponent = function(coord) {
          if (coord === 'global' || coord === 'wait-refresh') {
            scope.selectSymbol = coord;
            // rule
          } else if (Object.keys(coord).length === 1) {
            // clic sur la ligne principale de la règle
            if (scope.selectSymbol === undefined || scope.selectSymbol === null || !(scope.selectSymbol instanceof Object)) {
              scope.selectSymbol = {};
            } else {
              for (const key of Object.keys(scope.selectSymbol)) {
                if (scope.selectSymbol.hasOwnProperty(key)) {
                  delete scope.selectSymbol[key];
                }
              }
            }
            scope.selectSymbol.type = 'rule';
            scope.selectSymbol.rule = scope.rules[coord.rule];

            // recherche le type de symbole dans la clé d'une propriété de la règle courante (i.e "PointSymbolizer", "LineSymbolizer", "PolygonSymbolizer")
            // on base la recherche sur la sous-chaîne "symbolizer" plutôt que de rechercher en se basant sur le rang de la propriété
            scope.selectSymbol.symbolizerName = Object.keys(scope.rules[coord.rule]).find(key => key.includes('Symbolizer'));

            //-- Le ng-if dépendant de la valeur de "selectSymbol.show"
            //-- permet de reconstruire les éléments de la liste des filtres.
            //-- Sans cela, la liste n'est pas rafraichi à cause du ng-if d'un des parents du DOM.
            scope.selectSymbol.show = false;
            $timeout(function() {
              scope.selectSymbol.show = true;
            }, 300);

            // définit l'opérateur logique du filtre de la règle
            setRuleFilterOperator();

            // vérifie la structure du filtre si celui-ci existe et contient plusieurs critères
            if (!scope.selectedRuleFilterHasOneCriteria() && scope.selectSymbol.rule.Filter !== undefined) {
              checkMultiCriteriasFilterStructure(scope.selectSymbol.rule.Filter, scope.filterRuleOperator);
            }
          } else {
            // clic sur une symbologie d'une règle
            const rule = scope.rules[coord.rule][coord.type];
            scope.selectSymbol = coord;
            scope.selectSymbol.symbol = Array.isArray(rule) ? rule[coord.index] : rule;
            scope.selectedComponent.index = coord.rule + '|' + coord.type + '|' + coord.index;
            const type = scope.selectSymbol.type.slice();
            delete scope.selectSymbol.type;
            $timeout(function() {
              scope.selectSymbol.type = type;
            }, 10);
          }
        };

        scope.$on('refresh_selected_component', function(event, args) {
          scope.selectEditComponent(args.coord);
          // }, 200);
        });

        scope.checkSymbolIsActive = function(coord) {
          return (scope.selectedComponent.index === coord.rule + '|' + coord.type + '|' + coord.index);
        };

        scope.symbolToAdd = { type: scope.fti.typeInfo };

        /**
         * Add component to symbolizer
         */
        scope.addComponentToSymbolizer = function() {
          var toAdd = sldUtils.getSimpleSymbolizerForType(
            scope.symbolToAdd.type
          );
          scope.rules.push(toAdd);
          scope.$emit('styleComponentAdded');
          /*scope.addComponent({ symbol : scope.symbolToAdd.type});*/
        };

        scope.$on('styleComponentIndexToShow', function(evt, param) {
          scope.ruleMgtRuleOfTheMomentIndex = param.ruleInd;
        });

        /**
         * Add component to symbolizer
         */
        scope.addSymboleToRule = function() {
          const toAdd = sldUtils.getSimpleSymbolizerForType(
            scope.symbolToAdd.type
          );
          //var symb1='PolygonSymbolizer';
          let symb;
          if (scope.symbolToAdd.type === 'POLYGON') {
            symb = 'PolygonSymbolizer';
          } else if (scope.symbolToAdd.type === 'LINE') {
            symb = 'LineSymbolizer';
          } else if (scope.symbolToAdd.type === 'POINT') {
            symb = 'PointSymbolizer';
          }

          let iRule = scope.selectedComponent.index;
          iRule = iRule === -1 ? 0 : iRule;
          if (scope.rules[iRule] && scope.rules[iRule][symb] !== undefined) {
            scope.rules[iRule][symb].push(toAdd[symb][0]);
          } else {
            const tabkey = Object.keys(scope.rules[scope.selectedComponent.index]);
            const symb1 = tabkey[0];
            let symbToAdd;
            if (symb1 === 'PolygonSymbolizer') {
              symbToAdd = 'POLYGON';
            } else if (symb1 === 'LineSymbolizer') {
              symbToAdd = 'LINE';
            } else if (symb1 === 'PointSymbolizer') {
              symbToAdd = 'POINT';
            }
            const toAdd2 = sldUtils.getSimpleSymbolizerForType(symbToAdd);
            scope.rules[scope.selectedComponent.index][symb1].push(
              toAdd2[symb1][0]
            );
          }
        };

        scope.removeRule = function(ruleIndex) {
          const ans = confirm($filter('translate')('common.confirm_action'));
          if (ans) {
            scope.rules.splice(ruleIndex, 1);
          }
        };

        /**
         *
         */
        scope.removeSymbolizerFromRule = (rule, type, index, ruleIndex) => {
          const resetRule = sldUtils.removeSymbolizerFromRule(rule, type, index);
          if (resetRule) {
            scope.rules.splice(ruleIndex, 1);
          }
        };

        /**
         * Open Symbol Library
         */
        scope.openSymbolsLibrary = function() {
          scope.symbolizerTabs = [
            {
              title: 'model.styles.editor.library.lines',
            },
            {
              title: 'model.styles.editor.library.symbols',
            },
            {
              title: 'model.styles.editor.library.fill',
            },
            {
              title: 'model.styles.editor.library.palette',
            },
          ];
          scope.symbolizerTabs.activeTab = 0;

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

        // --------------------------------
        // Rules
        // --------------------------------

        // Symbol Type
        //
        scope.ruleTypePossibilities = {
          LineSymbolizer: [{ key: 'line-simple' }, { key: 'line-symbols' }],
          PointSymbolizer: [
            { key: 'point-simple' },
            { key: 'point-image' },
            { key: 'point-font' },
          ],
          PolygonSymbolizer: [
            { key: 'polygon-simple' },
            { key: 'polygon-lines' },
            { key: 'polygon-symbols' },
            { key: 'polygon-images' },
            { key: 'polygon-border-symbols' },
          ],
        };

        /**
         * When the user select another rule
         */
        scope.$watch(
          function() {
            if (scope.rules === undefined) {
              return undefined;
            } else {
              return scope.rules[scope.selectedComponent.index];
            }
          },
          function(symbol) {
            if (symbol) {
              if (scope.curSymb === undefined) {
                scope.curSymb = {};
              }
              scope.curSymb.symbolType = sldUtils.getSubTypeOfSymbolizer(
                scope.selectSymbol.symbolizerName,
                symbol
              );
            }
          }
        );

        /**
         * switchRuleSymbolType
         */
        scope.switchRuleSymbolType = function() {
          sldUtils
            .switchRuleSymbolType(scope.curSymb.symbolType)
            .then(function(newRule) {
              if (scope.rules[scope.selectedComponent.index].Filter) {
                newRule.Filter = angular.copy(
                  scope.rules[scope.selectedComponent.index].Filter
                );
              }
              scope.rules[scope.selectedComponent.index] = newRule;
            });
        };

        /**
         *
         */
        scope.toggleRuleScale = function() {
          var rule = scope.selectSymbol.rule;
          console.log(rule);
          if (rule.hasOwnProperty('MinScaleDenominator')) {
            delete rule.MinScaleDenominator;
            delete rule.MaxScaleDenominator;
          } else {
            rule.MinScaleDenominator = 50000;
            rule.MaxScaleDenominator = 250000;
          }
        };

        /**
         * NOT USED ANYMMORE, RULE ARE HIDDEN IN SIMPLE MODE
         * @param rule
         */
        scope.isComplexeRule = function(rule) {
          var complexite = 0;
          ['LineSymbolizer', 'PointSymbolizer', 'PolygonSymbolizer'].forEach(
            function(s) {
              if (rule[s]) {
                complexite += Array.isArray(rule[s]) ? rule[s].length : 1;
              }
            }
          );
          return complexite > 1;
        };

        /**
         * Add a filter to the Rule
         */
        scope.addFilterToRule = function() {
          if (!scope.selectSymbol.rule.Filter) {
            scope.selectSymbol.rule.Filter = [];
          }
          const draftFilter = {
            PropertyName: scope.fti.attributes[0].name,
            Literal: '',
          };
          const operator = scope.filterRuleOperator ? scope.filterRuleOperator : 'And';

          if (scope.selectSymbol.rule.Filter.length === 0) {
            // le filtre est vide, on créé un conteneur de conditions de filtre
            const filter = {};
            filter[operator] = {};

            // par défaut, la condition vide est insérée avec un type "La propriété est égale à la valeur"
            filter[operator].PropertyIsEqualTo = [];
            filter[operator].PropertyIsEqualTo.push(draftFilter);
            scope.selectSymbol.rule.Filter.push(filter);

          } else {
            // le filtre contient déjà 1 ou plusieurs conditions
            validFilterStructureForGeoserver(scope.selectSymbol.rule.Filter, scope.filterRuleOperator);

            if (!Array.isArray(scope.selectSymbol.rule.Filter[0][operator].PropertyIsEqualTo)){
              scope.selectSymbol.rule.Filter[0][operator].PropertyIsEqualTo = [];
            }
            // insère la nouvelle condition vide avec un type "La propriété est égale à la valeur"
            scope.selectSymbol.rule.Filter[0][operator].PropertyIsEqualTo.push(draftFilter);
          }
        };

        // --------------------------------
        // Symbol Library
        // --------------------------------

        // Picking a symbol from the library
        scope.picked = { symbol: false };
        scope.$watch('picked.symbol', function() {
          if (scope.picked.symbol) {
            var ans = confirm(
              $filter('translate')('model.styles.editor.library.confirm_reset')
            );
            if (ans) {
              scope.rules.length = 0;
              scope.picked.symbol.json.forEach(function(rule) {
                scope.rules.push(angular.copy(rule));
              });
            }
            // reset so a new click on the same symbol will fire this function again
            scope.picked.symbol = false;
          }
        });

        /**
         * Vérifie si le filtre de la règle sld sélectionnée ne contient qu'un seul critère
         * @return {boolean} true si le filtre ne contient qu'un seul critère
         * et sans être imbriqué dans une balise de l'opérateur logique
         */
        scope.selectedRuleFilterHasOneCriteria = () => {
          if (scope.selectSymbol.rule && scope.selectSymbol.rule.Filter) {
            const filter = scope.selectSymbol.rule.Filter;
            return Array.isArray(filter) && filter.length === 1 && Object.keys(filter[0]).length > 0
                && Object.keys(filter[0])[0] !== 'And' && Object.keys(filter[0])[0] !== 'Or';
          } else {
            return true;
          }

        };

        /**
         * Au clic sur un bouton radio d'un opérateur logique "AND" / "OR" d'un filtre de règle sld.<br>
         * Dans le fichier SLD, cette méthode déplace les critères situés à l'intérieur de la balise de l'ancien opérateur
         * vers l'intérieur d'une balise du nouvel opérateur logique du filtre de la règle sld
         */
        scope.onFilterRuleOperatorChange = () => {
          if (!scope.selectedRuleFilterHasOneCriteria()) {
            const oldOperator = Object.keys(scope.selectSymbol.rule.Filter[0])[0];
            if ((oldOperator === 'And' || oldOperator === 'Or') && oldOperator !== scope.tempFilterOperator.text) {
              scope.selectSymbol.rule.Filter[0][scope.tempFilterOperator.text] = scope.selectSymbol.rule.Filter[0][oldOperator];
              delete scope.selectSymbol.rule.Filter[0][oldOperator];
              scope.filterRuleOperator = scope.tempFilterOperator.text.slice();
            }
          }
        };

        /**
         * Définit l'opérateur logique de la directive
         * à partir de la clé de l'objet contenu dans le 1er élément du tableau du filtre de la règle sld
         */
        const setRuleFilterOperator = () => {
          const isFilterCriteriasNestedInOperatorTag = scope.selectSymbol && scope.selectSymbol.rule
              && Array.isArray(scope.selectSymbol.rule.Filter) && scope.selectSymbol.rule.Filter.length > 0
              && Object.keys(scope.selectSymbol.rule.Filter[0]).length > 0
              && (Object.keys(scope.selectSymbol.rule.Filter[0])[0] === 'And'
                  || Object.keys(scope.selectSymbol.rule.Filter[0])[0] === 'Or');

          // définition de l'opérateur logique qui sera la clé de l'objet
          // dans lequel seront imbriqués les critères du filtre de la règle sélectionnée
          if (isFilterCriteriasNestedInOperatorTag) {
            scope.filterRuleOperator = Object.keys(scope.selectSymbol.rule.Filter[0])[0];
          } else {
            scope.filterRuleOperator = 'And';
          }
          scope.tempFilterOperator = {text: scope.filterRuleOperator.slice()};
        };

        /**
         * Vérifie la conformité de la structure du filtre multi-critères de règle sld:<ul><li>
         *   La structure du filtre doit permettre l'enregistrement dans geoserver (gestion de l'opérateur logique)</li><li>
         *   Les groupes de critères du filtre sont bien des tableaux et non des objets</li></ul>
         * @param multiCriteriasFilter filtre de règle sld cliqué possédant plusieurs critères
         * @param operator opérateur logique du filtre de règle sld ("And" / "Or")
         */
        const checkMultiCriteriasFilterStructure = (multiCriteriasFilter, operator) => {

          // vérifie si le filtre contient plusieurs conditions sans avoir la structure requise pour Geoserver
          validFilterStructureForGeoserver(multiCriteriasFilter, operator);

          // remplace par des tableaux les groupes de conditions du filtre multiple qui seraient des objets
          // (xml2json convertit un groupe de condition en objet lorsque ce dernier ne contient qu'un seul critère)
          for (let i = 0; i < Object.values(multiCriteriasFilter[0][operator]).length; i++) {
            if (!Array.isArray(Object.values(multiCriteriasFilter[0][operator])[i])) {
              const criteriasArray = [];
              criteriasArray.push(Object.values(multiCriteriasFilter[0][operator])[i]);
              const groupType = Object.keys(multiCriteriasFilter[0][operator])[i];
              multiCriteriasFilter[0][operator][groupType] = criteriasArray;
            }
          }
        };

        /**
         * Vérifie si le filtre cliqué est un filtre multiple avec une structure valide pour Geoserver.<br>
         * C'est à dire une structure où les conditions du filtre sont imbriquées dans un opérateur And/Or.
         * Exécute le reformatage du filtre multi-critère si celui-ci n'est pas valide
         * @param {object[]} multiCriteriasFilter filtre de règle sld cliqué possédant plusieurs critères
         * @param {string} operator opérateur logique du filtre de règle sld ("And" / "Or")
         */
        const validFilterStructureForGeoserver = (multiCriteriasFilter, operator) => {
          // vérifie si le filtre contient plusieurs conditions sans avoir la structure requise pour Geoserver
          const filterHasOperatorObject = Array.isArray(multiCriteriasFilter)
              && multiCriteriasFilter.length > 0 && multiCriteriasFilter[0].hasOwnProperty(operator)
              && typeof multiCriteriasFilter[0][operator] === 'object'
              && multiCriteriasFilter[0][operator] !== null;
          const operatorObjectIsEmpty = filterHasOperatorObject && Object.keys(multiCriteriasFilter[0][operator]).length === 0;

          if (!filterHasOperatorObject || operatorObjectIsEmpty) {

            // création d'un filtre formaté selon la structure requise pour geoserver
            const rewritedFilter = reformatRuleFilterCriterias(multiCriteriasFilter, operator);

            // vide le tableau de conditions du filtre
            multiCriteriasFilter.splice(0,multiCriteriasFilter.length);

            multiCriteriasFilter.push(rewritedFilter);
          }
        };

        /**
         * Re-écrit un filtre de règle ayant plusieurs conditions pour être compatible avec Geoserver
         * @param {object[]} multiCriteriasFilter filtre ayant plusieurs conditions mal formatées pour être enregistrées dans Geoserver.
         * Structure du filtre erroné: <code>[PropertyIsEqualTo:{},PropertyIsBetween:{},PropertyIsEqualTo:{}]</code>
         * @param {string} operator opérateur logique qui est la clé d'un objet contenu dans le 1er élément du tableau <code>scope.selectSymbol.rule.Filter</code> ("And"/"Or")
         * @return {object} filtre compatible Geoserver avec la structure suivante <code>{And:{PropertyIsEqualTo:[{},{}], PropertyIsBetween:[{}]}}</code>
         */
        const reformatRuleFilterCriterias = (multiCriteriasFilter, operator) => {
          const filter = {};
          filter[operator] = {};
          if (Array.isArray(multiCriteriasFilter)) {
            if (multiCriteriasFilter.length > 0) {
              for (const filterCriteria of multiCriteriasFilter) {
                if (typeof filterCriteria === 'object' && filterCriteria !== null) {
                  const conditionType = Object.keys(filterCriteria)[0];
                  if (!Array.isArray(filter[operator][conditionType])) {
                    filter[operator][conditionType] = [];
                  }
                  filter[operator][conditionType].push(filterCriteria[conditionType]);
                }
              }
            }
          }
          return filter;
        };
      },
    };
  };

  styleeditorunique.$inject = ['ngDialog', 'sldUtils', '$filter', '$timeout'];
  return styleeditorunique;
});
