'use strict';
define(function() {
  let dashboardwidget = function(
    ConfigFactory,
    ReportIndicatorFactory,
    ngDialog,
    DataStoreFactory,
    ReportFactory,
    $rootScope,
    gaJsUtils
  ) {
    return {
      templateUrl: 'js/XG/widgets/mapapp/dashboard/views/dashboardwidget.html',
      restrict: 'A',
      link: function(scope) {

        //////////////////////////////////////////
        // Méthodes hors scope
        //////////////////////////////////////////

        /**
         * Récupèration de la liste des DataStores disponibles.
         * @return {*} tableau d'objets datastore
         */
        const getDataStores = () => {
          DataStoreFactory.get().then(
            res => {
              if (res.data !== '' && res.status === 200) {
                scope.dataStores = res.data;
              } else {
                scope.dataStores = [];
              }
            },
            err => {
              if (err.data && err.data.message) {
                require('toastr').error(err.data.message);
              } else {
                scope.dataStores = [];
              }
            }
          );
        };

        /**
         * Appel API pour supprimer le rapport Jasper (jrxml ou jasper)
         * @param jasperfileName nom du rapport
         * méthode qui factorise les doublons de code de la méthode scope.removeJasperFile
         * @return tableau des rapports jasper de type tableau de bord ou string de l'erreur levée en cas d'absence du fichier
         */
        const deleteJasperFile = (jasperfileName) => {
          const form = new FormData();
          form.append('file', jasperfileName);
          ReportFactory.deletejasperfile(form).then(
              (res) => {
                if (typeof res.data === 'string' && res.data !== '') {
                  require('toastr').info(res.data);
                } else if (Array.isArray(res.data)){
                  scope.reportFiles = res.data;
                }
              }
          );
        }

        /**
         * Récupèration de la configuration du widget et la charge dans le scope courant.
         * Les indicateurs sont stockées dans le json de config du widget repo/APPS/{app}/config/widgets/{ConfigName}
         * Le json de config contient un objet ayant une propriété "dataStoreName" et un tableau "indicators"
         */
        const getConfig = () => {
          ConfigFactory.get('widgets', scope.ConfigName).then(
            (resConf) => {
              if (resConf.data !== '' && resConf.status === 200) {
                if (Array.isArray(resConf.data.indicators) && resConf.data.indicators.length > 0) {
                  scope.currentResources = resConf.data.indicators;
                }
                if (resConf.data.dataStoreName && resConf.data.dataStoreName.length > 0) {
                  ReportIndicatorFactory.setDataStoreName(resConf.data.dataStoreName);
                  if (!scope.datastore) {
                    scope.datastore = {};
                  }
                  scope.datastore.name = resConf.data.dataStoreName;
                }
                if (resConf.data.categories && resConf.data.categories.length > 0) {
                  scope.categories = resConf.data.categories;
                }
              }

              // affiche dans le panel du widget uniquement les indicateurs ayant un rôle commun à l'utilisateur
              ReportIndicatorFactory.filterIndicatorsByUserRole(scope.currentResources).then(
                  indicatorsFiltered => {
                    groupIndicatorsByCategory(indicatorsFiltered);
                  }
              );
              console.info('Configuration du tableau de bord chargée.');
            },
            err => {
              ReportIndicatorFactory.handleServerError(err);
            }
          );
        };

        /**
         * Regroupe les indicateurs par catégorie("group")
         * Construit la liste des catégories: <code>grouplayersNames</code>
         * @param indicatorsByUserRole tableau des indicateurs auxquels l'utilisateur a droit d'accès
         */
        const groupIndicatorsByCategory = (indicatorsByUserRole) => {
          if (Array.isArray(indicatorsByUserRole) && indicatorsByUserRole.length > 0) {
            scope.grouplayers = {};
            scope.grouplayersNames = [];
            for (const indicator of indicatorsByUserRole) {
              const indicatorCategory = indicator.category && indicator.category.length > 0 ? indicator.category : 'default';
              if (!scope.grouplayers[indicatorCategory]) {
                scope.grouplayers[indicatorCategory] = [];
              }
              scope.grouplayers[indicatorCategory].push({
                id: indicator.id,
                desc: indicator.description
              });
            }
            scope.grouplayersNames = Object.keys(scope.grouplayers);
          }
        };

        /**
         * Vérifie que la liste des catégories issue du fichier de config contienne la catégorie de chaque indicateur<br>
         * Une catégorie issue du fichier de config peut être sauvegardée vide
         * afin d'être proposée dans le select des catégories à la création d'un indicateur
         */
        const checkMissingCategories = () => {
          if (Array.isArray(scope.categories) && Array.isArray(scope.grouplayersNames)) {
            for (const groupLayername of scope.grouplayersNames) {
              if (groupLayername !== 'default' && !scope.categories.some(category => categoryEqualsgroupLayerName(category, groupLayername))) {
                scope.categories.push(groupLayername);
              }
            }
          }
        };

        /**
         * Vérifie l'égalité des chaines de caractères entre un élément de la liste de catégorie et un nom de catégorie affiché dans le panel du widget
         * @param category nom de catégorie issu de la liste de catégories du widget (scope.categories)
         * @param groupLayername nom de catégorie issu des catégories d'indicateurs affichées dans le panel du widget (scope.grouplayersNames)
         * @return {boolean} true si les deux string en minuscules fournies en paramètre sont égales
         */
        const categoryEqualsgroupLayerName = (category, groupLayername) => {
          return gaJsUtils.notNullAndDefined(category) && category.length > 0 && (category.toLowerCase() === groupLayername.toLowerCase());
        };

        /**
         * Ferme toutes les catégories d'indicateurs
         */
        const hideAllPanels = () => {
          for (const group of scope.grouplayersNames) {
            scope.hiddenPanels[group] = true;
            scope.allPanelHidden = true;
          }
        };

        /**
         * Ouvre toutes les catégories d'indicateurs
         */
        const showAllPanels = () => {
          scope.hiddenPanels = {};
          scope.allPanelHidden = false;
        };
        /**
         * Exécuté à l'ouverture du widget "Tableaux de bord".<br>
         * Initialise les méthodes du scope de la directive
         */
        const initWidget = () => {
          // noms des catégories affichées dans le panel du widget
          scope.grouplayersNames = [];
          // contient les indicateurs affichés dans le panel du widget regroupés dans des catégories
          scope.grouplayers = {};
          // contient les noms des catégories réduites
          scope.hiddenPanels = {};
          // est true quand toutes les catégories sont ouvertes
          scope.allPanelHidden = false;
          // noms des catégories d'indicateurs alimentant le select de la popup d'ajout/édition d'un indicateur
          // tableau pouvant contenir des catégories vides
          scope.categories = [];
          // tous les indicateurs (y compris ceux auxquels l'utilisateur n'a pas accès dans le panel du widget)
          scope.currentResources = [];
          getConfig();
          getDataStores();

          /**
           * Récupèration des noms de fichiers modèles de rapport
           * pour alimenter le select de la fenêtre de configuration.<br>
           * Ouvre l'IHM de la configuration des indicateurs
           */
          scope.openConfig = () => {
            if (!scope.datastore) {
              scope.datastore = {};
            }
            ReportIndicatorFactory.getAllReportNames().then(
                res => {
                  if (res.data !== '' && res.status === 200) {
                    scope.reportFiles = res.data;
                  } else {
                    scope.reportFiles = [];
                  }
                  ngDialog.open({
                    template: 'js/XG/widgets/mapapp/dashboard/views/dashboardconfiguration.html',
                    className: 'ngdialog-theme-plain width800 nopadding miniclose',
                    scope: scope,
                  });
                },
                err => {
                  ReportIndicatorFactory.handleServerError(err);
                  scope.reportFiles = [];
                }
            );
          };

          /**
           * Sauvegarde la configuration courante
           * Si succès, filtre les indicateurs visibles dans le panel suivant le rôle des indicateurs et de l'utilisateur
           * @see ConfigFactory.add
           * @see filterByRole
           */
          scope.saveConfig = () => {
            ReportIndicatorFactory.setDataStoreName(scope.datastore.name);
            checkMissingCategories();
            const widgetConfig = {
              dataStoreName: scope.datastore.name,
              indicators: scope.currentResources.map(indicator => {
               if (indicator.$selected) {
                 delete indicator.$selected;
               }
               return indicator;
              }),
              categories: scope.categories
            };
            ConfigFactory.add(widgetConfig, 'widgets', scope.ConfigName).then(
                () => {
                  // affiche dans le panel du widget uniquement les indicateurs ayant un rôle commun à l'utilisateur
                  ReportIndicatorFactory.filterIndicatorsByUserRole(scope.currentResources).then(
                      res => {
                        groupIndicatorsByCategory(res);
                      }
                  );
                },
                err => {
                  ReportIndicatorFactory.handleServerError(err);
                }
            );
          };

          /**
           * Ouvre la popup de visualisation d'un indicateur
           * @param indicatorId chaine de caractère unique identifiant un indicateur <code>indicator.id</code>
           */
          scope.showIndicatorDetails = (indicatorId) => {
            scope.edit_indicator = scope.currentResources.find(indic => indic.id === indicatorId);
            ngDialog.open({
              template: 'js/XG/widgets/mapapp/dashboard/views/anIndicatorPopup.html',
              className: 'ngdialog-theme-plain width500 nopadding miniclose',
              closeByDocument: false,
              scope: scope,
              preCloseCallback: () => {
                scope.edit_indicator = null;
              }
            });
          };

          /**
           * Importe le fichier (rapport jasper ou image) dans le repo.<br>
           * Récupèration des noms de fichiers modèles de rapport
           * pour alimenter le select de la fenêtre de configuration
           * @param inputFile fichier image ou jrxml local sélectionné par l'utilisateur
           */
          scope.uploadFile = (inputFile) => {
            const form = new FormData();
            const file = inputFile.files[0];
            form.append('file', file);
            ReportFactory.addjasperfile(form, true).then(
                res => {
                  if (typeof res.data === 'string' && res.data !== '') {
                    require('toastr').info(res);

                  } else if (Array.isArray(res.data)) {
                    scope.reportFiles = res.data;
                  }
                },
                err => {
                  ReportIndicatorFactory.handleServerError(err);
                }
            );
          };

          /**
           * Efface du repository de l'application le fichier modèle de rapport (.jrxml et .jasper)
           */
          scope.jasperToRemove = {};
          scope.removeJasperFile = () => {
            //Suppression du fichier modèle de rapport .jrxml
            const jrxmlfileName = scope.jasperToRemove.name + '.jrxml';
            deleteJasperFile(jrxmlfileName);

            //Suppression du fichier modèle de rapport compilé .jasper
            const jasperfileName = scope.jasperToRemove.name + '.jasper';
            deleteJasperFile(jasperfileName);
          };

          /**
           * Au clic sur le bouton "Ajouter" un indicateur
           * Initialise l'indicateur temporaire
           * Appelle l'ouverture de la popup d'ajout/édition d'un indicateur
           */
          scope.addIndicator = () => {
            scope.edit_indicator = {};
            scope.isNewIndicator = true;
            openIndicator();
          };

          /**
           * Au clic sur le bouton "Edition" d'une ligne de la table
           * Affecte l'indicateur édité à la variable scope.edit_indicator (indicateur temporaire)
           * Appelle l'ouverture de la popup d'ajout/édition d'un indicateur
           */
          scope.editIndicator = (indicator) => {
            scope.edit_indicator = {};

            // copie l'indicateur de la ligne du tableau dans l'indicateur temporaire (équivalent JS de angular.copy())
            Object.assign(scope.edit_indicator, indicator);

            scope.isNewIndicator = false;
            openIndicator();
          };

          /**
           * Ouvre la popup d'ajout/édition d'un indicateur.<br>
           * Si l'indicateur est plus vieux que l'ajout de la fonctionnalité multi-format
           * alors le fichier rapport généré est au format pdf par défaut
           */
          const openIndicator = () => {
            if (scope.edit_indicator && !scope.edit_indicator.format) {
              scope.edit_indicator.format = 'pdf';
            }

            scope.addIndicatorDialog = ngDialog.open({
              template: 'js/XG/widgets/mapapp/dashboard/views/modal/modal.dashboard.indicator.addEdit.html',
              className: 'ngdialog-theme-plain width1000 nopadding indicator-add-edit miniclose',
              scope: scope,
              preCloseCallback: () => {
                scope.isNewIndicator = null;
                scope.edit_indicator = null;
              }
            });
          };

          /**
           * Au clic sur le bouton "Fermer" de la modal d'ajout d'un nouvel indicateur
           * Supprime l'indicateur temporaire
           * Ferme la popup
           */
          scope.closeIndicator = () => {
            if (scope.edit_indicator){
              scope.edit_indicator = null;
            }
            if (scope.addIndicatorDialog){
              scope.addIndicatorDialog.close();
            }
          };

          /**
           * Au clic sur le bouton "Enregistrer" de la modal d'ajout d'un nouvel indicateur
           * Ajoute le nouvel indicateur dans les indicateurs de la config du widget
           * Met à jour l'indicateur dans le cas d'une édition d'un indicateur existant
           * Supprime le nouvel indicateur
           * Ferme la popup
           * provoquant des changements dans l'indicateur devant être enregistrés dans le fichier de config
           */
          scope.saveIndicator = () => {

            // vérifie si l'indicateur existe déjà (pour distinguer ajout/édition)
            const index = scope.currentResources.findIndex(ind => scope.edit_indicator.id && scope.edit_indicator.id === ind.id);
            if (index >= 0) {
              // mise à jour de l'indicateur
              scope.currentResources[index] = scope.edit_indicator;
            } else {
              // ajout du nouvel indicateur
              scope.currentResources.push(scope.edit_indicator);
            }
            scope.saveConfig();

            // supprime l'indicateur temporaire et ferme la popup
            scope.closeIndicator();
          };


          /**
           * Supprime l'indicateur de la configuration.
           * @param index rang de l'indicateur dans les indicateurs de la configuration
           */
          scope.deleteIndicator = (index) => {
            scope.currentResources.splice(index, 1);
          };

          /**
           * Ajoute un paramètre à l'indicateur courant.
           */
          scope.addParam = function(indicator) {
            if (indicator.params === undefined) {
              indicator.params = [];
            }
            indicator.params.push({});
          };

          /**
           * Supprime un paramètre de l'indicateur courant
           */
          scope.deleteParam = (indicator, param) => {
            for (let i = 0; i < scope.edit_indicator.params.length; i++) {

              //Si le paramètre courant est le paramètre recherché
              if (scope.edit_indicator.params[i].name === param.name) {

                //Suppression du paramètre
                scope.edit_indicator.params.splice(i, 1);
                break;
              }
            }
          };

          /**
           * Définition de la datatable de la popup de configuration du widget
           * Elle contient tous les indicateurs (y compris ceux auxquels l'utilisateur n'a pas accès dans le panel du widget)
           */
          scope.editListCfg = {
            dataModule: 'dashBoard',
            resource_type: 'configuration', // récupère dashBoard.configuration.roles.list
            cols: ['id', 'category', 'roles'],
            colsFunction: {
              roles: 'joinArray', // utilise la directive joinArray pour présenter une string à partir du tableau de rôles
            },
            addResourceButton: true,
            addFunction: scope.addIndicator,
            editFunction: scope.editIndicator,
            removeFunction: scope.deleteIndicator,
            newResourceAppname: '',
            newResourceType: '',
          };

          // init filterLayers dans un objet sinon filterLayers n'est pas mis à jour depuis le html
          scope.search = {
            filterLayers: ''
          };

          /**
           * Supprime le filtrage quand on appuie sur la touche "escape"
           * Uniquement lors du 1er caractère écrit:<ul><li>
           * Sauvegarde l'état de d'affichage des groupes</li><li>
           * Dégroupe tous les groupes de couches</li><ul>
           * @param event keydown event
           */
          scope.onFilterKeydown = (event) => {
            // reset si escape
            scope.search.filterLayers = event.keyCode === 27 ? '' : scope.search.filterLayers;
            if (!scope.filterState && !scope.hiddenPanelsPrevState && scope.search.filterLayers.length > 0) {
              // initialise filterState pour ne plus exécuter après le 1er caractère saisi
              scope.filterState = true;
              if (Object.keys(scope.hiddenPanels).length > 0) {
                scope.hiddenPanelsPrevState = angular.copy(scope.hiddenPanels);
              }
              // modifie la variable allPanelHidden
              showAllPanels();
            }
          };

          /**
           * Après avoir effacé le texte de saisie du filtre,
           * restaure l'état initial d'affichage des groupes de couches
           */
          scope.onFilterKeyup = () => {
            if (scope.search.filterLayers.length === 0) {

              if (scope.filterState){
                scope.filterState = false;
              }
              // restaure un état initial existant
              if (scope.hiddenPanelsPrevState) {
                scope.hiddenPanels = scope.hiddenPanelsPrevState;
                // restaure allPanelHidden
                if (Object.keys(scope.hiddenPanels).length === Object.keys(scope.grouplayers).length) {
                  scope.allPanelHidden = Object.values(scope.hiddenPanels).every(active => active);
                }
                scope.hiddenPanelsPrevState = null;
              }
            }
          };

          /**
           * Au clic sur le bouton suppimer (croix) de la zone de saisie du filtre des couches
           */
          scope.resetFilter = () => {
            scope.search.filterLayers = ''
            scope.onFilterKeyup();
          };

          /**
           * Etend/réduit une catégorie d'indicateur
           * @param group nom de la catégorie
           */
          scope.toggleGroup = (group) => {
            scope.hiddenPanels[group] = !scope.hiddenPanels[group];
          };

          /**
           * Etend ou réduit toutes les catégories
           */
          scope.toggleAllPanels = () => {
            scope.allPanelHidden = !scope.allPanelHidden;
            if (scope.allPanelHidden) {
              hideAllPanels();
            } else {
              showAllPanels();
            }
          };

          /**
           * Dans l'input de la popup d'ajout de catégorie, à chaque caractère saisi,
           * vérifie si le nom de la nouvelle catégorie est déjà présent dans la liste de catégorie
           * Passe <code>categoryExists</code> à true quand le nom de la nouvelle catégorie est égal à un nom de catégorie existante
           */
          scope.onCategoryChange = () => {
            scope.categoryExists = scope.edit_category.name === 'default' ||
                scope.categories.some(category => category.toLowerCase() === scope.edit_category.name.toLowerCase());
          };

          /**
           * Ouvre la popup pour saisir le nom d'une nouvelle catégorie d'indicateur.
           * La catégorie est sauvegardée vide dans le fichier de config
           */
          scope.openAddCategoryPopup = () => {
            if (!scope.isAddCategoryPopupOpen) {
              scope.edit_category = {};
              // boolean pour éviter d'ouvrir plusieurs popup
              scope.isAddCategoryPopupOpen = true;
              ngDialog.open({
                template:
                    'js/XG/widgets/mapapp/dashboard/views/modal/modal.dashboard.category.add.html',
                className: 'ngdialog-theme-plain width360 nopadding miniclose',
                closeByDocument: false,
                scope: scope,
                preCloseCallback: () => {
                  scope.isAddCategoryPopupOpen = false;
                  scope.edit_category = null;
                }
              });
            }
          };

          /**
           * Sauvegarde une nouvelle catégorie vide dans le fichier de config du widget
           */
          scope.saveCategory = () => {
            scope.categories.push(scope.edit_category.name);
            scope.saveConfig();
          };

          /**
           * Vérifie si l'indicateur en cours d'édition possède une catégorie
           * Permet d'activer/désactiver le bouton de suppression de catégorie
           * @return {boolean} true si l'indicateur en cours d'édition possède une catégorie
           */
          scope.checkIndicatorHasCategory = () => {
            if (gaJsUtils.notNullAndDefined(scope.edit_indicator, 'category')) {
              return scope.edit_indicator.category !== 'default' && scope.edit_indicator.category !== '';
            } else {
              return false;
            }
          };

          /**
           * Ouvre la popup de confirmation pour supprimer une catégorie d'indicateurs
           */
          scope.openDeleteCategoryPopup = () => {
            if (!scope.isDeleteCategoryPopupOpen && scope.checkIndicatorHasCategory()) {
              scope.edit_category = {
                name: scope.edit_indicator.category
              }
              // boolean pour éviter d'ouvrir plusieurs popup
              scope.isDeleteCategoryPopupOpen = true;
              ngDialog.open({
                template:
                    'js/XG/widgets/mapapp/dashboard/views/modal/modal.dashboard.category.remove.html',
                className: 'ngdialog-theme-plain width500 nopadding miniclose',
                closeByDocument: false,
                scope: scope,
                preCloseCallback: () => {
                  scope.isDeleteCategoryPopupOpen = false;
                }
              });
            }
          };

          /**
           * Supprime une catégorie du tableau de catégories.<br>
           * Supprime la propriété "category" des éventuels indicateurs de la catégorie.<br>
           * Reconstruit le panel du widget.<br>
           * Sauvegarde le fichier de configuration.
           */
          scope.deleteCategory = () => {
            const index = scope.categories.findIndex(category => category === scope.edit_category.name);
            if (index > -1) {
              scope.categories.splice(index, 1);
            }
            // supprime la propriété "category" des éventuels indicateurs de la catégorie
            for (const indicator of scope.currentResources) {
              if (indicator.category === scope.edit_category.name) {
                delete indicator.category;
              }
            }
            // supprime la catégorie de l'indicateur en cours d'édition
            if (scope.edit_indicator && scope.edit_indicator.category && scope.edit_indicator.category === scope.edit_category.name) {
              delete scope.edit_indicator.category
            }
            // reconstruit le catalogue d'indicateurs
            // renvoie les éventuels indicateurs de la catégorie supprimée dans la catégorie "default"
            // affiche dans le panel du widget uniquement les indicateurs ayant un rôle commun à l'utilisateur
            scope.saveConfig();
            scope.edit_category = null;
          };
          scope.downloadFormats = ['pdf','rtf','xls'];
        };

        ///////////////////////////////////////////////////
        //
        // A l'ouverture du widget
        //
        ////////////////////////////////////////////////////


        scope.$on('openTools_dashboardwidget', (event, arg) => {
          if (arg.directive === 'dashboardwidget') {
            initWidget();
          }
        });
      },
    };
  };
  dashboardwidget.$inject = [
    'ConfigFactory',
    'ReportIndicatorFactory',
    'ngDialog',
    'DataStoreFactory',
    'ReportFactory',
    '$rootScope',
    'gaJsUtils'
  ];
  return dashboardwidget;
});
