(function(send) {
  /**
   * Adds a pre process hook to all xhr request, and then continues with the call as usual.
   * @param {*} data
   */
  XMLHttpRequest.prototype.send = function(data) {
    var self = this;
    var alters = jDrupal.moduleInvokeAll('rest_pre_process', this, data);
    if (!alters) { send.call(this, data); }
    else { alters.then(function() { send.call(self, data); }); }
  };
})(XMLHttpRequest.prototype.send);

/**
 * Gets the X-CSRF-Token from Drupal.
 * @returns {Promise}
 */
jDrupal.token = function() {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.dg = {
      service: 'system',
      resource: 'token'
    };
    req.open('GET', jDrupal.restPath() + 'rest/session/token');
    req.onload = function() {
      if (req.status == 200) {
        var invoke = jDrupal.moduleInvokeAll('rest_post_process', req);
        if (!invoke) { resolve(req.response); }
        else { invoke.then(resolve(req.response));}
      }
      else { reject(Error(req.statusText)); }
    };
    req.onerror = function() { reject(Error("Network Error")); };
    req.send();
  });
};

/**
 * Connects to Drupal and sets the currentUser object.
 * @returns {Promise}
 */
jDrupal.connect = function() {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.dg = {
      service: 'system',
      resource: 'connect'
    };
    req.open('GET', jDrupal.restPath() + 'jdrupal/connect?_format=json');
    var connected = function() {
      jDrupal.connected = true;
      var result = JSON.parse(typeof req.responseText !== 'undefined' ? req.responseText : req.response);
      if (result.uid == 0) {
        jDrupal.setCurrentUser(jDrupal.userDefaults());
        resolve(result);
      }
      else {
        jDrupal.userLoad(jDrupal.config('uid')).then(function(account) {
          jDrupal.setCurrentUser(account);
          resolve(result);
        });
      }
    };
    req.onload = function() {
      if (req.status != 200) { reject(Error(req.statusText)); return; }
      var invoke = jDrupal.moduleInvokeAll('rest_post_process', req);
      if (!invoke) { connected(); }
      else { invoke.then(connected); }
    };
    req.onerror = function() { reject(Error("Network Error")); };
    req.send();
  });
};

/**
 * Logs into Drupal, then makes a jDrupal.connect() call to properly set the currentUser object.
 * @param {String} name
 * @param {String} pass
 * @returns {Promise}
 */
jDrupal.userLogin = function(name, pass) {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.dg = {
      service: 'user',
      resource: 'login'
    };
    req.open('POST', jDrupal.restPath() + 'user/login?_format=json');
    req.setRequestHeader('Content-type', 'application/json');
    var connected = function() { jDrupal.connect().then(resolve); };
    req.onload = function() {
      // 8.2: pass only the current_user response object
      if (JSON.parse(req.responseText).current_user) {
          jDrupal.settings.uid = JSON.parse(req.responseText).current_user.uid;
          jDrupal.settings.user_csrf_token = JSON.parse(req.responseText).csrf_token;
          jDrupal.settings.user_logout_token = JSON.parse(req.responseText).logout_token;
          jDrupal.settings.auth_hash = btoa(name + ":" + pass);
          jDrupal.userLoad(jDrupal.config('uid')).then(function(account) {
              jDrupal.setCurrentUser(account);
              resolve(account);
          });
      };
      
      if (req.status == 200) {
        var invoke = jDrupal.moduleInvokeAll('rest_post_process', req);
        // if (!invoke) { connected(); }
        // else { invoke.then(connected); }
      }
      else { reject(Error(req.statusText)); }
    };
    req.onerror = function() { reject(Error("Network Error")); };
    req.send(JSON.stringify({
      name: name,
      pass: pass
    }));
  });
};

/**
 * Logs out of Drupal, clears the currentUser object, then performs a jDrupal.connect() to properly set the currentUser
 * object.
 * @returns {Promise}
 */
// fixme: logout persmission denied
jDrupal.userLogout = function() {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.dg = {
      service: 'user',
      resource: 'logout'
    };
    req.open('GET', jDrupal.restPath() + 'user/logout?_format=hal+json&token='+jDrupal.settings.user_logout_token);
    // req.open('GET', jDrupal.restPath() + 'user/logout');
    req.setRequestHeader('Content-type', 'application/json');
    // req.setRequestHeader('Content-type', 'hal+json');
    // req.setRequestHeader('Content-type', 'text/html');
    // req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    // req.setRequestHeader('X-CSRF-Token', jDrupal.settings.user_logout_token);
    // req.setRequestHeader('Authorization', 'Basic '+jDrupal.settings.auth_hash);
    var connected = function() {
      jDrupal.setCurrentUser(jDrupal.userDefaults());
      jDrupal.connect().then(resolve);
    };
    req.onload = function() {
      if (req.status == 200 || req.status == 303) {
        var invoke = jDrupal.moduleInvokeAll('rest_post_process', req);
        if (!invoke) { connected(); }
        else { invoke.then(connected); }
      }
      else { reject(Error(req.statusText)); }
    };
    req.onerror = function() { reject(Error("Network Error")); };
    req.send();
  });
};

/**
 * ENTITY PROXY FUNCTIONS
 */

/**
 * Given an entity type and id, this will attempt to load the entity.
 * @param {String} entityType
 * @param {Number} entityID
 * @returns {Promise}
 */
jDrupal.entityLoad = function(entityType, entityID) {
  var entity = new this[this.ucfirst(entityType)](entityID);
  return entity.load();
};

/**
 * Given a comment id, this will attempt to load the comment.
 * @param {Number} cid
 * @returns {Promise}
 */
jDrupal.commentLoad = function(cid) { return this.entityLoad('comment', cid); };

/**
 * Given a node id, this will attempt to load the node.
 * @param {Number} nid
 * @returns {Promise}
 */
jDrupal.nodeLoad = function(nid) { return this.entityLoad('node', nid); };

/**
 * Given a user id, this will attempt to load the account.
 * @param {Number} uid
 * @returns {Promise}
 */
jDrupal.userLoad = function(uid) { return this.entityLoad('user', uid); };

/**
 * Registers a new user.
 * @param {String} name
 * @param {String} pass
 * @param {String} mail
 * @returns {Promise}
 */
// todo: userRegister
jDrupal.userRegister = function(name, pass, mail) {
  return new Promise(function(resolve, reject) {
    jDrupal.token().then(function(token) {
        var req = new XMLHttpRequest();
        req.dg = {
            service: 'user',
            resource: 'register'
        };
        // req.open('POST', jDrupal.restPath() + 'user/register?_format=json');
        req.open('POST', jDrupal.restPath() + 'user/register?_format=hal_json');
        req.setRequestHeader('Content-type', 'application/json');
        req.setRequestHeader('Authorization', 'Basic ' + jDrupal.settings.auth_hash);
        req.setRequestHeader('X-CSRF-Token', token);

        var connected = function () {
            jDrupal.connect().then(resolve);
        };
        req.onload = function () {
            if (req.status == 200) {
                var invoke = jDrupal.moduleInvokeAll('rest_post_process', req);
                if (!invoke) {
                    connected();
                }
                else {
                    invoke.then(connected);
                }
            }
            else {
                reject(Error(req.statusText));
            }
        };
        req.onerror = function () {
            reject(Error("Network Error"));
        };
        req.send(JSON.stringify({
            name: {value: name},
            pass: {value: pass},
            mail: {value: mail}
        }));
    });
  });
};
