text: const $      = require('jquery');
  const _      = require('underscore');
  const Tokens = require('./tokens');
  
  /**
   * @param {String}  message
   * @param {*}       debug
   * @param {Number} code
   * @constructor
   */
  const ApiError = function (message, debug, code) {
      let temp     = Error.call(this, message);
      temp.name    = this.name = 'ApiError';
      this.stack   = temp.stack;
      this.message = temp.message;
      this.debug   = debug;
      this.code    = code;
  };
  
  ApiError.prototype = Object.create(Error.prototype, {
      constructor: {
          value:        ApiError,
          writable:     true,
          configurable: true
      }
  });
  
  /**
   *
   * @param   {String} verb
   * @param   {String} path
   * @param   {Object} [data]
   * @param   {Object} [options]
   * @returns {Promise}
   */
  function fetch(verb, path, data, options) {
      options = options || {};
  
      return new Promise(function (resolve, reject) {
          let api_url = '/api/';
          let url     = api_url + path;
          let token   = Tokens.getTopToken();
  
          if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') {
              data = JSON.stringify(data);
          }
  
          $.ajax({
              url:         url,
              data:        typeof data === 'object' ? JSON.stringify(data) : data,
              type:        verb,
              dataType:    'json',
              contentType: options.contentType || 'application/json; charset=UTF-8',
              processData: options.processData || true,
              crossDomain: true,
              timeout:     options.timeout ? options.timeout : 300000,
              xhrFields:   {
                  withCredentials: true
              },
  
              beforeSend: function (xhr) {
                  xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
              },
  
              success: function (data, textStatus, response) {
                  let total = response.getResponseHeader('X-Dataset-Total');
                  if (total !== null) {
                      resolve({
                          data:       data,
                          pagination: {
                              total:  parseInt(total, 10),
                              offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10),
                              limit:  parseInt(response.getResponseHeader('X-Dataset-Limit'), 10)
                          }
                      });
                  } else {
                      resolve(response);
                  }
              },
  
              error: function (xhr, status, error_thrown) {
                  let code = 400;
  
                  if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') {
                      error_thrown = xhr.responseJSON.error.message;
                      code         = xhr.responseJSON.error.code || 500;
                  }
  
                  reject(new ApiError(error_thrown, xhr.responseText, code));
              }
          });
      });
  }
  
  /**
   *
   * @param {Array} expand
   * @returns {String}
   */
  function makeExpansionString(expand) {
      let items = [];
      _.forEach(expand, function (exp) {
          items.push(encodeURIComponent(exp));
      });
  
      return items.join(',');
  }
  
  /**
   * @param   {String}   path
   * @param   {Array}    [expand]
   * @param   {String}   [query]
   * @returns {Promise}
   */
  function getAllObjects(path, expand, query) {
      let params = [];
  
      if (typeof expand === 'object' && expand !== null && expand.length) {
          params.push('expand=' + makeExpansionString(expand));
      }
  
      if (typeof query === 'string') {
          params.push('query=' + query);
      }
  
      return fetch('get', path + (params.length ? '?' + params.join('&') : ''));
  }
  
  function FileUpload(path, fd) {
      return new Promise((resolve, reject) => {
          let xhr   = new XMLHttpRequest();
          let token = Tokens.getTopToken();
  
          xhr.open('POST', '/api/' + path);
          xhr.overrideMimeType('text/plain');
          xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
          xhr.send(fd);
  
          xhr.onreadystatechange = function () {
              if (this.readyState === XMLHttpRequest.DONE) {
                  if (xhr.status !== 200 && xhr.status !== 201) {
                      reject(new Error('Upload failed: ' + xhr.status));
                  } else {
                      resolve(xhr.responseText);
                  }
              }
          };
      });
  }
  
  module.exports = {
      status: function () {
          return fetch('get', '');
      },
  
      Tokens: {
  
          /**
           * @param   {String}  identity
           * @param   {String}  secret
           * @param   {Boolean} [wipe]       Will wipe the stack before adding to it again if login was successful
           * @returns {Promise}
           */
          login: function (identity, secret, wipe) {
              return fetch('post', 'tokens', {identity: identity, secret: secret})
                  .then(response => {
                      if (response.token) {
                          if (wipe) {
                              Tokens.clearTokens();
                          }
  
                          // Set storage token
                          Tokens.addToken(response.token);
                          return response.token;
                      } else {
                          Tokens.clearTokens();
                          throw(new Error('No token returned'));
                      }
                  });
          },
  
          /**
           * @returns {Promise}
           */
          refresh: function () {
              return fetch('get', 'tokens')
                  .then(response => {
                      if (response.token) {
                          Tokens.setCurrentToken(response.token);
                          return response.token;
                      } else {
                          Tokens.clearTokens();
                          throw(new Error('No token returned'));
                      }
                  });
          }
      },
  
      Users: {
  
          /**
           * @param   {Number|String}  user_id
           * @param   {Array}           [expand]
           * @returns {Promise}
           */
          getById: function (user_id, expand) {
              return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : ''));
          },
  
          /**
           * @param   {Array}    [expand]
           * @param   {String}   [query]
           * @returns {Promise}
           */
          getAll: function (expand, query) {
              return getAllObjects('users', expand, query);
          },
  
          /**
           * @param   {Object}  data
           * @returns {Promise}
           */
          create: function (data) {
              return fetch('post', 'users', data);
          },
  
          /**
           * @param   {Object}   data
           * @param   {Number}   data.id
           * @returns {Promise}
           */
          update: function (data) {
              let id = data.id;
              delete data.id;
              return fetch('put', 'users/' + id, data);
          },
  
          /**
           * @param   {Number}  id
           * @returns {Promise}
           */
          delete: function (id) {
              return fetch('delete', 'users/' + id);
          },
  
          /**
           *
           * @param   {Number}   id
           * @param   {Object}   auth
           * @returns {Promise}
           */
          setPassword: function (id, auth) {
              return fetch('put', 'users/' + id + '/auth', auth);
          },
  
          /**
           * @param   {Number}  id
           * @returns {Promise}
           */
          loginAs: function (id) {
              return fetch('post', 'users/' + id + '/login');
          },
  
          /**
           *
           * @param   {Number}   id
           * @param   {Object}   perms
           * @returns {Promise}
           */
          setPermissions: function (id, perms) {
              return fetch('put', 'users/' + id + '/permissions', perms);
          }
      },
  
      Nginx: {
  
          ProxyHosts: {
              /**
               * @param   {Array}    [expand]
               * @param   {String}   [query]
               * @returns {Promise}
               */
              getAll: function (expand, query) {
                  return getAllObjects('nginx/proxy-hosts', expand, query);
              },
  
              /**
               * @param {Object}  data
               */
              create: function (data) {
                  return fetch('post', 'nginx/proxy-hosts', data);
              },
  
              /**
               * @param   {Object}   data
               * @param   {Number}  data.id
               * @returns {Promise}
               */
              update: function (data) {
                  let id = data.id;
                  delete data.id;
                  return fetch('put', 'nginx/proxy-hosts/' + id, data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              delete: function (id) {
                  return fetch('delete', 'nginx/proxy-hosts/' + id);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              get: function (id) {
                  return fetch('get', 'nginx/proxy-hosts/' + id);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              enable: function (id) {
                  return fetch('post', 'nginx/proxy-hosts/' + id + '/enable');
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              disable: function (id) {
                  return fetch('post', 'nginx/proxy-hosts/' + id + '/disable');
              }
          },
  
          RedirectionHosts: {
              /**
               * @param   {Array}    [expand]
               * @param   {String}   [query]
               * @returns {Promise}
               */
              getAll: function (expand, query) {
                  return getAllObjects('nginx/redirection-hosts', expand, query);
              },
  
              /**
               * @param {Object}  data
               */
              create: function (data) {
                  return fetch('post', 'nginx/redirection-hosts', data);
              },
  
              /**
               * @param   {Object}   data
               * @param   {Number}   data.id
               * @returns {Promise}
               */
              update: function (data) {
                  let id = data.id;
                  delete data.id;
                  return fetch('put', 'nginx/redirection-hosts/' + id, data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              delete: function (id) {
                  return fetch('delete', 'nginx/redirection-hosts/' + id);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              get: function (id) {
                  return fetch('get', 'nginx/redirection-hosts/' + id);
              },
  
              /**
               * @param  {Number}   id
               * @param  {FormData} form_data
               * @params {Promise}
               */
              setCerts: function (id, form_data) {
                  return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              enable: function (id) {
                  return fetch('post', 'nginx/redirection-hosts/' + id + '/enable');
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              disable: function (id) {
                  return fetch('post', 'nginx/redirection-hosts/' + id + '/disable');
              }
          },
  
          Streams: {
              /**
               * @param   {Array}    [expand]
               * @param   {String}   [query]
               * @returns {Promise}
               */
              getAll: function (expand, query) {
                  return getAllObjects('nginx/streams', expand, query);
              },
  
              /**
               * @param {Object}  data
               */
              create: function (data) {
                  return fetch('post', 'nginx/streams', data);
              },
  
              /**
               * @param   {Object}   data
               * @param   {Number}   data.id
               * @returns {Promise}
               */
              update: function (data) {
                  let id = data.id;
                  delete data.id;
                  return fetch('put', 'nginx/streams/' + id, data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              delete: function (id) {
                  return fetch('delete', 'nginx/streams/' + id);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              get: function (id) {
                  return fetch('get', 'nginx/streams/' + id);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              enable: function (id) {
                  return fetch('post', 'nginx/streams/' + id + '/enable');
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              disable: function (id) {
                  return fetch('post', 'nginx/streams/' + id + '/disable');
              }
          },
  
          DeadHosts: {
              /**
               * @param   {Array}    [expand]
               * @param   {String}   [query]
               * @returns {Promise}
               */
              getAll: function (expand, query) {
                  return getAllObjects('nginx/dead-hosts', expand, query);
              },
  
              /**
               * @param {Object}  data
               */
              create: function (data) {
                  return fetch('post', 'nginx/dead-hosts', data);
              },
  
              /**
               * @param   {Object}   data
               * @param   {Number}   data.id
               * @returns {Promise}
               */
              update: function (data) {
                  let id = data.id;
                  delete data.id;
                  return fetch('put', 'nginx/dead-hosts/' + id, data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              delete: function (id) {
                  return fetch('delete', 'nginx/dead-hosts/' + id);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              get: function (id) {
                  return fetch('get', 'nginx/dead-hosts/' + id);
              },
  
              /**
               * @param  {Number}   id
               * @param  {FormData} form_data
               * @params {Promise}
               */
              setCerts: function (id, form_data) {
                  return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              enable: function (id) {
                  return fetch('post', 'nginx/dead-hosts/' + id + '/enable');
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              disable: function (id) {
                  return fetch('post', 'nginx/dead-hosts/' + id + '/disable');
              }
          },
  
          AccessLists: {
              /**
               * @param   {Array}    [expand]
               * @param   {String}   [query]
               * @returns {Promise}
               */
              getAll: function (expand, query) {
                  return getAllObjects('nginx/access-lists', expand, query);
              },
  
              /**
               * @param {Object}  data
               */
              create: function (data) {
                  return fetch('post', 'nginx/access-lists', data);
              },
  
              /**
               * @param   {Object}   data
               * @param   {Number}   data.id
               * @returns {Promise}
               */
              update: function (data) {
                  let id = data.id;
                  delete data.id;
                  return fetch('put', 'nginx/access-lists/' + id, data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              delete: function (id) {
                  return fetch('delete', 'nginx/access-lists/' + id);
              }
          },
  
          Certificates: {
              /**
               * @param   {Array}    [expand]
               * @param   {String}   [query]
               * @returns {Promise}
               */
              getAll: function (expand, query) {
                  return getAllObjects('nginx/certificates', expand, query);
              },
  
              /**
               * @param {Object}  data
               */
              create: function (data) {
                  return fetch('post', 'nginx/certificates', data);
              },
  
              /**
               * @param   {Object}   data
               * @param   {Number}   data.id
               * @returns {Promise}
               */
              update: function (data) {
                  let id = data.id;
                  delete data.id;
                  return fetch('put', 'nginx/certificates/' + id, data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              delete: function (id) {
                  return fetch('delete', 'nginx/certificates/' + id);
              },
  
              /**
               * @param  {Number}  id
               * @param  {FormData} form_data
               * @params {Promise}
               */
              upload: function (id, form_data) {
                  return FileUpload('nginx/certificates/' + id + '/upload', form_data);
              },
  
              /**
               * @param  {FormData} form_data
               * @params {Promise}
               */
              validate: function (form_data) {
                  return FileUpload('nginx/certificates/validate', form_data);
              },
  
              /**
               * @param   {Number}  id
               * @returns {Promise}
               */
              renew: function (id) {
                  return fetch('post', 'nginx/certificates/' + id + '/renew');
              }
          }
      },
  
      AuditLog: {
          /**
           * @param   {Array}    [expand]
           * @param   {String}   [query]
           * @returns {Promise}
           */
          getAll: function (expand, query) {
              return getAllObjects('audit-log', expand, query);
          }
      },
  
      Reports: {
  
          /**
           * @returns {Promise}
           */
          getHostStats: function () {
              return fetch('get', 'reports/hosts');
          }
      },
  
      Settings: {
  
          /**
           * @param   {String}  setting_id
           * @returns {Promise}
           */
          getById: function (setting_id) {
              return fetch('get', 'settings/' + setting_id);
          },
  
          /**
           * @returns {Promise}
           */
          getAll: function () {
              return getAllObjects('settings');
          },
  
          /**
           * @param   {Object}   data
           * @param   {Number}   data.id
           * @returns {Promise}
           */
          update: function (data) {
              let id = data.id;
              delete data.id;
              return fetch('put', 'settings/' + id, data);
          }
      }
  };
  