(function (exports) {
  'use strict';

  var DaplieApi;
  var Oauth3 = (exports.OAUTH3 || require('./oauth3'));

  function realGet(conf, account, id, url) {
    id = id || Math.random().toString();

    if (conf.promisesMap[id]) {
      return conf.promisesMap[id];
    }

    conf.promisesMap[id] = Oauth3.request({
      url: url
    , method: 'GET'
    , headers: { 'Authorization': 'Bearer ' + account.token }
    }).then(function (resp) {
      delete conf.promisesMap[id];

      if (!resp.data) {
        // This seems to happen on abort...
        return Oauth3.PromiseA.reject("no data returned, the request may have been aborted");
        //window.alert("[SANITY FAIL] '" + url + "' returned nothing (not even an error)");
        //return;
      }

      if (resp.data.error) {
        console.error('[ERROR]', url);
        console.error(resp.data);
        window.alert("[DEVELOPER ERROR] '" + url + "' returned an error (is the url correct? did you check login first?)");
        return Oauth3.PromiseA.reject("[DEVELOPER ERROR] '" + url + "' returned an error (is the url correct? did you check login first?)");
      }


      return resp.data;
    }, function (err) {
      delete conf.promisesMap[id];

      return Oauth3.PromiseA.reject(err);
    });

    return conf.promisesMap[id];
  }

  function promiseApiCall(conf, account, id, url, opts) {
    opts = opts || {};

    function fetch() {
      return new Oauth3.PromiseA(function (resolve, reject) {
        var kotoken = setTimeout(function () {
          if (opts.tried) {
            reject(new Error("timed out (twice) when attempting to get data"));
            return;
          }

          opts.tried = true;
          return promiseApiCall(account, id, url, opts).then(resolve, reject);
        }, opts.tried && 16000 || 8000);
        //opts.tried && 16000 || 8000

        realGet(conf, account, id, url).then(function (data) {
          clearTimeout(kotoken);
          resolve(data);
        }, function (err) {
          clearTimeout(kotoken);
          reject(err);
        });
      });
    }

    if (!id) {
      return fetch();
    }

    return conf.cache.read(id, fetch, opts).then(function (data) {
      // TODO, just data.value (after bugfix)
      return data.value && data.value.value || data.value;
    });
  }

  // TODO wrap with promises so that if a call is made before a prior call finishes,
  // it's just one call
  DaplieApi = {
    create: function create(conf) {
      // conf = { config, cache, session }
      conf.promisesMap = {};
      var myInstance = {};

      myInstance.accountsWithProfiles = function () {
        var args = Array.prototype.slice.call(arguments);
        args.unshift(conf);
        return DaplieApi.accountsWithProfiles.apply(null, args);
      };
      myInstance.guessGender = DaplieApi.guessGender;

      Object.keys(DaplieApi.api).forEach(function (key) {
        DaplieApi[key] = DaplieApi.api[key];
      });

      Object.keys(DaplieApi.api).forEach(function (key) {
        myInstance[key] = function () {
          var args = Array.prototype.slice.call(arguments);
          args.unshift(conf);
          return DaplieApi.api[key].apply(null, args);
        };
      });

      return myInstance;
    }
  , accountsWithProfiles: function accountsWithProfiles(conf, accounts) {
      // TODO conf.session.get()
      var session = conf.session._conf.session;
      var promises = [];
      accounts = accounts || [];

      session.accounts.forEach(function (account) {
        account = conf.session.cloneAccount(account);

        promises.push(DaplieApi.api.profile(conf, account).then(function (profile) {
          // TODO get a slim profile?
          account.profile = profile;
          accounts.push(account);
        }));
      });

      return Oauth3.PromiseA.all(promises).then(function () {
        // get the most recently added account as the first in the list
        // (they should already be sorted this way)
        accounts.sort(function (a, b) {
          return new Date(b.addedAt).valueOf() - new Date(a.addedAt).valueOf();
        });

        return accounts;
      });
    }
  };
  DaplieApi.api = {
    create: function (conf, account) {
      var accountInstance = {};

      account = account || conf.session.selectAccount();

      Object.keys(DaplieApi.api).forEach(function (key) {
        accountInstance[key] = DaplieApi.api[key];
      });

      Object.keys(DaplieApi.api).forEach(function (key) {
        accountInstance[key] = function () {
          var args = Array.prototype.slice.call(arguments);
          args.unshift(account);
          args.unshift(conf);
          return DaplieApi.api[key].apply(null, args);
        };
      });

      return accountInstance;
    }
  , raw: function (conf, account, rawUrl, params, opts) {
      params = params || {};

      if (!rawUrl) {
        throw new Error("no rawUrl provided");
      }

      Object.keys(params).forEach(function (key) {
        var val = params[key];
        rawUrl = rawUrl.replace(':' + key, encodeURIComponent(val));
      });

      //rawUrl = rawUrl.replace(':account_id' + key, encodeURIComponent(val));

      var url = conf.config.apiBaseUri + conf.config.apiPrefix
        + '/' + conf.session.getId(account) + rawUrl;
      var id = url;

      return promiseApiCall(
        conf
      , account
      , id
      , url
      , opts
      );
    }
  //, me: function (conf, account, opts)
  , profile: function (conf, account, opts) {
      // NOTE: account may also be a session object with an accountId and token
      var id = conf.session.getId(account) + '.me';
      var url = conf.config.apiBaseUri + conf.config.apiPrefix
        + '/accounts/' + conf.session.getId(account) + '/me';

      return promiseApiCall(
        conf
      , account
      , id
      , url
      , opts
      );
    }
  };

  exports.DaplieApi = DaplieApi.DaplieApi = DaplieApi;
  if ('undefined' !== typeof module) {
    module.exports = DaplieApi;
  }
}('undefined' !== typeof exports ? exports : window));
