'use strict';
define(function () {
  var defaultFiltersFactory = function ($rootScope, gclayers, $filter,
    ParametersFactory, ConfigFactory, $q) {
    var defaultFiltersFactory = {};
    let resources = {categories: []};

    /** Compteur de visite dans le widget 'Filtres de données'
     *  Permet de gérer la visibilité courante des filtres
     *  en distinguant les ouvertures de la catégorie du widget
     *  relaunchCount = 0 à la 1ère visite du widget
     *  relaunchCount >0 après navigation
     * @type {number}
     */
    let relaunchCount = -1;

    /**
     * Récupère le nombre de visite dans le widget 'Filtres de données'
     * @returns {number} nombre de visite dans le widget
     */
    function getLaunchWidgetCount() {
      return relaunchCount;
    }

    /**
     * Incrémente le nombre entier qui comptabilise les ouvertures du widget 'Filtres de données'
     */
    function incrLaunchWidgetCount() {
      relaunchCount++
    }

    // nom du(des) filtre(s) désactivés
    let deactivatedFilterName = null;

    /**
     * Getter de la chaîne de caractère deactivatedFilterName
     * @return deactivatedFilterName
     */
    const getDeactivatedFilterName = () => {
      return deactivatedFilterName;
    };


    /**
     * Créé un objet contenant une liste de clé/valeur
     * clé = index, valeur = true si svisible = true
     * Structure établie suivant l'objet requis par la méthode defaultfiltersWidget#applyFilter
     * @param filters liste de paramètres de type 'defaultfilter'
     * @returns {{}}
     */
    const getActiveFiltersOnStartup = (filters) => {
      return filters.filter(f => f.data && f.data.svisible).map(f => f.name);
    };

    /**
     *  Permet de mémoriser l'operande d'une clause pour restauration
     *  @see gcformfunction.$get.deactivateFilter
     */
    let possibleOperande = ['AND', 'OR'];
    let deactivatedFiltersOperandeMap = new Map();

    /** Est true quand un formulaire IS est ouvert */
    let formIsOpen = false;
    const isFormOpen = () => {
      return formIsOpen;
    };

    // tableau des couches actuellement filtrées dans l'application
    let filteredLayers = [];

    /**
     * A l'initialisation de la carte,
     * récupère les filtres et applique ceux pour lesquels <code>data.svisible = true</code>
     */
    const findFilters = () => {
      ParametersFactory.getbytype('defaultfilter').then(
        (res) => {
          if (!res.data || res.data.length === 0) {
            console.log(res.data ? "Aucun filtre dans le repo" : "Erreur de récupération de la liste des filtres")
            return;
          }
          if (Array.isArray(res.data)) {
            const filters = res.data;
            const activeFilters = getActiveFiltersOnStartup(filters);
            applyFilters(filters, activeFilters);
          }
        });
    }
    /**
     * Affecte les clauses aux propriétés <code>csl_filter</code> des layers openlayers
     * @param activeFiltersNames tableau de nom des filtres pour lesquels la propriété data.theme = true
     * @param filters tableau de filtres portant les clauses à appliquer aux layers openlayers
     */
    const applyFilters = (filters, activeFiltersNames) => {
      let mapLayers = gclayers.getOperationalLayer();
      // clean
      for (let layer of filteredLayers){
        layer.cql_filter = '';
      }
      if (filters && filters.length > 0) {
        for (const filter of filters){
          if (activeFiltersNames.includes(filter.name)){
            let clauses = filter.data.where_clauses;
            for (const wc of clauses) {
              for (const l of mapLayers){
                if (l.fti.uid == wc.fti || l.fti.uid == wc.fti.uid) {
                  let setlayer = filteredLayers
                    .map((x) =>{
                      return x.fti.uid;
                    })
                    .indexOf(l.fti.uid);
                  if (setlayer === -1) {
                    l.cql_filter = '1=1';
                    l.visible = true;
                    l.cql_filter = l.cql_filter + ' AND ' + wc.where;
                    filteredLayers.push(l);
                  }else{
                    l.cql_filter = l.cql_filter + ' AND ' + wc.where;
                  }
                }
              }
            }
          }
        }
      }
      angular.forEach(filteredLayers, (l) => {
        console.info('Application du filtre de données sur la couche', l.name + ' : ' + l.cql_filter);
        $rootScope.$broadcast('gcOperationalLayerChange', l, 'cql_filter');
      });
    };

    /**
     * cette fonction parcours tous les filtres existant et visible
     * selon le paramétre isActive, elle remove ou bien restore le filtre
     * @param {*} isActive 
     */
    const removeActivatedFilter = (isActive) => {
      ParametersFactory.getbytype('defaultfilter').then(
        (res) => {
          if (Array.isArray(res.data)) {
            res.data.map(filter => {
              if(filter.data && filter.data.svisible){
                if(isActive){
                  removeFilter(filter, true);
                }else{
                  const mapLayers = gclayers.getOperationalLayer();
                  restoreFilters(filter, mapLayers);
                }
              }
            })
          }
        })
    }
    /**
     * Supprime le filtre de la propriété cql_filter de la couche ol.layer<br>
     * Utilisée actuellement par la fonction <code>deactivatedFilter</code>
     * @param filter paramètre de type 'defaultfilter'
     *  @see gcformfunction.$get.deactivateFilter
     */
    const removeFilter = (filter, notFromForm) => {
      if (filter) {
        if(!notFromForm){
          formIsOpen = true;
        }
        deactivatedFilterName = filter.name;
        const filteredLayers = new Set();
        let mapLayers = gclayers.getOperationalLayer();
        const clauses = filter.data.where_clauses;
        for (const clause of clauses) {
          for (const layer of mapLayers) {
            if (layer.fti.uid === clause.fti
              || layer.fti.uid=== clause.fti.uid) {
              for (const operande of possibleOperande) {
                const searchedClause = ' ' + operande + ' ' + clause.where;
                if (layer.cql_filter
                  && layer.cql_filter.includes(searchedClause)) {

                  // suppression de la clause dans la propriété cql_filter
                  layer.cql_filter
                    = layer.cql_filter.replace(searchedClause, '');

                  // mémorisation de l'opérande préfixant la clause dans la propriété cql_filter
                  const filterClausesOperandeMap = deactivatedFiltersOperandeMap.has(filter.id)
                    ? deactivatedFiltersOperandeMap.get(filter.id) : new Map();
                  filterClausesOperandeMap.set(clause.where, operande);
                  deactivatedFiltersOperandeMap.set(filter.id,
                    filterClausesOperandeMap);
                  filteredLayers.add(layer);
                  break;
                }
              }
            }
          }
          filter.data.disabled = true;
          // met à jour les couches suivant les filtres
          for (let layer of filteredLayers) {
            $rootScope.$broadcast('gcOperationalLayerChange', layer,
              'cql_filter');
          }

          // avertis la directive du widget
          // pour décocher la case du filtre désactivé par la fonction deactivatedFilter
          $rootScope.$broadcast('resetActiveFilters', { filter: filter });
          console.info('Désactivation du filtre de données ', filter.name);
        }
      }
    };


    /**
     * Vérifie si le formulaire contient une fonction au démarrage
     * de type 'deactivateFilter'
     * Applique le filtre de données
     * @param events tableaux d'objets des évènements à l'ouverture et
     *  à la fermeture du formulaire courant
     * @see gcformfunction.$get.deactivateFilter
     */
    const checkAndReactivateFilters = (events) => {
      if (events) {
        const mapLayers = gclayers.getOperationalLayer();
        for (const event of events) {
          // isole les évènements déclenchés à l'ouverture
          if (event.origin.key === 'formEvents' && event.origin.type
              === 'load') {
            for (const action of event.actions) {
              // traitement lorsque l'évènement à l'ouverture est une fonction 'deactivateFilter'
              if (action.includes('deactivateFilter')) {
                formIsOpen = false;
                // parse le titre du filtre
                const filterTitle = action.split('(\'')[1].split('\')')[0];
                // récupère le filtre
                ParametersFactory.getDefaultFilterByTitle(filterTitle).then(
                  res => {
                    if (res.data) {
                      const filter = res.data;
                      // Plus de filtre désactivé
                      deactivatedFilterName = null;
                      // applique le filtre s'il est initialisé au démarrage
                      restoreFilters(filter, mapLayers);
                    }
                    else {
                      require('toastr').error($filter('translate')(
                        'default_filters.config.titleerror'));
                    }
                  },
                  (error) => {
                    require('toastr').error($filter('translate')(
                      'default_filters.config.titleerror') + '\n'
                          + error.message);
                  }
                );
              }
            }
          }
        }
      }
    }

    /**
     * Applique les clauses du filtre sur les couches de la carte
     * en utilisant la propriété cql_filter des ol.layer
     * @param filter paramètre de type 'defaultfilter'
     * @param mapLayers tableau des couches affichées à l'écran de type ol.layer
     */
    const restoreFilters = (filter, mapLayers) => {
      const currentFilteredLayers = new Set();
      const clauses = filter.data.where_clauses;
      for (const clause of clauses) {
        for (const layer of mapLayers) {
          if (deactivatedFiltersOperandeMap.has(filter.id) && (layer.fti.uid === clause.fti || layer.fti.uid
              === clause.fti.uid)) {
            // récupération de l'opérande
            const operande = deactivatedFiltersOperandeMap.get(filter.id).get(clause.where);
            layer.cql_filter = layer.cql_filter + ' ' + operande + ' ' + clause.where;
            currentFilteredLayers.add(layer);
          }
        }
      }
      // avertis la directive du widget pour
      // cocher les cases des filtres actifs à l'initialisation de la carte
      $rootScope.$broadcast('defaultfilterchange', filter);

      console.info('Restauration du filtre de données ', filter.name);

      for (let layer of currentFilteredLayers) {
        $rootScope.$broadcast('gcOperationalLayerChange', layer, 'cql_filter');
      }
    }

    /**
     * Supprime plusieurs paramètres de type defaultfilter
     * Retourne la liste des paramètres mis à jour
     * @param filterNames tableau de noms de filtres de données à supprimer
     */
    const deleteFiltersByName = (filterNames) => {
      let defer = $q.defer();
      ParametersFactory.deleteFiltersByName(filterNames).then(
        res => {
          if (res.data && Array.isArray(res.data)) {
            defer.resolve(res.data);
          }
        },
        err => {
          require('toastr').error(
            $filter('translate')('default_filters.config.updateerror'));
          defer.reject(err);
        }
      );
      return defer.promise;
    }

    /**
     * Met à jour les paramètres de repo de type 'defaultfilter'
     * @param filters filtres à mettre à jour
     */
    const updateFilters = (filters) => {
      let defer = $q.defer();
      ParametersFactory.updateFilters(filters).then(
        res => {
          if (res.data && Array.isArray(res.data)){
            defer.resolve(res.data);
          }else{
            defer.resolve();
          }
        },
        err => {
          require('toastr').error(
            $filter('translate')('default_filters.config.updateerror'));
          defer.reject(err);
        }
      )
      return defer.promise;
    }



    /**
     * Récupère les catégories du fichier de configuration du widget
     * dans le répertoire app/CONFIG/widgets/
     * @param ConfigName nom du fichier de configuration
     * @param filters liste de filtres servant à exclure les catégories vides
     * @return {Promise} contenant le tableau de catégories resources.categories
     */
    const getCategories = (ConfigName, filters) => {
      let defer = $q.defer();
      ConfigFactory.get('widgets', ConfigName).then(
        (res) => {
          if (res.data && res.data.categories) {
            if (!resources.categories) {
              resources.categories = res.data.categories;
            } else {
              for (const category of res.data.categories) {
                if (resources.categories.findIndex(c => c.name === category.name) === -1) {
                  resources.categories.push(category);
                }
              }
            }
            deleteUselessCategories(defer, filters, res.data, ConfigName);
          } else {
            defer.resolve([]);
          }
        },
        () => {
          require('toastr').error($filter('translate')(
            'default_filters.config.getError'
          ));
          defer.resolve();
        }
      );

      return defer.promise;
    };

    /**
     * Enregistre une catégorie dans le fichier de configuration
     * Met à jour le tableau de catégories resources.categories
     * @param ConfigName nom du fichier json de configuration dans le dossier app/CONFIG/widgets
     * @param newCategory nouvelle catégorie à ajouter: {name}
     * @return {Promise} contenant le tableau de catégories resources.categories
     */
    const addNewCategory = (ConfigName, newCategory) => {
      let defer = $q.defer();
      ConfigFactory.get('widgets', ConfigName).then(
        (res) => {
          let config = res.data;
          if (res.data !== null && res.data !== undefined) {
            if ((typeof res.data === 'string' && res.data.length === 0) || Array.isArray(
              res.data)) {
              config = {}
            } else if (res.data.categories) {
              if (res.data.categories.findIndex(c => c.name === newCategory.name) === -1) {
                resources.categories.push(newCategory);
              }
            }
          }
          if (resources.categories && resources.categories.findIndex(
            c => c.name === newCategory.name) === -1) {
            resources.categories.push(newCategory);
          }
          config.categories = resources.categories;
          if (config.hasOwnProperty('filterNames')){
            delete config.filterNames;
          }
          if (config.hasOwnProperty('filterTitles')){
            delete config.filterTitles;
          }
          ConfigFactory.add(config, 'widgets', ConfigName).then(
            () => {
              defer.resolve(resources.categories);
            },
            (err) => {
              defer.reject(err);
            }
          );
        },
        (error) => {
          require('toastr').error($filter('translate')(
            'default_filters.config.getError'
          ));
          defer.reject(error);
        }
      );
      return defer.promise;
    };

    /**
     * Supprime une catégorie de filtre.
     * Récupère la config du repo.
     * Modifie les catégories du service.
     * Sauvegarde la configuration.
     * @param config fichier de configuration du widget qui sera sauvegardé dans le repo après màj des catégories
     * @param toDeleteCategoryData objet contenant le nom de la catégorie et un tableau de noms de filtres à supprimer
     * @param ConfigName nom du fichier de configuration dans APPS/{appname}/CONFIG/widgets
     * @return {Promise} contenant la liste des catégories mise à jour: resource.categories
     */
    const deleteCategory = (config, toDeleteCategoryData, ConfigName) => {
      let defer = $q.defer();
      ConfigFactory.get('widgets', ConfigName).then(
        (res) => {
          let config;
          if (res.data !== null && res.data !== undefined) {
            if ((typeof res.data === 'string' && res.data.length === 0) || Array.isArray(
              res.data)) {
              config = {}
            }
            config = res.data;
          }else{
            config = {}
          }
          if (toDeleteCategoryData && toDeleteCategoryData.name && toDeleteCategoryData.filterNames){
            const filterNames = toDeleteCategoryData.filterNames;
            const categoryName = toDeleteCategoryData.name;
            if (toDeleteCategoryData.deleteAll) {
              if (filterNames && filterNames.length > 0){
                deleteFiltersByName(filterNames).then(
                  (deletedFilters) => {
                    if (deletedFilters && Array.isArray(deletedFilters)){
                      if (deletedFilters.length === filterNames.length){
                        resources.categories = resources.categories.filter(c => c.name !== categoryName);
                        return saveConfig(defer, config, ConfigName,deletedFilters);
                      }
                    }
                  }
                );
              }
            } else {
              resources.categories = resources.categories.filter(c => c.name !== categoryName);
              config.categories = resources.categories;
              const categorieFilters = [];
              for (const filter of toDeleteCategoryData.filters){
                filter.theme = undefined;
                filter.data.theme = null
                filter.$selected = undefined;
                categorieFilters.push(filter);
              }
              for (const filter of categorieFilters){
                if (filter.visible){
                  delete filter.visible;
                }
              }
              updateFilters(categorieFilters).then(
                (updatedFilters) => {
                  return saveConfig(defer, config, ConfigName,[], updatedFilters);
                });

            }
          }
        },
        (error) => {
          require('toastr').error($filter('translate')(
            'default_filters.config.getError'
          ));
          defer.reject(error);
        }
      );
      return defer.promise;
    }

    /**
     * Sauvegarde la configuration.
     * Actuellement, on n'a que les catégories à sauvegarder
     * @param defer objet $q pour poursuivre la promise
     * @param config configuration du widget à sauvegarder
     * @param ConfigName nom du fichier de configuration dans le dossier du repo APPS/{appname}/CONFIG/widgets
     * @param deletedFilterNames noms de filtres supprimés
     * @param updatedFilters nom des filtres mis à jour
     * @return {Promise} contenant un objet {categories, configname, success, deletedFilterNames, updatedFilters}
     */
    const saveConfig = (defer, config, ConfigName, deletedFilterNames, updatedFilters) => {
      if (config.hasOwnProperty('filterNames')){
        delete config.filterNames;
      }
      if (config.hasOwnProperty('filterTitles')){
        delete config.filterTitles;
      }
      ConfigFactory.add(config, 'widgets', ConfigName).then(
        (res) => {
          // res.data est true si la configuration a réussi
          // cnonsole.log('Ssavegarde de la configuration du widget + ConfigName + ' : ' + res.data);
          defer.resolve(
            {
              categories:resources.categories,
              configname: ConfigName,
              success: res.data,
              deletedFilterNames: deletedFilterNames ? deletedFilterNames : [],
              updatedFilters: updatedFilters ? updatedFilters : []
            }
          );
        },
        (err) => {
          require('toastr').error($filter('translate')(
            'default_filters.config.saveError'
          ));
          defer.reject(err);
        }
      );
    };

    /**
     * Sauvegarde une liste de filtres de données en tant que paramètres du repo
     * @param filters liste de filtres à sauvegarder
     * @return {Promise} dont le corps est un objet contenant les filtres
     */
    const saveFilters = (filters) => {

      const promises = [];

      for (const filter of filters) {

        if (filter.hasOwnProperty('visible')) {
          delete filter.visible;
        }
        if (filter.hasOwnProperty('theme')) {
          delete filter.theme;
        }
        let promise;
        if (!filter.id) {
          promise = ParametersFactory.add(
            filter,
            'defaultfilter',
            filter.name
          ).then(() => {
          },
          () => {

          });
        } else {
          promise = ParametersFactory.update(
            filter,
            filter.id
          ).then(() => {
          },
          () => {

          });
        }
        promises.push(promise);
      }
      return $q.all(promises);
    };

    /**
     * Supprime les catégories inutilisées dans une liste de filtres
     * Met à jour la variable resources.categories
     * @param defer
     * @param filters liste de filtres servant de base
     * @param config
     * @param ConfigName
     */
    const deleteUselessCategories = (defer, filters, config, ConfigName) => {
      const categories = [];
      const categoriesInFilters = filters.filter(f => f.data.theme).map(f => f.data.theme);
      let hasChanged = false;
      for (const categorie of resources.categories) {
        if (categoriesInFilters.indexOf(c => c.name === categorie.name) > -1){
          categories.push(categorie);
        }else {
          hasChanged = true;
        }
      }
      resources.categories = categories;
      if (hasChanged){
        if (config.hasOwnProperty('filterNames')){
          delete config.filterNames;
        }
        if (config.hasOwnProperty('filterTitles')){
          delete config.filterTitles;
        }
        ConfigFactory.add(config, 'widgets', ConfigName).then(
          () => {
            defer.resolve(resources.categories);
          },
          () => {
            defer.reject();
            require('toastr').error($filter('translate')(
              'default_filters.config.saveError'
            ))
          }
        );
      }else{
        defer.resolve(resources.categories);
      }
    };

    return {
      defaultFiltersFactory: defaultFiltersFactory,
      getLaunchWidgetCount: getLaunchWidgetCount,
      incrLaunchWidgetCount: incrLaunchWidgetCount,
      getActiveFiltersOnStartup: getActiveFiltersOnStartup,
      findFilters: findFilters,
      removeFilter: removeFilter,
      checkAndReactivateFilters: checkAndReactivateFilters,
      isFormOpen: isFormOpen,
      getDeactivatedFilterName: getDeactivatedFilterName,
      getCategories: getCategories,
      addNewCategory: addNewCategory,
      deleteCategory: deleteCategory,
      applyFilters: applyFilters,
      saveFilters: saveFilters,
      filteredLayers: filteredLayers,
      removeActivatedFilter:removeActivatedFilter
    };
  };
  defaultFiltersFactory.$inject = ['$rootScope', 'gclayers', '$filter',
    'ParametersFactory', 'ConfigFactory', '$q'];
  return defaultFiltersFactory;
}
)
;
