'use strict';
define(function() {
  var FeatureAttachmentFactory = function(
    $http,
    gaUrlUtils,
    $q,
    $timeout,
    ngDialog,
    $rootScope,
    $filter
  ) {
    var FeatureAttachmentFactory = {};
    let CHUNK_SIZE = 5e6;
    /**
     * Class : FeatureAttachmentFactory
     * Factory WebServices
     */

    /**
     * Function: add
     */
    function addServerFile(fileName, layerName, featureId, newFileName) {
      //-- If a new file name is given in the pramaters then add it to the HTTP request.
      var newFileNameParam = '';
      if (newFileName != undefined)
        newFileNameParam = '&newFileName=' + newFileName;
      return $http.post(
        '/services/{portalid}/attachment/addServerFile?f=json' +
          '&fileName=' +
          fileName +
          '&layerName=' +
          layerName +
          '&featureId=' +
          featureId +
          newFileNameParam
      );
    }
    /**
     * Function: add
     */
    function add(
      senddata,
      fileName,
      partCountStr,
      layerName,
      featureId,
      fileToUnzipStr,
      zipFileAssociation,
      zipExtractUnusedEntriesStr,
      isPhoto
    ) {
      if (typeof ancAppAndroid !== 'undefined') {
        var defer = $q.defer();
        var response = {
          data: undefined,
        };
        var value;
        var val = ancAppAndroid.validerSignature(senddata, featureId, fileName);
        try {
          value = JSON.parse(val);
        } catch (e) {
          e.stack;
          value = val;
        }
        response.data = value;
        defer.resolve(response);
        return defer.promise;
      } else {
        var fName = gaUrlUtils.encodeUriQuery(fileName);
        // fix Extension toLowerCase
        if (isPhoto) {
          const lastDotPosition = fName.lastIndexOf('.');
          if (lastDotPosition > 0) {
            const extension = fName.substr(lastDotPosition, fName.length);
            fName = fName.replace(
              extension,
              extension.toLowerCase()
            );
          }
        }
        return $http.post(
          '/services/{portalid}/attachment/add?f=json' +
            '&fileName=' +
            fName +
            '&partCountStr=' +
            partCountStr +
            '&layerName=' +
            layerName +
            '&featureId=' +
            featureId +
            '&fileToUnzipStr=' +
            fileToUnzipStr +
            '&zipFileAssociation=' +
            zipFileAssociation +
            '&zipExtractUnusedEntriesStr=' +
            zipExtractUnusedEntriesStr+
            '&isPhoto=' +
            isPhoto,
          senddata
        );
      }
    }
    /**
   * remove Fichier 
   * 
   * @param {*} fileNames 
   * @param {*} layerName 
   * @param {*} featureId 
   * @returns 
   */
    function remove(fileNames, layerName, featureId) {
      var fNames;
      if (typeof fileNames == 'string')
        fNames = gaUrlUtils.encodeUriQuery(fileNames);
      else {
        fNames = [];
        for (var i in fileNames)
          fNames.push(gaUrlUtils.encodeUriQuery(fileNames[i]));
      }

      //-- Retourne la promesse du retour de la requête.
      return $http.get(
        '/services/{portalid}/attachment/remove?f=json' +
          '&fileNames=' +
          fNames +
          '&layerName=' +
          layerName +
          '&featureId=' +
          featureId
      )
      .catch(error => {
        if (typeof error.data === 'string') {
          require('toastr').error($filter('translate')(error.data));
        }
      });
    }
    /**
     * rename Fichier
     *
     * @param {*} fileName
     * @param {*} newFileName
     * @param {*} layerName
     * @param {*} featureId
     * @returns
     */
    function rename(fileName, newFileName, layerName, featureId) {
      //-- Retourne la promesse du retour de la requête.
      return $http.get(
        '/services/{portalid}/attachment/rename?f=json' +
          '&fileName=' +
          fileName +
          '&newFileName=' +
          newFileName +
          '&layerName=' +
          layerName +
          '&featureId=' +
          featureId
      );
    }
    /**
     * Function: list
     */
    function list(layerName, featureId) {
      if (typeof ancAppAndroid !== 'undefined') {
        var defer = $q.defer();
        var response = {
          data: undefined,
        };
        var value;
        var val = ancAppAndroid.listAttachement(layerName, featureId);
        try {
          value = JSON.parse(val);
        } catch (e) {
          e.stack;
          value = val;
        }
        response.data = value;
        defer.resolve(response);
        return defer.promise;
      } else {
        var promise = $http.get(
          '/services/{portalid}/attachment/list?f=json' +
            '&layerName=' +
            layerName +
            '&featureId=' +
            featureId
        );

        return promise;
      }
    }

    /**
     * Function: listallinformations
     */
    function listallinformations(layerName, featureId) {
      var infos;
      try{
        infos = $http.get(
          '/services/{portalid}/attachment/listAllInformations?f=json' +
                '&layerName=' +
                layerName +
                '&featureId=' +
                featureId
        );
      }
      catch(e){
        e.stack;
      }
      return infos;
    }

    /**
     * Récupère le fichier attaché spécifié
     * @param {string} fileName nom du fichier attaché
     * @param {string} layerName nom du composant auquel l'objet appartient
     * @param {string} featureId id de l'objet auquel le fichier est attaché
     * @param {string} methode "pour_download" -> return a link to run file download,
     *        "contenu" -> return file content
     * @return {Promise}
     */
    function getdownloadurl(fileName, layerName, featureId, methode) {

      var fName = gaUrlUtils.encodeUriQuery(fileName);
      return $http.get(
        '/services/{portalid}/attachment/getdownloadurl?f=json' +
          '&fileName=' +
          fName +
          '&layerName=' +
          layerName +
          '&featureId=' +
          featureId +
          '&methode=' +
          methode
      );
    }


    /**
     * Function: getcontent
     */
    function getcontent(fileName, layerName, featureId, methode) {
      var fName = gaUrlUtils.encodeUriQuery(fileName);
      return $http.get(
        '/services/{portalid}/attachment/getcontent?f=json' +
          '&fileName=' +
          fName +
          '&layerName=' +
          layerName +
          '&featureId=' +
          featureId +
          '&methode=' +
          methode
      );
    }
    /**
     * Function: removeall
     */
    function removeall(layerName, featureId) {
      return $http.get(
        '/services/{portalid}/attachment/removeall?f=json' +
          '&layerName=' +
          layerName +
          '&featureId=' +
          featureId
      );
    }

    /**
     * Function: changeid
     */
    function changeid(layerName, uniqid, featureId) {
      return $http.get(
        '/services/{portalid}/attachment/changeid?f=json' +
          '&layerName=' + layerName +
          '&uniqid=' + uniqid +
          '&featureId=' + featureId
      );
    }

    function prepareMailAttachment(attachmentsInfos) {
      var promise = $http.post(
        '/services/{portalid}/attachment/prepareMailAttachment',
        attachmentsInfos
      );

      return promise;
    }

    function uploadFullFile(theFile, layerName, featId, defer,isPhoto) {
      var reader = new FileReader();
      reader.onload = function(e) {
        var data = e.target.result;
        add(data, theFile.name, '1', layerName, featId, false,'','',isPhoto)
        .then((res) => {
          defer.resolve(res);
        }, () => {
          defer.reject();
        });
      };
      reader.readAsDataURL(theFile);
    }

    /**
     *     Chargement d'un gros fichier par morceau.
     */
    function uploadFileToSplit(theFile, layerName, featId, defer, progFile) {
      var fr = new FileReader();
      var indPart = 1,
        offset = 0;
      var filename = theFile.name;

      var loadedPartCnt = 0, partSentCount = 0;
      //-- Calcul du nombre de morceau à envoyer
      let partCount = Math.ceil(theFile.size / CHUNK_SIZE);
      let scope, pbDialog;

      function setProgression(value) {
        if (progFile != undefined) {
          progFile.progression = value;
        } else scope.progression = value;
      }
      scope = progFile == undefined ? $rootScope.$new() : undefined;
      setProgression(0);
      let loading = false;

      //-- Affichage un pourcentage d avancement pour que l utilisateur ne s inquite pas trop.
      function showPercent() {
        pbDialog = ngDialog.open({
          template: 'js/XG/modules/featureattachment/views/FafProgressBar.html',
          className: 'ngdialog-theme-plain width600',
          closeByDocument: false,
          scope: scope,
        });
      }

      function closePercent() {
        setProgression(100);
        if (progFile != undefined)
          //-- Dans le cas où l'appelant charge plusieurs fichier,
          //-- on enlève la barre de progression.
          $timeout(function() {
            progFile.progressBarIsNeeded = false;
          }, 2500);

        $timeout(function() {
          if (scope != undefined) pbDialog.close();
        }, 8000);
      }

      //-- Lecture du morceau de fichier suivant.
      let runLoadOfSomeParts = function() {
        if (offset < theFile.size) {
          offset += CHUNK_SIZE;
          if (offset < theFile.size) {
            let bufferSize =
              offset + CHUNK_SIZE > theFile.size
                ? theFile.size - CHUNK_SIZE
                : CHUNK_SIZE;
            let slice = theFile.slice(offset, offset + bufferSize);
            loading = true;
            fr.readAsBinaryString(slice);
          }
        }
      };

      function startLoadOfSomeParts() {
        if (loading) $timeout(startLoadOfSomeParts, 100);
        else runLoadOfSomeParts();
      }

      fr.onloadend = function() {
        //-- La lecture du morceau est complétemnt finie
        //-- on peut lire le suivant si cela est envisageable.
        //-- On peut envoyer plusieurs morceau à la fois
        //-- mais si on en envoie trop, le serveur va saturer.
        //-- On fixe une limite du nombre de morceau envoyé
        //-- en simultané à 8.
        loading = false;
        if (partSentCount < 7) runLoadOfSomeParts();
      };

      fr.onload = function(e) {
        //-- On récupère le résultat de la lecture d'un morceau du fichier.
        //-- On converti ce contenu en base 64.
        var buf = window.btoa(e.target.result);


        let loadNextPart = function() {
          //-- Envoi du morceau lu au serveur.
          partSentCount++;
          add(
            'base64:' + buf,
            indPart++ + '_' + filename,
            partCount,
            layerName,
            featId,
            false
          ).then(function(res) {
            partSentCount--;
            if (partSentCount < 2) startLoadOfSomeParts();
            if (++loadedPartCnt == partCount) {
              if (progFile == undefined) closePercent();
              //-- Tous les morceaux ont été chargés sur le serveur.
              defer.resolve(res);
            }
            setProgression(Math.ceil((100 * loadedPartCnt) / partCount));
          });
        };

        loadNextPart();
      };

      if (progFile == undefined) $timeout(showPercent, 0);
      loading = true;
      fr.readAsBinaryString(theFile.slice(0, CHUNK_SIZE));
    }

    function uploadFile(theFile, layerName, featId, progFile, isPhoto) {
      let defer = $q.defer();
      if (theFile.size <= CHUNK_SIZE || theFile.name.endsWith(".jpeg") ||
          theFile.name.endsWith(".jpg") || theFile.name.endsWith(".png")) {
        uploadFullFile(theFile, layerName, featId, defer,isPhoto);
      } else {
        if (progFile != undefined) progFile.progressBarIsNeeded = true;
        uploadFileToSplit(theFile, layerName, featId, defer, progFile);
      }

      return defer.promise;
    };


    function importFeatureAttachmentsArchive(file, featureName, attributeName) {
      const defer = $q.defer();

      const fr = new FileReader();
      const filename = file.name;

      let indPart = 1;
      let offset = 0;

      let loadedPartCnt = 0;
      let partSentCount = 0;
      let partCount = Math.ceil(file.size / CHUNK_SIZE);

      let loading = false;

      fr.onloadend = function(e) {
        loading = false;
        if (partSentCount < 7) runLoadOfSomeParts();
      };

      fr.onload = function(e) {
        var buf = window.btoa(e.target.result);
        loadNextPart(buf);
      };

      loading = true;
      fr.readAsBinaryString(file.slice(0, CHUNK_SIZE));

      //-- Lecture du morceau de fichier suivant.
      function runLoadOfSomeParts() {
        if (offset < file.size) {
          offset += CHUNK_SIZE;
          if (offset < file.size) {
            let bufferSize =
              offset + CHUNK_SIZE > file.size
                ? file.size - CHUNK_SIZE
                : CHUNK_SIZE;
            let slice = file.slice(offset, offset + bufferSize);
            loading = true;
            fr.readAsBinaryString(slice);
          }
        }
      }

      function startLoadOfSomeParts() {
        if (loading) $timeout(startLoadOfSomeParts, 100);
        else runLoadOfSomeParts();
      }

      function loadNextPart(buf) {
        partSentCount++;
        $http.post(
          getApiAttachmentUrl('importFeatureAttachmentsArchive', {
            archiveName: indPart++ + '_' + filename,
            partCountStr: partCount,
            featureName, attributeName
          }),
          'base64:' + buf
        )
          .then((res) => {
            partSentCount--;
            if (partSentCount < 2) startLoadOfSomeParts();
            if (++loadedPartCnt == partCount) {
              defer.resolve(res);
            }
          }).catch(() => defer.reject());
      }
      return defer.promise;
    }

    function mapFeaturesAttachments(fileName, featureName, attributeName, removeOrphans) {
      return $http.get(
        getApiAttachmentUrl('mapFeaturesAttachments', {archiveName: fileName, featureName, attributeName, removeOrphans})
      );
    }

    function downloadFeaturesAttachments(featureName, attributeName) {
      return $http.get(
        getApiAttachmentUrl('downloadFeaturesAttachments', {featureName, attributeName})
      );
    }
    function checkDownloadAttributeStatus(processId) {
      return $http.get(
        getApiAttachmentUrl('checkDownloadAttributeStatus', {processId})
      );
    }

    function deleteFeaturesAttachments(featureName, attributeName, selectedFeaturesOfLayer) {
      return $http.post(
        getApiAttachmentUrl('deleteFeaturesAttachments'),
        {
          featureName: featureName,
          attributeName: attributeName,
          featureIds: selectedFeaturesOfLayer || []
        }
      );
    }

    function getApiAttachmentUrl(url, data) {
      data = data || {};
      data.f = 'json';
      const queryParamsUrl = Object.keys(data)
        .map(queryParam => `${queryParam}=${data[queryParam]}`)
        .join('&');
      return `/services/{portalid}/attachment/${url}?${queryParamsUrl}`;
    }

    /**
     * Copie une liste de fichiers présents dans le dossier UPLOAD vers le sous-dossier objet du répertoire ATTACHMENTS du repo
     * @param processUid uid du processus d'import (défini par la dropzone)
     * @param ftiuid uid du fti
     * @param attributes liste des attributs d'attachments
     * @param featsCreated liste des features sauvegardées retournée par le back
     * @return {Promise} contenant true si l'import est réussi
     */
    function attachImportedFiles(processUid, ftiuid, attributes, featsCreated) {
      const attrs = attributes.join(',');
      return $http.post(
        '/services/{portalid}/attachment/attachImportedFiles?f=json' +
          '&processId=' +
          processUid +
          '&ftiuid=' +
          ftiuid +
          '&attributes=' +
          attrs,
        featsCreated
      ).catch(error => console.error('attachImportedFiles : ' , error));
    }

    /**
     * Charge un fichier dans le dossier "UPLOAD" du repo
     * @param {string} layerName nom du composant
     * @param {string} featureId identifiant de l'objet du formulaire
     * @param {File} file fichier à charger
     * @return {*} Promise au statut 200 si l'import a réussi
     */
    const addFile = (layerName, featureId, file) => {
      if (!layerName || !featureId || !file) {
        return;
      }
      const fd = new FormData();
      fd.append('layerName', layerName);
      fd.append('featureId', featureId);
      fd.append('file', file);
      return $http.post('/services/{portalid}/attachment/addFile?f=json', fd,
          {
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined },
          }
      ).catch((error) => {
        console.error('addFile : Impossible de charger le fichier ' + file.name
        + error && error.data && error.data.message ? '\n' + error.data.message : '');
        require('toastr').error($filter('translate')('featureattachment.importToAttachmentsFailed'));
      });
    };

    /**
     * Copie les fichiers attachés d'un feature vers le sous-répertoire des fichiers attachés d'un autre feature
     * Les features peuvent être de composants différents
     * @param {objet} source conteneur du nom de composant source et de l'id de la feature source
     * @param {objet} destination conteneur du nom de composant destination et de l'id de la feature destination
     * @param {string[]} files liste de noms de fichiers
     * @param {boolean} removeFilesFromSource true pour supprimer les fichiers de la source
     */
    const copyFilesToFeature = (source, destination, files, removeFilesFromSource ) => {
      if (!files) {
        files = [];
      }
      if (source.hasOwnProperty('layername') && source.hasOwnProperty('featureid') &&
          destination.hasOwnProperty('layername') && destination.hasOwnProperty('featureid')) {
        return $http.post('/services/{portalid}/attachment/copyFilesToFeature?f=json' +
            '&sourceLayerName=' + source.layername +
            '&sourceFeatureId=' + source.featureid +
            '&destinationLayerName=' + destination.layername +
            '&destinationFeatureId=' + destination.featureid +
            '&removeall=' + removeFilesFromSource,
            JSON.stringify(files))
        .then((res) => {
          if (Array.isArray(res.data)) {
            console.info('Copie réussie des fichiers attachés depuis le feature ',
                source.featureid, ' vers le feature ', destination.featureid, ' : \n',
                res.data.join(', '));
          }
        })
        .catch((error) => {
          require('toastr').error(error.message);
          if (Array.isArray(error.details) && error.details.length > 0) {
            console.error(error.details[0]);
          }
        });
      }
    };

    /**
     * Dans le cadre d'une duplication d'une Intervention Simple (reprogrammation).
     * Copie les fichiers attachés d'un feature vers des sous-répertoires du dossier UPLOAD
     * @param {object} source conteneur du nom de composant source et de l'id de la feature source
     * @param {object} mapFilesWithAttachmentId map de correspondance entre un id et les noms de fichiers à copier dans le sous-dossier ayant l'id pour nom
     * @return {Promise} tableau des noms de fichiers correctement copiés
     */
    const copyFilesToUpload = (source, mapFilesWithAttachmentId) => {
      if (source.hasOwnProperty('layername') && source.hasOwnProperty('featureid')) {
        return $http.post('/services/{portalid}/attachment/copyFilesToUpload?f=json' +
            '&sourceLayerName=' + source.layername +
            '&sourceFeatureId=' + source.featureid,
            JSON.stringify(mapFilesWithAttachmentId))
        .catch((error) => {
          require('toastr').error(error.message);
          if (Array.isArray(error.details) && error.details.length > 0) {
            console.error(error.details[0]);
          }
        });
      }
    };

    /**
     * Evite les erreurs lorsque le champ attachments contient une ligne vide.
     * Méthode utilisée pour copier les fichiers attachés lors de la reprogrammation d'une IS
     * @param attributeType nom du type d'attribut (on ne traite que les attachement-multi)
     * @param attachedFilesString liste à vérifier des fichiers attachés avec séparateur virgule
     * @return {string} liste des fichiers attachés avec séparateur virgule
     */
    const preventEmptyAttachedFile = (attributeType, attachedFilesString) => {
      if (typeof attachedFilesString === 'string' && attachedFilesString.length > 0) {
        let cleanAttachedFilesStr = attachedFilesString.slice();
        if (attributeType === 'g2c.attachments') {
          if (cleanAttachedFilesStr.startsWith(',')) {
            cleanAttachedFilesStr = cleanAttachedFilesStr.slice(1);
          }
          if (attachedFilesString.includes(',,')) {
            cleanAttachedFilesStr = cleanAttachedFilesStr.replaceAll(',,', ',');
          }
          if (attachedFilesString.endsWith(',')) {
            cleanAttachedFilesStr = cleanAttachedFilesStr.slice(-1);
          }
        }
        return cleanAttachedFilesStr;
      }
      return '';
    };

    /**
     * Importe et mappe les pièces jointes d'un fichier ZIP vers des features
     * @param {File} file - Le fichier ZIP à importer
     * @param {string} featureName - Le nom du feature
     * @param {string} attributeName - Le nom de l'attribut
     * @param {boolean} removeOrphans - Indique si les références orphelines doivent être supprimées
     * @returns {Promise} Une promesse qui se résout avec l'UID du processus
     */
    const importAndMapAttachments = (file, featureName, attributeName, removeOrphans) => {
      const defer = $q.defer();
      const fr = new FileReader();
      const filename = file.name;
      let indPart = 1;
      let offset = 0;
      let loadedPartCnt = 0;
      let partCount = Math.ceil(file.size / CHUNK_SIZE);
      let loading = false;
      let processUid = '';

      function processNextPart(base64) {
        return $http.post(
          getApiAttachmentUrl('importAndMapAttachments', {
            archiveName: (partCount > 1 ? indPart + '_' : '') + filename,
            partCountStr: partCount,
            featureName: featureName,
            attributeName: attributeName,
            removeOrphans: removeOrphans,
            processUid: processUid
          }),
          'base64:' + base64
        ).then((res) => {
          processUid = res.data;
          loadedPartCnt++;
          indPart++;
          return res;
        });
      }

      fr.onload = function(e) {
        const arrayBuffer = e.target.result;
        const bytes = new Uint8Array(arrayBuffer);
        let binary = '';
        for (let i = 0; i < bytes.byteLength; i++) {
          binary += String.fromCharCode(bytes[i]);
        }
        const base64 = window.btoa(binary);
        
        processNextPart(base64).then(() => {
          if (loadedPartCnt < partCount) {
            // Lecture de la partie suivante
            offset += CHUNK_SIZE;
            if (offset < file.size) {
              let bufferSize = offset + CHUNK_SIZE > file.size ? file.size - offset : CHUNK_SIZE;
              let slice = file.slice(offset, offset + bufferSize);
              fr.readAsArrayBuffer(slice);
            }
          } else {
            // Toutes les parties ont été envoyées
            defer.resolve(processUid);
          }
        }).catch((error) => {
          defer.reject(error);
        });
      };

      // Lecture de la première partie
      let firstPartSize = Math.min(CHUNK_SIZE, file.size);
      fr.readAsArrayBuffer(file.slice(0, firstPartSize));

      return defer.promise;
    }

    /**
     * Vérifie l'état d'un processus d'import
     * @param {string} processUid - L'UID du processus à vérifier
     * @returns {Promise} Une promesse qui se résout avec l'état du processus
     */
    const checkImportProcessStatus = (processUid) => {
      return $http.get(`/services/{portalid}/process/get/file?uid=${processUid}`);
    }

    return {
      FeatureAttachmentFactory: FeatureAttachmentFactory,
      add: add,
      remove: remove,
      rename: rename,
      list: list,
      listallinformations: listallinformations,
      getdownloadurl: getdownloadurl,
      getcontent: getcontent,
      removeall: removeall,
      addServerFile: addServerFile,
      changeid: changeid,
      prepareMailAttachment: prepareMailAttachment,
      uploadFile: uploadFile,
      importFeatureAttachmentsArchive: importFeatureAttachmentsArchive,
      mapFeaturesAttachments: mapFeaturesAttachments,
      downloadFeaturesAttachments: downloadFeaturesAttachments,
      checkDownloadAttributeStatus: checkDownloadAttributeStatus,
      deleteFeaturesAttachments: deleteFeaturesAttachments,
      attachImportedFiles: attachImportedFiles,
      addFile: addFile,
      copyFilesToFeature: copyFilesToFeature,
      copyFilesToUpload: copyFilesToUpload,
      preventEmptyAttachedFile: preventEmptyAttachedFile,
      importAndMapAttachments: importAndMapAttachments,
      checkImportProcessStatus: checkImportProcessStatus
    };
  };
  FeatureAttachmentFactory.$inject = ['$http', 'gaUrlUtils', '$q', '$timeout',
    'ngDialog','$rootScope', '$filter'
  ];
  return FeatureAttachmentFactory;
});
