;(function (exports) {
  'use strict';

  var core = window.OAUTH3_CORE;

  // Provider-Only
  core.urls.loginCode = function (directive, opts) {
    //
    // Example Resource Owner Password Request
    // (generally for 1st party and direct-partner mobile apps, and webapps)
    //
    // POST https://api.example.com/api/org.oauth3.provider/otp
    //    { "request_otp": true, "client_id": "<<id>>", "scope": "<<scope>>"
    //    , "username": "<<username>>" }
    //
    opts = opts || {};
    var clientId = opts.appId || opts.clientId;

    var args = directive.credential_otp;
    if (!directive.credential_otp) {
      console.log('[debug] loginCode directive:');
      console.log(directive);
    }
    var params = {
      "username": opts.id || opts.username
    , "request_otp": true // opts.requestOtp || undefined
    //, "jwt": opts.jwt // TODO sign a proof
    , debug: opts.debug || undefined
    };
    var uri = args.url;
    var body;
    if (opts.clientUri) {
      params.client_uri = opts.clientUri;
    }
    if (opts.clientAgreeTos) {
      params.client_agree_tos = opts.clientAgreeTos;
    }
    if (clientId) {
      params.client_id = clientId;
    }
    if ('GET' === args.method.toUpperCase()) {
      uri += '?' + core.querystringify(params);
    } else {
      body = params;
    }

    return {
      url: uri
    , method: args.method
    , data: body
    };
  };

  core.urls.resourceOwnerPassword = function (directive, opts) {
    //
    // Example Resource Owner Password Request
    // (generally for 1st party and direct-partner mobile apps, and webapps)
    //
    // POST https://example.com/api/org.oauth3.provider/access_token
    //    { "grant_type": "password", "client_id": "<<id>>", "scope": "<<scope>>"
    //    , "username": "<<username>>", "password": "password" }
    //
    opts = opts || {};
    var type = 'access_token';
    var grantType = 'password';

    if (!opts.password) {
      if (opts.otp) {
        // for backwards compat
        opts.password = opts.otp; // 'otp:' + opts.otpUuid + ':' + opts.otp;
      }
    }

    var scope = opts.scope || directive.authn_scope;
    var clientId = opts.appId || opts.clientId || opts.client_id;
    var clientAgreeTos = opts.clientAgreeTos || opts.client_agree_tos;
    var clientUri = opts.clientUri || opts.client_uri || opts.clientUrl || opts.client_url;
    var args = directive[type];
    var otpCode = opts.otp || opts.otpCode || opts.otp_code || opts.otpToken || opts.otp_token || undefined;
    var params = {
      "grant_type": grantType
    , "username": opts.username
    , "password": opts.password || otpCode || undefined
    , "totp": opts.totp || opts.totpToken || opts.totp_token || undefined
    , "otp": otpCode
    , "password_type": otpCode && 'otp'
    , "otp_code": otpCode
    , "otp_uuid": opts.otpUuid || opts.otp_uuid || undefined
    , "user_agent": opts.userAgent || opts.useragent || opts.user_agent || undefined // AJ's Macbook
    , "jwk": (opts.rememberDevice || opts.remember_device) && 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: opts.debug || undefined
    };
    var uri = args.url;
    var body;
    if (opts.totp) {
      params.totp = opts.totp;
    }

    if (clientId) {
      params.clientId = clientId;
    }
    if (clientUri) {
      params.clientUri = clientUri;
      params.clientAgreeTos = clientAgreeTos;
      if (!clientAgreeTos) {
        throw new Error('Developer Error: missing clientAgreeTos uri');
      }
    }

    if (scope) {
      params.scope = core.stringifyscope(scope);
    }

    if ('GET' === args.method.toUpperCase()) {
      uri += '?' + core.querystringify(params);
    } else {
      body = params;
    }

    return {
      url: uri
    , method: args.method
    , data: body
    };
  };


  core.urls.grants = function (directive, opts) {
    // directive = { issuer, authorization_decision }
    // opts = { response_type, scopes{ granted, requested, pending, accepted } }

    if (!opts) {
      throw new Error("You must supply a directive and an options object.");
    }
    if (!opts.client_id) {
      throw new Error("You must supply options.client_id.");
    }
    if (!opts.session) {
      throw new Error("You must supply options.session.");
    }
    if (!opts.referrer) {
      console.warn("You should supply options.referrer");
    }
    if (!opts.method) {
      console.warn("You must supply options.method as either 'GET', or 'POST'");
    }
    if ('POST' === opts.method) {
      if ('string' !== typeof opts.scope) {
        console.warn("You should supply options.scope as a space-delimited string of scopes");
      }
      if (-1 === ['token', 'code'].indexOf(opts.response_type)) {
        throw new Error("You must supply options.response_type as 'token' or 'code'");
      }
    }

    var url = core.urls.resolve(directive.issuer, directive.grants.url)
      .replace(/(:azp|:client_id)/g, core.normalizeUri(opts.client_id || opts.client_uri))
      .replace(/(:sub|:account_id)/g, opts.session.token.sub)
      ;
    var data = {
      client_id: opts.client_id
    , client_uri: opts.client_uri
    , referrer: opts.referrer
    , response_type: opts.response_type
    , scope: opts.scope
    , tenant_id: opts.tenant_id
    };
    var body;

    if ('GET' === opts.method) {
      url += '?' + core.querystringify(data);
    }
    else {
      body = data;
    }

    return {
      method: opts.method
    , url: url
    , data: body
    , session: opts.session
    };
  };
  core.urls.authorizationDecision = function (directive, opts) {
    var url = core.urls.resolve(directive.issuer, directive.authorization_decision.url);
    if (!opts) {
      throw new Error("You must supply a directive and an options object");
    }
    console.info(url);
    throw new Error("NOT IMPLEMENTED authorization_decision");
  };
  core.authz = core.authz || {};
  core.authz.scopes = function (session, clientParams) {
    // OAuth3.requests.grants(providerUri, {});         // return list of grants
    // OAuth3.checkGrants(providerUri, {});             //
    var clientUri = OAUTH3.core.normalizeUri(clientParams.client_uri || window.document.referrer);
    var scope = clientParams.scope || '';
    var clientObj = clientParams;

    if (!scope) {
      scope = 'oauth3_authn';
    }

    //$('.js-user-avatar').attr('src', userAvatar);

    /*
    console.log('grants options');
    console.log(loc.hash);
    console.log(loc.search);
    console.log(clientObj);
    console.log(session.token);
    console.log(window.document.referrer);
    */

    return OAUTH3.requests.grants(CONFIG.host, {
      method: 'GET'
    , client_id: clientUri
    , client_uri: clientUri
    , session: session
    }).then(function (grantResults) {
      var grants;
      var grantedScopes;
      var grantedScopesMap;
      var pendingScopes;
      var acceptedScopes;
      var scopes = scope.split(/[+, ]/g);
      var callbackUrl;

      console.log('previous grants:');
      console.log(grantResults);

      if (grantResults.data.error) {
        window.alert('grantResults: ' + grantResults.data.error_description || grantResults.data.error.message);
        return;
      }

      // it doesn't matter who the referrer is as long as the destination
      // is an authorized destination for the client in question
      // (though it may not hurt to pass the referrer's info on to the client)
      if (!OAUTH3.checkRedirect(grantResults.data.client, clientObj)) {
        callbackUrl = 'https://oauth3.org/docs/errors#E_REDIRECT_ATTACK'
          + '?redirect_uri=' + clientObj.redirect_uri
          + '&allowed_urls=' + grantResults.data.client.url
          + '&client_id=' + clientUri
          + '&referrer_uri=' + OAUTH3.core.normalizeUri(window.document.referrer)
          ;
        location.href = callbackUrl;
        return;
      }

      if ('oauth3_authn' === scope) {
        // implicit ppid grant is automatic
        console.warn('[security] fix scope checking on backend so that we can do automatic grants');
        // TODO check user preference if implicit ppid grant is allowed
        //return generateToken(session, clientObj);
      }

      grants = (grantResults.originalData||grantResults.data).grants.filter(function (grant) {
        if (clientUri === (grant.azp || grant.oauth_client_id || grant.oauthClientId)) {
          return true;
        }
      });

      grantedScopesMap = {};
      acceptedScopes = [];
      pendingScopes = scopes.filter(function (requestedScope) {
        return grants.every(function (grant) {
          if (!grant.scope) {
            grant.scope = 'oauth3_authn';
          }
          var gscopes = grant.scope.split(/[+, ]/g);
          gscopes.forEach(function (s) { grantedScopesMap[s] = true; });
          if (-1 !== gscopes.indexOf(requestedScope)) {
            // already accepted in the past
            acceptedScopes.push(requestedScope);
          }
          else {
            // true, is pending
            return true;
          }
        });
      });
      grantedScopes = Object.keys(grantedScopesMap);

      return {
        pending: pendingScopes    // not yet accepted
      , granted: grantedScopes    // all granted, ever
      , requested: scopes         // all requested, now
      , accepted: acceptedScopes  // granted (ever) and requested (now)
      };
    });
  };

  exports.OAUTH3_CORE_PROVIDER = core;

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