(function () {
  'use strict';

  console.log('[DAPLIE oauth3.js]');
  console.log(window.location);

  var iter = 0;

  function main() {

    var rpc = {};
    //var myself = location.protocol + '//' + location.host + location.pathname;
    var incoming;
    var forwarding = {};
    var anchor;
    var err;
    var browserState;
    var browserCallback;
    var action;

    function parseParams() {
      var params = {};

      function parseParamsString(str) {
        str.substr(1).split('&').filter(function (el) { return el; }).forEach(function (pair) {
          pair = pair.split('=');
          var key = decodeURIComponent(pair[0]);
          var val = decodeURIComponent(pair[1]);

          if (params[key]) {
            console.warn("overwriting key '" + key + "' '" + params[key] + "'");
          }
          params[key] = val;
        });
      }

      anchor = document.createElement('a');
      anchor.href = window.location.href;

      parseParamsString(anchor.search);
      parseParamsString(anchor.hash);

      return params;
    }

    function querystringify(params) {
      var arr = [];

      Object.keys(params).forEach(function (k) {
        arr.push(encodeURIComponent(k) + '=' + encodeURIComponent(params[k]));
      });

      return arr.join('&');
    }

    function phoneAway(/*redirectURi, params*/) {
      // TODO test for ? / #
      window.location.href = incoming.redirect_uri + '#' + querystringify(forwarding);
    }

    function lintAndSetRedirectable(browserState, params) {
      if (!params.redirect_uri) {
        window.alert('redirect_uri not defined');
        err = new Error('redirect_uri not defined');
        console.error(err.message);
        console.warn(err.stack);
        params.redirect_uri = document.referer;
        return false;
      }

      if (!browserState) {
        forwarding.error = "E_NO_BROWSER_STATE";
        forwarding.error_description = "you must specify a state parameter";
        return false;
      }

      localStorage.setItem('oauth3.states.' + browserState, JSON.stringify(params));
      return true;
    }

    function redirectCallback() {
      var redirect_uri = incoming.redirect_uri;
      forwarding.callback = browserState;
      forwarding.action = 'close';

      var url = redirect_uri + '#' + querystringify(forwarding);

      console.log('[debug] redirect_uri + params:', url);
      window.location.href = url;
      setTimeout(function () {
        if (iter >= 3) {
          console.log("dancing way too much... stopping now");
          return;
        }
        iter += 1;
        console.log("I'm dancing by myse-e-elf");
        // in case I'm redirecting to myself
        main();
      }, 0);
    }

    rpc = {};

    // Act as a provider and log the user out
    rpc.logout = function (browserState, incoming) {
      var url;
      if (!lintAndSetRedirectable(browserState, incoming)) {
        // TODO fail
      }

      localStorage.setItem('oauth3.states.' + browserState, JSON.stringify(incoming));
      url = '/#/logout/' + browserState;

      // TODO specify specific account or all?
      window.location.href = url;
      setTimeout(function () {
        // in case I'm redirecting to myself
        main();
      }, 0);
    };

    // Act as a provider and inform the consumer the logout is complete
    rpc.logout_callback = function (browserState/*, incoming*/) {
      // TODO pass redirect_uri and state through here so we can avoid localStorage
      var forwarding = {};
      var originalRequest;

      if (!browserState) {
        forwarding.error = "E_NO_BROWSER_STATE";
        forwarding.error_description = "you must specify a state parameter";
        if (incoming.redirect_uri) {
          phoneAway(incoming.redirect_uri, forwarding);
        }
        return;
      }

      originalRequest = JSON.parse(localStorage.getItem('oauth3.states.' + browserState));
      forwarding.action = 'close';
      forwarding.state = browserState;
      //phoneAway(originalRequest.redirect_uri, forwarding);
      window.location.href = originalRequest.redirect_uri + '#' + querystringify(forwarding);
    };

    rpc.directives = function (browserState, incoming) {
      if (!lintAndSetRedirectable(browserState, incoming)) {
        phoneAway();
        return;
      }

      var updatedAt = new Date(localStorage.getItem('oauth3.directives.updated_at')).valueOf();
      var fresh = (Date.now() - updatedAt) < (24 * 60 * 60 * 1000);
      var directives = localStorage.getItem('oauth3.directives');
      var redirected = false;

      function redirectIf() {
        if (redirected) {
          return;
        }

        redirected = true;
        redirectCallback();
      }

      if (directives) {
        forwarding.directives = directives;
        redirectIf();
        if (fresh) {
          return;
        }
      }

      var req = new XMLHttpRequest();
      req.open('GET', 'oauth3.json', true);
      req.addEventListener('readystatechange', function () {
        if (4 !== req.readyState) {
          return;
        }

        if (200 !== req.status) {
          forwarding.error = "E_STATUS_" + req.status;
          forwarding.error_description = "expected 200 OK json or text response for oauth3.json but got '" + req.status + "'";
          redirectIf();
          return;
        }

        try {
          directives = btoa(JSON.stringify(JSON.parse(req.responseText)));
          forwarding.directives = directives;
          forwarding.callback = browserState;
          localStorage.setItem('oauth3.directives', directives);
          localStorage.setItem('oauth3.directives.updated_at', new Date().toISOString());
        } catch(e) {
          forwarding.error = "E_PARSE_JSON";
          forwarding.error_description = e.message;
          console.error(forwarding.error);
          console.error(forwarding.error_description);
          console.error(req.responseText);
        }

        redirectIf();
      });
      req.send();
    };

    // the provider is contacting me
    rpc.close = function (browserState, incoming) {
      incoming.callback = browserState;
      catchAll();
    };
    // the provider is contacting me
    rpc.redirect = function (/*browserState, incoming*/) {
      catchAll();
    };

    function catchAll() {
      function phoneHome() {
        if (browserCallback === 'completeLogin') {
          // Deprecated
          (window.opener||window.parent).completeLogin(null, null, incoming);
        } else {
          console.log('I would be closed by my parent now');
          (window.opener||window.parent)['__oauth3_' + browserCallback](incoming);
        }
      }

      if (!(incoming.browser_state || incoming.state)) {
        window.alert("callback URLs should include 'browser_state' (authorization code)"
          + " or 'state' (implicit grant))");
      }

      setTimeout(function () {
        // opener is for popup window, new tab
        // parent is for iframe
        phoneHome();
      }, 10);

      // iOS Webview (namely Chrome) workaround
      setTimeout(function () {
        console.log('I would close now');
        window.open('', '_self', '');
        window.close();
      }, 50);

      setTimeout(function () {
        var i;
        var len = localStorage.length;
        var key;
        var json;
        var fresh;

        for (i = 0; i < len; i += 1) {
          key = localStorage.key(i);
          // TODO check updatedAt
          if (/^oauth3\./.test(key)) {
            try {
              json = localStorage.getItem(key);
              if (json) {
                json = JSON.parse(json);
              }
            } catch (e) {
              // ignore
              json = null;
            }

            fresh = json && (Date.now() - json.updatedAt < (5 * 60 * 1000));

            if (!fresh) {
              localStorage.removeItem(key);
            }
          }
        }
        forwarding.updatedAt = Date.now();
        localStorage.setItem('oauth3.' + (forwarding.browser_state || forwarding.state), JSON.stringify(forwarding));
      }, 0);

    }

    function parseAction(params) {
      if (params.action) {
        return params.action;
      }

      if (params.close) {
        return 'close';
      }
      if (params.logout_callback) {
        return 'logout_callback';
      }
      if (params.logout) {
        return 'logout';
      }
      if (params.callback) {
        return 'close';
      }
      if (params.directives) {
        return 'directives';
      }

      return 'redirect';
    }

    incoming = parseParams();
    browserState = incoming.browser_state || incoming.state;
    action = parseAction(incoming);
    forwarding.url = window.location.href;
    forwarding.browser_state = browserState;
    forwarding.state = browserState;

    if (!incoming.provider_uri) {
      browserCallback = incoming.callback || browserState;
    } else {
      // deprecated
      browserCallback = 'completeLogin';
    }

    console.log('[debug]', action, incoming);

    if (rpc[action]) {
      rpc[action](browserState, incoming);
    } else {
      window.alert('unsupported action');
    }
  }

  main();
}());
