/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

(function() {
  'use strict';

  /**
   * @name Resource
   * @constructor
   * @param {Object} $http - the Angular HTTP service
   * @param {Object} $q - the Angular promise/deferred service
   * @param {String} path - the base path of the external resource
   * @param {Object} options - extra attributes to be associated to the object
   */
  function Resource($http, $q, $window, $cookies, path, activeUser, options) {
    angular.extend(this, {
      _http: $http,
      _q: $q,
      _window: $window,
      _cookies: $cookies,
      _path: path,
      _activeUser: activeUser
    });
    angular.extend(this, options);
    // Trim trailing slash
    this._path = this._path.replace(/\/$/, '');
  }

  /**
   * @memberof Resource
   * @desc The factory we'll use to register with Angular.
   * @return a new Resource object
   */
  Resource.$factory =  ['$http', '$q', '$window', '$cookies', function($http, $q, $window, $cookies) {
    return function(path, activeUser, options) {
      return new Resource($http, $q, $window, $cookies, path, activeUser, options);
    };
  }];

  /**
   * @module SOGo.Common
   * @desc Factory registration of Resource in Angular module.
   */
  angular.module('SOGo.Common').factory('Resource', Resource.$factory);

  Resource.prototype.encodeURL = function(url) {
    var _this = this,
        segments = url;

    if (!angular.isArray(segments)) {
      segments = url.split('/');
    }
    return _.map(segments, function(segment) {
      return _this._window.encodeURIComponent(segment.toString());
    });
  };

  /**
   * @function userResource
   * @memberof Resource.prototype
   * @desc Create a new Resource object associated to a username different than the active user.
   * @param {String} uid - the user UID
   * @return a new Resource object
   */
  Resource.prototype.userResource = function(uid) {
    var path = _.compact(this._activeUser.folderURL.split('/'));

    if (uid)
      path.splice(path.length - 1, 1, escape(uid));

    return new Resource(this._http, this._q, this._window, this._cookies, '/' + path.join('/'), this._activeUser);
  };

  /**
   * @function path
   * @memberof Resource.prototype
   * @desc Create a URL of the resource context with any number of additional segments
   * @return an absolute URL
   */
  Resource.prototype.path = function() {
    var path = [this._path];

    if (arguments.length > 0)
      Array.prototype.push.apply(path, Array.prototype.slice.call(arguments));

    return path.join('/');
  };

  /**
   * @function fetch
   * @memberof Resource.prototype
   * @desc Fetch resource using a specific folder, action and/or parameters.
   * @param {string} folderId - the folder on which the action will be applied (ex: addressbook, calendar)
   * @param {string} action - the action to be used in the URL
   * @param {Object} params - Object parameters injected through the $http service
   * @return a promise
   */
  Resource.prototype.fetch = function(folderId, action, params) {
    var deferred = this._q.defer(),
        path = [this._path];
    if (folderId) path.push(this.encodeURL(folderId));
    if (action)   path.push(action);
    path = _.compact(_.flatten(path)).join('/');

    this._http({
      method: 'GET',
      url: path,
      params: params
    })
      .then(function(response) {
        return deferred.resolve(response.data);
      }, deferred.reject);

    return deferred.promise;
  };

  /**
   * @function quietFetch
   * @memberof Resource.prototype
   * @desc Fetch resource using a specific folder, action and/or parameters, but disable the global
   *       error interceptor.
   * @param {string} folderId - the folder on which the action will be applied (ex: addressbook, calendar)
   * @param {string} action - the action to be used in the URL
   * @param {Object} params - Object parameters injected through the $http service
   * @return a promise
   */
  Resource.prototype.quietFetch = function(folderId, action, params) {
    var deferred = this._q.defer(),
        path = [this._path];
    if (folderId) path.push(this.encodeURL(folderId));
    if (action)   path.push(action);
    path = _.compact(_.flatten(path)).join('/');

    this._http({
      method: 'GET',
      url: path,
      params: params,
      transformResponse: function(data) {
        var jsonData;
        try {
          jsonData = angular.fromJson(data);
        }
        catch (e) {
          jsonData = {};
        }
        return angular.extend({ quiet: true }, jsonData);
      }
    })
      .then(function(response) {
        return deferred.resolve(response.data);
      }, deferred.reject);

    return deferred.promise;
  };

  /**
   * @function newguid
   * @memberof Resource.prototype
   * @desc Fetch a new GUID on the specified folder ID.
   * @return a promise of the new data structure
   */
  Resource.prototype.newguid = function(folderId) {
    var deferred = this._q.defer(),
        path = this._path + '/' + folderId + '/newguid';

    this._http
      .get(path)
      .then(function(response) {
        return deferred.resolve(response.data);
      }, deferred.reject);

    return deferred.promise;
  };

  /**
   * @function create
   * @memberof Resource.prototype
   * @desc Create a new resource using a specific action (post).
   * @param {string} action - the action to be used in the URL
   * @param {string} name - the new resource's name
   * @return a promise
   */
  Resource.prototype.create = function(action, name) {
    var deferred = this._q.defer(),
        path = this._path + '/' + action;

    this._http
      .post(path, { name: name })
      .then(function(response) {
        return deferred.resolve(response.data);
      }, deferred.reject);

    return deferred.promise;
  };

  /**
   * @function post
   * @memberof Resource.prototype
   * @desc Post a resource attributes on the server.
   * @return a promise
   */
  Resource.prototype.post = function(id, action, data) {
    var deferred = this._q.defer(),
        path = [this._path];
    if (id) path.push(this.encodeURL(id));
    if (action) path.push(action);
    path = _.compact(_.flatten(path)).join('/');

    this._http
      .post(path, data)
      .then(function(response) {
        return deferred.resolve(response.data);
      }, deferred.reject);

    return deferred.promise;
  };

  /**
   * @function save
   * @memberof Resource.prototype
   * @desc Save a resource attributes on the server (post /save).
   * @return a promise
   */
  Resource.prototype.save = function(id, newValue, options) {
    var action = (options && options.action)? options.action : 'save';

    return this.post(id, action, newValue);
  };

  /**
   * @function download
   * @memberof Resource.prototype
   * @desc Download a file from the server. Requires FileSaver.js.
   * @see {@link http://blog.davidjs.com/2015/07/download-files-via-post-request-in-angularjs/|Download files via POST request in AngularJs}
   * @see {@link https://github.com/eligrey/FileSaver.js|FileSaver.js}
   * @return a promise
   */
  Resource.prototype.download = function(id, action, data, options) {
    var deferred = this._q.defer(),
        type = (options && options.type)? options.type : 'application/zip',
        path = [this._path];
    if (id) path.push(this.encodeURL(id));
    if (action) path.push(action);
    path = _.compact(_.flatten(path)).join('/');

    if (typeof saveAs == 'undefined') {
      throw new Error('To use Resource.download, FileSaver.js must be loaded.');
    }

    function getFileNameFromHeader(header) {
      var result;

      if (!header) return null;
      result = header.split(";")[1].trim().split("=")[1];

      return result.replace(/"/g, '');
    }

    return this._http({
      method: 'POST',
      url: path,
      data: data,
      headers: {
        accept: type
      },
      responseType: 'arraybuffer',
      cache: false,
      transformResponse: function (data, headers, status) {
        var fileName, result, blob = null;

        if (status < 200 || status > 299) {
          throw new Error('Bad gateway');
        }
        if (data) {
          blob = new Blob([data], { type: type });
        }
        if (options && options.filename) {
          fileName = options.filename;
        }
        else {
          fileName = getFileNameFromHeader(headers('content-disposition'));
        }
        saveAs(blob, fileName);
      }
    });
  };

  Resource.prototype.open = function(id, action) {
    var path = [this._path], xsrfToken;
    xsrfToken = this._cookies.get('XSRF-TOKEN');
    if (id) path.push(id);
    if (action) path.push(action);
    path = _.compact(_.flatten(path)).join('/');
    if (xsrfToken) {
      path += '?X-XSRF-TOKEN=' + xsrfToken;
    }

    this._window.location.href = path;
  };

  /**
   * @function remove
   * @memberof Resource.prototype
   * @desc Delete a resource (get /delete).
   * @return a promise
   */
  Resource.prototype.remove = function(uid) {
    var deferred = this._q.defer(),
        path = _.flatten([this._path, this.encodeURL(uid), 'delete']).join('/');

    this._http
      .get(path)
      .then(function(response) {
        return deferred.resolve(response.data);
      }, deferred.reject);

    return deferred.promise;
  };

})();
