export class Ajax {
  constructor() {
    this._cookieName = null;
    this._csrfToken = null;
    this._locks = {};
  }

  init(cookieName) {
    this._cookieName = cookieName;
  }

  getCsrfToken() {
    if (document.cookie.indexOf(this._cookieName) !== -1) {
      let cookieRegex = new RegExp(this._cookieName + '\=([^;]*)');
      let cookie = document.cookie.match(cookieRegex)[0];
      return cookie ? cookie.split('=')[1] : null;
    } else {
      return null;
    }
  }

  request(method, url, data) {
    let self = this;
    return new Promise(function(resolve, reject) {
      let xhr = {
        url: url,
        method: method,
        headers: {
          'X-CSRFToken': self.getCsrfToken()
        },

        data: (data ? JSON.stringify(data) : null),
        contentType: "application/json; charset=utf-8",
        dataType: 'json',

        success: function(data) {
          resolve(data);
        },

        error: function(jqXHR) {
          let rejection = jqXHR.responseJSON || {};

          rejection.status = jqXHR.status;

          if (rejection.status === 0) {
            rejection.detail = gettext("Lost connection with application.");
          }

          if (rejection.status === 404) {
            if (!rejection.detail || rejection.detail === 'NOT FOUND') {
              rejection.detail = gettext("Action link is invalid.");
            }
          }

          if (rejection.status === 500 && !rejection.detail) {
            rejection.detail = gettext("Unknown error has occured.");
          }

          rejection.statusText = jqXHR.statusText;

          reject(rejection);
        }
      };

      $.ajax(xhr);
    });
  }

  get(url, params, lock) {
    if (params) {
      url += '?' + $.param(params);
    }

    if (lock) {
      let self = this;

      // update url in existing lock?
      if (this._locks[lock]) {
        this._locks[lock].url = url;
      }

      // immediately dereference promise handlers without doing anything
      // we are already waiting for existing response to resolve
      if (this._locks[lock] && this._locks[lock].waiter) {
        return {
          then: function() {
            return;
          }
        };

      // return promise that will begin when original one resolves
      } else if (this._locks[lock] && this._locks[lock].wait) {
        this._locks[lock].waiter = true;

        return new Promise(function(resolve, reject) {
          let wait = function(url) {
            // keep waiting on promise
            if (self._locks[lock].wait) {
              window.setTimeout(function() {
                wait(url);
              }, 300);

            // poll for new url
            } else if (self._locks[lock].url !== url) {
              wait(self._locks[lock].url);

            // ajax backend for response
            } else {
              self._locks[lock].waiter = false;
              self.request('GET', self._locks[lock].url).then(function(data) {
                if (self._locks[lock].url === url) {
                  resolve(data);
                } else {
                  self._locks[lock].waiter = true;
                  wait(self._locks[lock].url);
                }
              }, function(rejection) {
                if (self._locks[lock].url === url) {
                  reject(rejection);
                } else {
                  self._locks[lock].waiter = true;
                  wait(self._locks[lock].url);
                }
              });
            }
          };

          window.setTimeout(function() {
            wait(url);
          }, 300);
        });

      // setup new lock without waiter
      } else {
        this._locks[lock] = {
          url,
          wait: true,
          waiter: false
        };

        return new Promise(function(resolve, reject) {
          self.request('GET', url).then(function(data) {
            self._locks[lock].wait = false;
            if (self._locks[lock].url === url) {
              resolve(data);
            }
          }, function(rejection) {
            self._locks[lock].wait = false;
            if (self._locks[lock].url === url) {
              reject(rejection);
            }
          });
        });
      }
    } else {
      return this.request('GET', url);
    }
  }

  post(url, data) {
    return this.request('POST', url, data);
  }

  patch(url, data) {
    return this.request('PATCH', url, data);
  }

  put(url, data) {
    return this.request('PUT', url, data);
  }

  delete(url) {
    return this.request('DELETE', url);
  }

  upload(url, data, progress) {
    let self = this;
    return new Promise(function(resolve, reject) {
      let xhr = {
        url: url,
        method: 'POST',
        headers: {
          'X-CSRFToken': self.getCsrfToken()
        },

        data: data,
        contentType: false,
        processData: false,

        xhr: function() {
          let xhr = new window.XMLHttpRequest();
          xhr.upload.addEventListener("progress", function(evt) {
            if (evt.lengthComputable) {
              progress(Math.round(evt.loaded / evt.total * 100));
            }
          }, false);
          return xhr;
        },

        success: function(response) {
          resolve(response);
        },

        error: function(jqXHR) {
          let rejection = jqXHR.responseJSON || {};

          rejection.status = jqXHR.status;

          if (rejection.status === 0) {
            rejection.detail = gettext("Lost connection with application.");
          }

          if (rejection.status === 413 && !rejection.detail) {
            rejection.detail = gettext("Upload was rejected by server as too large.");
          }

          if (rejection.status === 404) {
            if (!rejection.detail || rejection.detail === 'NOT FOUND') {
              rejection.detail = gettext("Action link is invalid.");
            }
          }

          if (rejection.status === 500 && !rejection.detail) {
            rejection.detail = gettext("Unknown error has occured.");
          }

          rejection.statusText = jqXHR.statusText;

          reject(rejection);
        }
      };

      $.ajax(xhr);
    });
  }
}

export default new Ajax();
