(function () {
  'use strict';
  window.ngOauth3App = angular.module('oauth3Playground', [ 'oauth3.org' ])
  //window.ngOauth3App = angular.module('oauth3Playground', [ 'ui.router' ])
/*
  ngOauth3App.config(function($stateProvider) {
    var helloState = {
      name: 'hello',
      url: '/hello',
      template: '
hello world!
'
    }
    var aboutState = {
      name: 'about',
      url: '/about',
      template: 'Its the UI-Router hello world app!
'
    }
    $stateProvider.state(helloState);
    $stateProvider.state(aboutState);
  });
*/
  .controller('PlaygroundCtrl', [ '$timeout', 'azp@oauth3.org', function ($timeout, OAUTH3) {
    // NOTE: This OAUTH3 is the same as window.OAUTH3, but with angular's promise injected
    // TODO: how to load more than one version of oauth3 on the page (i.e. a vanilla version without angular entaglement)
    var vm = this;
    vm.framework = 'none';
    vm.clientUri = OAUTH3.clientUri({ host: window.location.host });
    vm.conf = { debug: undefined, client_id: vm.clientUri, client_uri: vm.clientUri, provider_uri: vm.clientUri };
    vm.providerUri = vm.conf.client_uri;
    // map of things being debounced presently
    vm.debouncing = {};
    vm.defaults = { provider: vm.conf.provider_uri, directives: null };
    vm.defaults.scopes = [
      { name: 'profile@oauth3.org', desc: "Basic profile information", checked: true }
    , { name: 'authn@oauth3.org', desc: "Basic secure authentication", checked: true }
      //{ name: 'authn@oauth3.org', desc: "Basic secure authentication" }
    , { name: 'photos@daplie.com', desc: "Access to photos" }
    , { name: 'profile@oauth3.org', desc: "Access to basic profile info such as username, display_name, etc" }
    , { name: 'dns', desc: "DNS records (A/AAAA, TXT, SRV, MX, etc)" }
    , { name: '*', desc: "FULL ACCOUNT ACCESS" }
    //, 'auth@oauth3.org': "Basic secure authentication"
    //, 'profile.email@oauth3.org': "Email address"
    //, 'profile.phone@oauth3.org': "Phone number"
    ];
    vm.form = {};
    vm.form.id = 'coolaj86@gmail.com';
    vm.form.subject = '';
    vm.form.userProvider = '';
    vm.form.provider = 'sso.hellabit.com';
    vm.form.scopes = '';
    vm.locks = {};
    vm.validated = {};
    vm.responses = {};
    //
    // Convenience for our app
    //
    vm.fn = {};
    vm.fn.updateUrls = function () {
      Object.keys(vm.api.urls).forEach(function (key) {
        var fn = vm.api.urls[key];
        fn();
      });
    };
    vm.fn._debounce = {};
    vm.fn.debounceUi = function () {
      if (vm.debouncing.user || vm.debouncing.provider) {
        vm.locks['login'] = true;
      } else {
        vm.locks['login'] = false;
      }
    };
    vm.fn.debounce = function (name, time) {
      vm.debouncing[name] = true;
      vm.fn.debounceUi();
      $timeout.cancel(vm.fn._debounce[name]);
      vm.fn._debounce[name] = $timeout(function () {
        vm.debouncing[name] = false;
        vm.fn.debounceUi();
        // do nothing, just use promise
        return;
      }, time || 250);
      return vm.fn._debounce[name];
    }
    vm.fn.changeUser = function () {
      var parts = vm.form.id.split('@');
      var user;
      var provider;
      if (/@/.test(vm.form.id)) {
        // The username may have a single @, the provider may not
        // user@thing.com@whatever.com -> user@thing.com, whatever.com
        provider = parts.pop();
        user = parts.join('');
      } else {
        //vm.form.hasUser = false;
        user = '';
        provider = parts.join('');
      }
      vm.form.subject = vm.form.id;
      return vm.fn.debounce('provider', 250).then(function () {
        var parts = vm.form.provider.split('.');
        if (!vm.form.providerIndependent) {
          vm.form.provider = provider;
        }
        vm.form.userProvider = provider;
        // Careful: don't use state within a debounce function
        // uses vm.form.provider for lookup
        if (parts.length >= 2 && parts[parts.length - 1].length >= 2 && parts.every(function (p) {return p.length})) {
          return vm.api.discover().then(function () {
            console.log('[changeUser] vm.directives:');
            console.log(vm.directives);
            console.log(provider);
            console.log(OAUTH3.uri.normalize(vm.directives.issuer));
            if (vm.directives && provider === OAUTH3.uri.normalize(vm.directives.issuer)) {
              vm.form.subject = user;
            } else {
              vm.form.subject = vm.form.id;
            }
          });
        }
      });
    };
    vm.fn.changeProvider = function () {
      vm.form.providerIndependent = true;
      var parts = vm.form.provider.split('.');
      vm.fn.debounce('provider', 250).then(function () {
        // Careful: don't use state within a debounce function
        if (parts.length >= 2 && parts[parts.length - 1].length >= 2 && parts.every(function (p) {return p.length})) {
          return vm.api.discover();
        }
      });
    };
    vm.fn.toggleAdvanced = function () {
      vm.advanced = !vm.advanced;
      vm.form.provider = vm.form.userProvider;
      if (!vm.advanced) {
        vm.form.providerIndependent = false;
        vm.fn.changeUser();
      }
    };
    vm.fn.updateDebug = function () {
      if (!vm.conf.debug) {
        vm.conf.debug = undefined;
      }
    };
    vm.fn.updateScopes = function () {
      var scopes = {};
      (vm.scopes && vm.scopes.split(',') || []).forEach(function (name) {
        scopes[name] = true;
      });
      vm.defaults.scopes.forEach(function (scope) {
        if (scope.checked) {
          scopes[scope.name] = true;
        } else {
          scopes[scope.name] = false;
        }
      });
      vm.form.scopes = Object.keys(scopes).filter(function (key) {
        return scopes[key];
      }).map(function (key) {
        return key;
      }).join(',');
      vm.fn.updateUrls(); // vm.api.urls.implicitGrant();
    };
    vm.fn.lock = function () {
      vm._working = true;
    };
    vm.fn.unlock = function () {
      vm._working = false;
    };
    vm.fn.clearError = function () {
      vm.error = null;
    };
    vm.fn.clearDirectives = function () {
      vm.directives = null;
    };
    // A place for all the generated urls
    vm.urls = {};
    //
    // Wrap around the OAUTH3 APIs
    //
    vm.api = {};
    vm.api.urls = {};
    vm.api.authn = {};
    vm.api.jwt = {};
    vm.api.urls.credentialMeta = function () {
      if (!vm.directives ||!vm.directives.credential_meta || !vm.form.id) { return; }
      vm.urls.credentialMeta = OAUTH3.urls.credentialMeta(vm.directives, { email: vm.form.id });
    };
    vm.api.urls.otp = function () {
      if (!vm.directives || !vm.form.id) { return; }
      vm.urls.otp = OAUTH3.urls.otp(vm.directives, { email: vm.form.id });
    };
    vm.api.authn.otp = function () {
      vm.fn.updateUrls(); // vm.api.urls.otp();
      OAUTH3.authn.otp(vm.directives, { email: vm.form.id }).then(function (resp) {
        vm.responses.otp = resp;
        vm.form.otpUuid = resp.data.code_id;
        console.log('vm.responses.otp: (' + typeof resp + ')');
        console.log(vm.responses.otp);
        console.log('vm.form.otpUuid:');
        console.log(vm.form.otpUuid);
        vm.fn.updateUrls(); // vm.api.urls.resourceOwnerPassword();
      });
    };
    vm.api.authn.credentialMeta = function () {
      vm.fn.updateUrls(); // vm.api.urls.credentialMeta();
      OAUTH3.authn.loginMeta(vm.directives, { email: vm.form.id }).then(function () {
        vm.fn.updateUrls(); // vm.api.urls.credentialMeta();
      });
    };
    vm.api.authn._ropOpts = function () {
      //var opts = { email: vm.form.id, uuid: vm.form.otpUuid, code: vm.form.otpCode };
      return vm.api.authn._ropOpts_ = {
        client_id: vm.conf.client_uid || undefined
      , client_uri: vm.conf.client_uri || undefined
      , grant_type: 'password'
      , username: vm.form.id || undefined
      , password: vm.form.otpCode || undefined
      , totp: vm.form.totpToken || undefined
      , otp: vm.form.otpCode || "{{otp-code}}"
      , password_type: vm.form.otpCode && 'otp' || undefined
      , otp_code: vm.form.otpCode || undefined
      , otp_id: vm.form.otpUuid || undefined
      , otp_uuid: vm.form.otpUuid || undefined
      , user_agent: navigator.userAgent || undefined // "AJ's Macbook" for a specific device?
      , jwk: vm.form.rememberDevice && opts.jwk || undefined
      //, "public_key": opts.rememberDevice && opts.publicKey || undefined
      //, "public_key_type":  opts.rememberDevice && opts.publicKeyType || undefined // RSA/ECDSA
      //, "jwt": opts.jwt // TODO sign a proof with a previously loaded public_key
      , debug: vm.form.debug || undefined
      };
    };
    vm.api.urls.resourceOwnerPassword = function () {
      if (!vm.directives || !vm.form.otpUuid) { return; }
      vm.urls.resourceOwnerPassword = OAUTH3.urls.resourceOwnerPassword(vm.directives, vm.api.authn._ropOpts());
    };
    vm.api.authn.resourceOwnerPassword = function () {
      vm.fn.updateUrls(); // vm.api.urls.resourceOwnerPassword();
      OAUTH3.authn.resourceOwnerPassword(vm.directives, vm.api.authn._ropOpts()).then(function (resp) {
        vm.responses.resourceOwnerPassword = { status: 0, data: resp };
        vm.form.accessToken = vm.accessToken = resp.access_token;
        vm.form.refreshToken = vm.refreshToken = resp.refresh_token;
        vm.ropSession = resp;
        vm.ropToken = resp.token;
        vm.fn.updateUrls(); // vm.api.urls.resourceOwnerPassword(); also grants
      });
    };
    vm.api.jwt.decode = function () {
      vm.ropToken = OAUTH3.jwt.decode(vm.form.accessToken || vm.accessToken);
    };
    vm.api.jwt.decodeRefresh = function () {
      vm.ropToken = OAUTH3.jwt.decode(vm.form.refreshToken || vm.refreshToken);
    };
    vm.api.providerUri = function () {
      console.log('[DEBUG] providerUri:', vm.providerUri);
      try {
        vm.providerUri = OAUTH3.uri.normalize(vm.providerUri);
        vm.conf.provider_uri = vm.providerUri;
      } catch(e) {
        vm.error = e;
      }
    };
    vm.api.clientUri = function () {
      console.log('[DEBUG] clientUri:', vm.clientUri);
      try {
        vm.clientUri = OAUTH3.clientUri({ host: vm.clientUri });
        if (vm.clientUri) {
      console.log('[DEBUG] clientUri:', vm.clientUri);
          vm.conf.client_uri = vm.clientUri;
          vm.conf.client_id = vm.clientUri;
        }
      } catch(e) {
        vm.error = e;
      }
    };
    vm.api._discoverCount = 0;
    vm.api.urls.implicitGrant = function (provider) {
      if (!vm.directives) {
        console.log('[DEBUG] skipping implicit grant due to missing directives');
        return;
      }
      var opts = {
        client_uri: vm.conf.client_uri
      , subject: vm.form.subject || undefined
      , debug: vm.conf.debug || undefined
      , scope: vm.form.scopes || undefined
      };
      var implicitGrantObj = OAUTH3.urls.implicitGrant(vm.directives, opts);
      vm.urls.implicitGrant = vm.implicitGrantUrl = (OAUTH3.url.normalize(provider || vm.form.provider) + '/' + implicitGrantObj.url).replace(implicitGrantObj.state, '{{random}}');
    }
    vm.api.discover = function () {
      vm.directives = null;
      vm.validated.provider = '';
      vm.api._discoverCount += 1;
      var latest = vm.api._discoverCount;
      var provider = vm.form.provider; // shouldn't be mutable during this time but...
      vm.fn.lock();
      vm.discoveryObj = OAUTH3.urls.discover(provider, vm.conf);
      vm.urls.directives = vm.directivesUrl = OAUTH3.url.normalize(provider) + '/' + vm.discoveryObj.query._pathname;
      vm.urls.discovery = vm.discoveryUrl = vm.discoveryObj.method + ' ' + vm.discoveryObj.url;
      console.log('about to discover');
      return OAUTH3.discover(provider, vm.conf).then(function (dir) {
        if (latest !== vm.api._discoverCount) {
          console.log('[DEBUG] ignoring stale discover response for', provider);
          return;
        }
        console.log('[DEBUG] directives:');
        console.log(dir);
        vm.validated.provider = provider;
        vm.directives = dir;
        vm.api.urls.implicitGrant(provider);
        //JSON.stringify(dir, null, 2);
      }, function (err) {
        vm.form.provider = vm.defaults.provider;
        vm.validated.provider = vm.defaults.provider;
        vm.directives = vm.defaults.directives;
        if (latest !== vm.api._discoverCount) {
          console.warn('[DEBUG] ignoring stale discover error for', provider);
          console.warn(err);
          return;
        }
        console.log('error on discover');
        vm.error = err;
      }).then(function () {
        vm.fn.unlock();
      });
    };
    vm.api.urls.scope = function () {
      // TODO do something like this in the oauth3 library
      vm.urls.scope = 'https://:host/.well-known/oauth3/scopes/:scope.json\nhttps://example.com/.well-known/oauth3/scopes/example@oauth3.org.json\n\n'
        + 'GET https://example.com/.well-known/oauth3/#/?action=scope&state={{random}}&redirect_uri=https%3A%2F%2Fsso.hellabit.com%2F.well-known%2Foauth3%2Fcallback.html%23%2F&response_type=rpc&_method=GET&_pathname=.well-known%2Foauth3%2Fscopes%2Fexample@oauth3.org.json';
    };
    vm.api.discoverScopes = function () {
      var scopes = vm.form.scopes && vm.form.scopes.split(',') || [];
      vm.scopesObj = [];
      function nextScope() {
        var scopename = scopes.shift();
        if (!scopename) {
          return;
        }
        // something like https://example.com/.well-known/oauth3.org/scopes/:scopename.json
        var scopeUrlObj = OAUTH3.urls.discoverScope(vm.form.provider, {
          client_uri: vm.conf.client_uri
        , scope: scopename
        , debug: vm.conf.debug || undefined
        });
        vm.urls.scope = vm.scopeUrl = OAUTH3.url.normalize(provider) + '/' + scopeUrlObj.query._pathname;
        // something like the discovery url that loads in an iframe
        var discoverScopeObj = OAUTH3.urls.discoverScope(vm.form.provider, {
          client_uri: vm.conf.client_uri
        , scope: scopename
        , debug: vm.conf.debug || undefined
        });
        vm.urls.discoverScope = vm.discoverScopeUrl = OAUTH3.url.normalize(provider) + '/' + discoverScopeObj.url;
        // Go and fetch!
        return OAUTH3.discoverScopes(vm.form.provider, {
          client_uri: vm.conf.client_uri
        , scope: scopename
        , debug: vm.conf.debug || undefined
        }).then(function (scope) {
          var allScopes = {};
          vm.scopesObj.push(scope);
          vm.defaults.scopes.push(scope);
          vm.defaults.scopes = vm.defaults.scopes.filter(function (scope) {
            if (allScopes[scope.name]) {
              return false;
            }
            allScopes[scope.name] = true;
            return true;
          });
        }, function (err) {
          console.error("Error in discover scope:");
          console.error(err);
          vm.scopesObj.push({ name: scopename, desc: "Error, not found" });
        });
      }
      return nextScope();
    };
    vm.api.implicitGrant = function () {
      var provider = vm.validated.provider;
      var opts = {
        client_uri: vm.conf.client_uri
      , subject: vm.form.subject || undefined
      , debug: vm.conf.debug || undefined
      , scope: vm.form.scopes || undefined
      };
      console.log('[DEBUG] vm.directives');
      console.log(vm.directives);
      vm.implicitGrantObj = OAUTH3.urls.implicitGrant(vm.directives, opts);
      console.log('[DEBUG] vm.implicitGrantObj');
      console.log(vm.implicitGrantObj);
      vm.urls.implicitGrant = vm.implicitGrantUrl = (OAUTH3.url.normalize(provider) + '/' + vm.implicitGrantObj.url);
      return OAUTH3.implicitGrant(vm.directives, opts).then(function (session) {
        vm.session = session;
      });
    };
    vm.urls.profile = {};
    vm.api.urls.profile_get = function () {
      if (!vm.directives || !vm.accessToken) { return; }
      vm.urls.profile_get = OAUTH3.urls.accounts.get(vm.directives, vm.ropSession);
    };
    vm.api.profile = {};
    vm.api.profile_get = function () {
      console.log('you tickled me!', vm.ropSession);
      vm.api.urls.profile_get();
      return OAUTH3.requests.accounts.get(vm.directives, vm.ropSession).then(function (resp) {
        console.log('you tickled me twice!');
        if (!resp.data) {
          resp = { status: 0, data: resp };
        }
        vm.responses.profile_get = resp;
      }, function (err) {
        console.error('Could not get profile:');
        console.error(err);
        vm.error = err;
      });
    };
    vm.api.profile_set = function () {
      console.log('you tickled me!', vm.ropSession);
      vm.api.urls.profile_set();
      return OAUTH3.requests.accounts.set(vm.directives, vm.ropSession).then(function (resp) {
        console.log('you tickled me twice!');
        if (!resp.data) {
          resp = { status: 0, data: resp };
        }
        vm.responses.profile_set = resp;
      }, function (err) {
        console.error('Could not set profile:');
        console.error(err);
        vm.error = err;
      });
    };
    vm.api.authz = {};
    vm.api.authz._grantsOpts = function () {
      return vm.api.authz._grantsOpts_ = {
        method: 'GET'
      , client_id: vm.conf.client_id
      , client_uri: vm.conf.client_uri
      , session: vm.ropSession
      , debug: vm.conf.debug
      , all: true
      };
    };
    vm.api.urls.grants = function () {
      if (!vm.directives || !vm.ropSession || !vm.form.id) { return; }
      vm.urls.grants = OAUTH3.urls.grants(vm.directives, vm.api.authz._grantsOpts());
    };
    vm.api.authz.grants = function () {
      vm.fn.updateUrls(); // vm.api.urls.grants();
      return OAUTH3.authz.grants(vm.form.provider, vm.api.authz._grantsOpts()).then(function (resp) {
        vm.responses.grants = { status: 0, data: resp };
        vm.fn.updateUrls(); // vm.api.urls.grants();
      }, function (err) {
        console.error('[error] authz.grants:');
        console.error(err);
        vm.error = err;
      });
    };
    vm.form.provider = vm.defaults.provider;
    vm.validated.provider = vm.defaults.provider;
    vm.api.discover().then(function () {
      vm.defaults.directives = vm.directives;
    });
    vm.fn.updateScopes();
    vm.apistr = '';
    Object.keys(OAUTH3).forEach(function (key) {
      var thingy = OAUTH3[key];
      if ('_' === key[0] || -1 !== [ 'create', '_browser', '_defaultStorage', 'hooks', '_hooks', '_digest' ].indexOf(key)) {
        return;
      }
      if ('function' === typeof thingy) {
        vm.apistr += thingy.toString().split(/\n/)[0].replace('function ', 'OAUTH3.' + key).replace(/\s+{\s*/, '') + '\n';
      }
      if ('object' === typeof thingy) {
        Object.keys(thingy).forEach(function (key2) {
          var thingy2 = thingy[key2];
          if ('function' === typeof thingy2) {
            vm.apistr += thingy2.toString().split(/\n/)[0].replace('function ', 'OAUTH3.' + key + '.' + key2).replace(/\s+{\s*/, '') + '\n';
          }
          if ('object' === typeof thingy2) {
            Object.keys(thingy2).forEach(function (key3) {
              var thingy3 = thingy2[key3];
              if ('function' === typeof thingy3) {
                vm.apistr += thingy3.toString().split(/\n/)[0].replace('function ', 'OAUTH3.' + key + '.' + key2 + '.' + key3).replace(/\s+{\s*/, '') + '\n';
              }
            });
          }
        });
      }
    });
  } ] );
}());