/*jshint eqnull:true, browser:true, expr:true, sub:true */
//
// Twitch API interface
// Depends on twitch.core, twitch.storage
//
// Allows you to access the Twitch Kraken API from the client. Authentication
// is handled automatically for logged-in users.
//
//
// Usage:
//   Twitch.api.get('channel').done([successCallback]).fail([failCallback])
//
// Some identifiers in paths are routed dynamically, such
// as :login. See #_constructUrl()
//

// = require rsvp

(function (Twitch, $) {
  var api = {};
  var CLIENT_API_ID = 'jzkbprff40iqj646a697cyrvl0zt2m6';

  RSVP.EventTarget.mixin(api);

  // Avoid attempting to make xhr requests before the iframe
  // has loaded, as this results in untraceable exceptions
  var requestQueue = [];
  api.isIframeReady = false;

  var API_VERSION = 4;

  function isIE9() { // TODO: Delete when IE9 is dropped
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf('MSIE ');
    if (msie > 0) {
      return (parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10) < 10);
    }
  }

  api.init = function (config) {
    var self = this;
    var appendIframe = function (tagString) {
      self.config.iframe = $('<iframe>')
        .attr('src', self.config.receiverUrl)
        .appendTo(tagString)
        .get(0);
    };

    this.config = {
      clientID: CLIENT_API_ID,
      baseUrl: config.baseUrl,
      baseUrlSecure: config.baseUrlSecure,
      streamsBaseUrl: config.streamsBaseUrl,
      streamsBaseUrlSecure: config.streamsBaseUrlSecure,
      receiverUrl: config.receiverUrl,
      login: config.login
    };

    // Use an iframe trick to allow us to communicate to
    // cross-domain api urls by setting document.domain on
    // both the parent and iframe, then using the iframe's
    // internal XHR object
    if (navigator.userAgent.indexOf('Prerender') === -1) {
      appendIframe('head')
    } else {
      // Additionally, iframe in the head as part of the initial HTML served
      // (e.g., such as prerendered content) will terminate the head tag
      // causing issues with meta tags added by web-client, so we make sure to
      // add the iframe to the body for Prerender's crawlers.
      // We don't do this all the time because it introduces a significant delay
      // to initial API load.
      // TODO: Remove when CORS is implemented across api.twitch.tv
      $(document).ready(function () {
        appendIframe('body')
      });
    }

    // Allow us to talk to the iframe
    document.domain = 'twitch.tv';
  };

  api._ajax = function (method, path, data, options) {
    var def = new $.Deferred(),
      authenticityToken,
      ajaxOptions;

    options = $.extend({}, options);
    options.headers = options.headers || {};
    options.headers['Client-ID'] = options.headers['Client-ID'] || CLIENT_API_ID;
    options.headers['X-CSRF-Token'] = options.headers['X-CSRF-Token'] || Twitch.storage.get("csrf_token");

    // Allow us to make non-ssl requests to Kraken
    data || (data = {});
    // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
    data.on_site = '1';
    // jscs:enable requireCamelCaseOrUpperCaseIdentifiers

    // TODO: Delete this entire check when IE9 is dropped, just set `options.type = method`
    if (isIE9() && !(method === 'GET' || method === 'POST')) {
      options.type = 'POST';
      options.headers['X-Http-Method-Override'] = method;
    } else {
      options.type = method;
    }

    var version = options.version || options.v || API_VERSION;
    options.headers['Accept'] = 'application/vnd.twitchtv.v' + version + '+json';

    if (options.headers.Authorization) {
      // We can't send oauth tokens over non-SSL
      options.secure = true;
      options.allow_cookie = false;
      delete data['on_site'];
    } else {
      // sending Twitch-Api-Token triggers cookie-based auth, incompatible with Authorization header
      authenticityToken = Twitch.storage.legacy.get('api_token');
      if (authenticityToken) {
        options.headers['Twitch-Api-Token'] = authenticityToken;
      }
      options.allow_cookie = true;
    }

    // Use streams-api directly instead of routing through rails-nginx:
    // This matches /kraken/streams/.* and streams/.* (which gets expanded to /kraken/streams in _constructUrl)
    // it does not match streams/summary, streams/recommended, streams/recommended/available, streams/featured,
    // or streams/communities* since these endpoints are not handled in streams-api
    if(/^(\/kraken\/)?streams(?!\/summary\/*$|\/recommended\/*$|\/recommended\/available\/*$|\/featured\/*$|\/communities\/)/.test(path)) {
      options.use_streams_api = true;
      options.allow_cookie = false;
    }

    if (options.success) def.done(options.success);
    if (options.error) def.fail(options.error);

    ajaxOptions = $.extend({}, options, {
      url: this._constructUrl(path, options),
      dataType: "json",
      cache: true,
      global: false,
      data: data,
      xhr: this._createXHR.bind(this),
      xhrFields: {withCredentials: options.secure},

      /** the following callbacks are wrapped in the setTimeouts to run in the top window execution context */
      success: function () {
        var args = arguments;
        setTimeout(function () { def.resolve.apply(def, args); });
      },
      error: function () {
        var args = arguments;
        setTimeout(function () { def.reject.apply(def, args); });
      }
    });

    // Sometimes this actually WILL be a CORS request. Detect that and use JSONP if needed
    if (options.use_streams_api || (options.secure && window.location.protocol.slice(0,5) !== "https")) {
      if (this._canCORS()) {
        ajaxOptions.xhrFields.withCredentials = options.allow_cookie;
      } else {
        ajaxOptions.dataType = "jsonp";

        if (ajaxOptions.headers && ajaxOptions.headers['Client-ID']) {
          ajaxOptions.data.client_id = ajaxOptions.headers['Client-ID'];
          delete ajaxOptions.headers['Client-ID'];
        }

        if (ajaxOptions.headers && ajaxOptions.headers.Authorization) {
          var oauthMatch = /^OAuth:?\s+(\w+)$/.exec(ajaxOptions.headers.Authorization);
          if (oauthMatch) {
            ajaxOptions.data.oauth_token = oauthMatch[1];
            delete ajaxOptions.headers['Authorization'];
          }
        }
      }
    } else {
      // this is not a crossdomain request
      ajaxOptions.beforeSend = this._beforeSend;
    }

    def.fail(function (jqXHR, textStatus, errorThrown) {
      api.trigger('fail', {
        url: ajaxOptions.url,
        jqXHR: jqXHR,
        textStatus: textStatus,
        errorThrown: errorThrown
      });
    });

    if (api.isIframeReady) {
      $.ajax(ajaxOptions);
    } else {
      requestQueue.push(ajaxOptions);
    }

    return def.promise();
  };

  api.get = function (path, data, options) {
    return api._ajax('GET', path, data, options);
  };

  api.post = function (path, data, options) {
    return api._ajax('POST', path, data, options);
  };

  api.put = function (path, data, options) {
    return api._ajax('PUT', path, data, options);
  };

  api.del = function (path, data, options) {
    return api._ajax('DELETE', path, data, options);
  };

  api._canCORS = _.once(function() {
    return 'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest();
  });

  // Construct a url from the api method given, replacing
  // some identifiers dynamically.
  // Replacements:
  //   - :login => Current user (if any)
  api._constructUrl = function (path, options) {
    if (this.config.login) {
      path = path.replace(/:login([^\w])/, this.config.login + '$1');
    }

    if (path[0] !== '/') {
      path = '/kraken/' + path;
    }

    var urlProp = options.use_streams_api ? 'streamsBaseUrl' : 'baseUrl';
    if (options.secure) {
      urlProp += 'Secure';
    }

    var host = options.host || this.config[urlProp];
    return host + path;
  };

  api._createXHR = function () {
    // Key to the magic: grab the XHR object of the child iframe that has
    // its location on the api domain, so any XHR requests are considered
    // to be part of the same domain.
    // https://github.com/jquery/jquery/blob/7c23b77af2477417205fda9bde5208a81e57e40e/src/ajax/xhr.js#L26
    // Don't support ActiveXObject because we don't need local file support
    return new this.config.iframe.contentWindow.XMLHttpRequest();
  };

  api._beforeSend = function (jqXHR, settings) {
    // jQuery incorrectly thinks this is a crossdomain request,
    // and tries to prevent it from sending on browsers that do not
    // support CORS (IE9). Since we know better, tell jQuery.
    settings.crossDomain = false;
  };

  // Called by receiver iframe to trigger any pending requests
  api.iframeReady = function () {
    api.isIframeReady = true;

    this.trigger('ready');

    requestQueue.forEach(function (ajaxOptions) {
      $.ajax(ajaxOptions);
    });
  };

  Twitch.mixin({
    api: api
  });

})(Twitch, jQuery);
