/* SimpleOAuth - Simply builds OAuth 1.0 headers
*
* Copyright (c) 2013, Ben Olson (github.com/bseth99/simple-oauth-js)
*
* Adapted from Ruby Gem simple_oauth:
*   https://github.com/laserlemon/simple_oauth
*
* and OAuthSimple:
*   http://unitedHeroes.net/OAuthSimple
*
* Usage is essentially the same and should match the Ruby versions output
* for server-side processing.
*
*
* This basic usage will yield a string suitable for setting on the
* Authorization header in an AJAX request.  It is not library specific
* nor does it assume which header you will use it with:
*
*   var options = {
*      consumer_key: 'R1Y3QW1L15uw8X0t5ddJbQ',
*      consumer_secret: '7xKJvmTCKm97WBQQllji9Oz8DRQHJoN1svhiY8vo'
*   };
*
*   var header = new SimpleOAuth.Header('get', 'http://example.org/resource', null, options);
*   var authorization = header.build();
*
* See github.com/bseth99/simple-oauth for more usage examples and notes
* Also check the test cases and samples for jQuery/Backbone integration cases
*
* Only support HMAC-SHA1 signing
*
* Other sources noted throughout.
*
* Dependancies:
*     underscore.js >= 1.4.3 (http://underscorejs.org)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the unitedHeroes.net nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD.
        define(['underscore'], factory);
    } else {
        // Browser globals
        root.SimpleOAuth = factory( root._ );
    }
}(this, function ( _ ) {

   var ATTRIBUTE_KEYS = ['callback', 'consumer_key', 'nonce', 'signature_method', 'timestamp', 'token', 'verifier', 'version'];
   var NONCE_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

   /* getNonce adapted from OAuthSimple
     * A simpler version of OAuth
     *
     * author:     jr conlin
     * mail:       src@anticipatr.com
     * copyright:  unitedHeroes.net
     * version:    1.2
     * url:        http://unitedHeroes.net/OAuthSimple
     *
     * Copyright (c) 2011, unitedHeroes.net
     *
    */

   function getNonce(length) {

      var length = length || 16,
          result = '',
          i=0,
          rnum,
          len = NONCE_CHARS.length;

      for ( ;i<length;i++ ) {
         rnum = Math.floor(Math.random() * len);
         result += NONCE_CHARS.substring(rnum, rnum+1);
      }

      return result;

   }

   function getTimestamp() {
      var d = new Date();

      return ''+Math.floor(d.getTime() / 1000);
   }

   // Global scope
   SimpleOAuth = {};

   var Header = function(method, url, params, oauth) {
      var oauth = oauth || {};

      this.method = method.toUpperCase();

      this.uri = URI.parseUri(url);
      this.uri.fragment = '';
      this.uri.normalize();

      this.params = params;
      this.options = _.extend(Header.default_options(), oauth);

   }

   Header.default_options = function () {
      return ({
             nonce: getNonce(),
             signature_method: 'HMAC-SHA1',
             timestamp: getTimestamp(),
             version: '1.0'
         });
   }

   /*
   *
   *  5.1.  Parameter Encoding
   *
   *  All parameter names and values are escaped using the [RFC3986] percent-encoding (%xx) mechanism.
   *  Characters not in the unreserved character set ([RFC3986] section 2.3) MUST be encoded. Characters
   *  in the unreserved character set MUST NOT be encoded. Hexadecimal characters in encodings MUST be upper
   *  case. Text names and values MUST be encoded as UTF-8 octets before percent-encoding them per [RFC3629].
   *
   *            unreserved = ALPHA, DIGIT, '-', '.', '_', '~'
   */

   Header.escape = function(value) {
      return encodeURIComponent(value)
               .replace(/\!/g, "%21")
               .replace(/\*/g, "%2A")
               .replace(/'/g, "%27")
               .replace(/\(/g, "%28")
               .replace(/\)/g, "%29");
   }

   Header.unescape = function(value) {
      return decodeURIComponent(value);
   }

   _.extend(Header.prototype, {


      /*
      *  Section 9.1.2 Construct Request URL
      *
      *  The Signature Base String includes the request absolute URL, tying the signature to a specific endpoint.
      *  The URL used in the Signature Base String MUST include the scheme, authority, and path, and MUST exclude the
      *  query and fragment as defined by [RFC3986] section 3.
      *
      *  If the absolute request URL is not available to the Service Provider (it is always available to the Consumer),
      *  it can be constructed by combining the scheme being used, the HTTP Host header, and the relative HTTP request URL.
      *  If the Host header is not available, the Service Provider SHOULD use the host name communicated to the Consumer
      *  in the documentation or other means.
      *
      *  The Service Provider SHOULD document the form of URL used in the Signature Base String to avoid ambiguity due to
      *  URL normalization. Unless specified, URL scheme and authority MUST be lowercase and include the port number; http
      *  default port 80 and https default port 443 MUST be excluded.
      *
      *  For example, the request:
      *
      *                  HTTP://Example.com:80/resource?id=123
      *
      *  Is included in the Signature Base String as:
      *
      *                  http://example.com/resource
      *
      *
      */

      url: function () {

         var uri = new _uri(_.clone(this.uri));
         uri.query = null;
         return uri.build();

      },


      /*
      *  Section 7. Accessing Protected Resources
      *
      *  After successfully receiving the Access Token and Token Secret, the Consumer is able to access the
      *  Protected Resources on behalf of the User. The request MUST be signed per Signing Requests, and
      *  contains the following parameters:
      *
      *      oauth_consumer_key:
      *          The Consumer Key.
      *      oauth_token:
      *          The Access Token.
      *      oauth_signature_method:
      *          The signature method the Consumer used to sign the request.
      *      oauth_signature:
      *          The signature as defined in Signing Requests.
      *      oauth_timestamp:
      *          As defined in Nonce and Timestamp.
      *      oauth_nonce:
      *          As defined in Nonce and Timestamp.
      *      oauth_version:
      *          OPTIONAL. If present, value MUST be 1.0. Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. Service Providers’ response to non-1.0 value is left undefined.
      *      Additional parameters:
      *          Any additional parameters, as defined by the Service Provider.
      *
      *
      */

      build: function ( output ) {
         var output = output || 'header',
             s;

         if ( output == 'header' )
            s = 'OAuth ' + this.normalized_header_attributes();
         else if ( output == 'query' )
            s = this.normalized_query_attributes();

         return s;
      },

      signed_attributes: function () {
         var attr = _.clone(this.attributes());
         attr['oauth_signature'] = this.signature();
         return attr;
      },

     // private

      normalized_header_attributes: function ( ) {

         return (
               _.map(
                  _.sortBy( _.pairs(this.signed_attributes()), function (v) { return v[0]; } ),
                     function (v) {
                           return v[0]+'="'+Header.escape(v[1])+'"';
                        }).join(', ')
            );
      },

      normalized_query_attributes: function ( ) {

         return (
               _.map(
                  _.sortBy( _.pairs(this.signed_attributes()), function (v) { return v[0]; } ),
                     function (v) {
                           return v[0]+'='+Header.escape(v[1]);
                        }).join('&')
            );
      },

      attributes: function() {
         var attr = {},
             opt = this.options;

         _.each(ATTRIBUTE_KEYS, function (k) {
               if (opt[k]) attr['oauth_'+k] = opt[k];
            });

         return attr;
      },

      /*
      *  9.  Signing Requests
      *
      *  All Token requests and Protected Resources requests MUST be signed by the Consumer and verified by the
      *  Service Provider. The purpose of signing requests is to prevent unauthorized parties from using the
      *  Consumer Key and Tokens when making Token requests or Protected Resources requests. The signature process
      *  encodes the Consumer Secret and Token Secret into a verifiable value which is included with the request.
      *
      *  OAuth does not mandate a particular signature method, as each implementation can have its own unique
      *  requirements. The protocol defines three signature methods: HMAC-SHA1, RSA-SHA1, and PLAINTEXT, but
      *  Service Providers are free to implement and document their own methods. Recommending any particular
      *  method is beyond the scope of this specification.
      *
      *  The Consumer declares a signature method in the oauth_signature_method parameter, generates a signature,
      *  and stores it in the oauth_signature parameter. The Service Provider verifies the signature as specified
      *  in each method. When verifying a Consumer signature, the Service Provider SHOULD check the request nonce
      *  to ensure it has not been used in a previous Consumer request.
      *
      *  The signature process MUST NOT change the request parameter names or values, with the exception of the
      *  oauth_signature parameter.
      */

      signature: function () {
         return this.hmac_sha1_signature();
      },

      hmac_sha1_signature: function () {
         return b64_hmac_sha1(this.secret(), this.signature_base());
      },

      /*
      *  9.2.  HMAC-SHA1
      *
      *  The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] where the
      *  Signature Base String is the text and the key is the concatenated values (each first encoded per Parameter Encoding)
      *  of the Consumer Secret and Token Secret, separated by an ‘&’ character (ASCII code 38) even if empty.
      */

      secret: function () {
         var opt = _.pick(this.options, 'consumer_secret', 'token_secret');

         opt['consumer_secret'] = opt['consumer_secret'] || '';
         opt['token_secret'] = opt['token_secret'] || '';

         return _.map(opt, function (v) { return Header.escape(v); }).join('&');
      },


      /*
      *   9.1.3.  Concatenate Request Elements
      *
      *  The following items MUST be concatenated in order into a single string. Each item is encoded and separated
      *  by an ‘&’ character (ASCII code 38), even if empty.
      *
      *      The HTTP request method used to send the request. Value MUST be uppercase, for example: HEAD, GET , POST, etc.
      *      The request URL from Section 9.1.2.
      *      The normalized request parameters string from Section 9.1.1.
      *
      *  See Signature Base String example in Appendix A.5.1.
      */

      signature_base: function () {
        return _.map([this.method, this.url(), this.normalized_params()], function (v) { return Header.escape(v); }).join('&');
      },


      /*
      *  9.1.1.  Normalize Request Parameters
      *
      *  The request parameters are collected, sorted and concatenated into a normalized string:
      *
      *      Parameters in the OAuth HTTP Authorization header excluding the realm parameter.
      *      Parameters in the HTTP POST request body (with a content-type of application/x-www-form-urlencoded).
      *      HTTP GET parameters added to the URLs in the query part (as defined by [RFC3986] section 3).
      *
      *  The oauth_signature parameter MUST be excluded.
      *
      *  The parameters are normalized into a single string as follows:
      *
      *      Parameters are sorted by name, using lexicographical byte value ordering. If two or more parameters
      *      share the same name, they are sorted by their value. For example:
      *
      *                          a=1, c=hi%20there, f=25, f=50, f=a, z=p, z=t
      *
      *      Parameters are concatenated in their sorted order into a single string. For each parameter, the name is
      *      separated from the corresponding value by an ‘=’ character (ASCII code 61), even if the value is empty.
      *      Each name-value pair is separated by an ‘&’ character (ASCII code 38). For example:
      *
      *                          a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t
      *
      */

      normalized_params: function () {
         return (
            _.map(
               _.map(this.signature_params(), function (p) {
                  return _.map(p, function (v) {
                     return Header.escape(v);
                  })
               }).sort(), function (p) { return p.join('='); }).join('&')
            );
      },

      signature_params: function () {
         return _.pairs(this.attributes()).concat(_.pairs(this.params), this.url_params());
      },

      url_params: function () {
         var params = [];

         _.each(URI.parseQuery(this.uri.query || ''), function(vs, k) {
               params.push( _(_.flatten([vs]).sort()).chain().map(function (v) { return [k, v]; }).value() );
            });

         return ( params.flatten ? params.flatten(true) : _.flatten(params, true) );
      }

   });

   SimpleOAuth.Header = Header;

   /*
   *  Need to have some URI utilities for parsing the URI and query string.
   *  This mimics some of the functionality provided by the Ruby URI and CGI
   *  modules
   */
   var _uri = function (parsed) {

      _.extend(this, parsed);

      this.default_port = '';
      switch (this.scheme) {

         case 'http' :
            this.default_port = '80';
            break;
         case 'https' :
            this.default_port = '443';
            break;

      }
   }
   _.extend(_uri.prototype, {

      normalize: function () {

         if ( this.path && this.path == '' )
            this.path = '/';

         if ( this.scheme && this.scheme != this.scheme.toLowerCase() )
            this.scheme = this.scheme.toLowerCase();

         if ( this.host && this.host != this.host.toLowerCase() )
            this.host = this.host.toLowerCase();

      },

      build: function () {
         var str = '';

         if ( this.scheme ) {
            str += this.scheme;
            str += ':';
         }

         if ( this.opaque ) {
            str += this.opaque;
         } else {

            if ( this.host )
              str += '//';

            if ( this.userinfo ) {
              str += this.userinfo;
              str += '@';
            }

            if ( this.host ) {
              str += this.host;
            }

            if ( this.port && this.port != this.default_port ) {
              str += ':';
              str += this.port;
            }

            str += this.path;

            if ( this.query ) {
               str += '?';
               str += this.query;
            }
         }

         if ( this.fragment ) {
            str += '#';
            str += this.fragment;
         }

         return str;
      }
   });

   URI = {

       /*!
        * Adapted from OAuthSimple
        * A simpler version of OAuth
        *
        * author:     jr conlin
        * mail:       src@anticipatr.com
        * copyright:  unitedHeroes.net
        * version:    1.2
        * url:        http://unitedHeroes.net/OAuthSimple
        *
        * Copyright (c) 2011, unitedHeroes.net
        *
       */

      parseQuery: function (query) {

         var elements = (query || "").split('&'),
             result={},
             element;

         for( element=elements.shift();element;element=elements.shift() ) {

            var keyToken=element.split('='),
                value='';

            if (keyToken[1]) {
               value=decodeURIComponent(keyToken[1]);
            }

            if(result[keyToken[0]]) {

               if (!(result[keyToken[0]] instanceof Array)) {
                  result[keyToken[0]] = Array(result[keyToken[0]], value);
               } else {
                 if ( _.isArray(result[keyToken[0]]) )
                    result[keyToken[0]].push(value);
                 else
                    result[keyToken[0]] = [result[keyToken[0]], value];
               }

            } else {
               result[keyToken[0]] = value;
            }
         }

         return result;

      },

      queryString: function (params) {
         return _.map(params, function (v,k) { return k+'='+encodeURIComponent(v); }).join('&');
      },

      /*!
      * parseUri 1.2.2
      * (c) Steven Levithan <stevenlevithan.com>
      * MIT License
      */

      parseUri: function (str) {
         var uri = Parser.parse(str);

         uri['queryKey'] = {};
         uri['query'].replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function ($0, $1, $2) {
            if ($1) uri['queryKey'][$1] = $2;
         });

         return new _uri(uri);
      },

   }

   /*!
   * https://github.com/medialize/URI.js
   * MIT License
   */

   var Parser = {};

   Parser.protocol_expression = /^[a-z][a-z0-9.+-]*$/i;

   Parser.parse = function( string ) {
      var pos, parts = {};
      // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]

      // extract fragment
      parts.source = string;
      parts.fragment = '';

      pos = string.indexOf('#');
      if (pos > -1) {
         // escaping?
         parts.fragment = string.substring(pos + 1) || '';
         string = string.substring(0, pos);
      }

      // extract query
      pos = string.indexOf('?');
      parts.query = '';
      if (pos > -1) {
         // escaping?
         parts.query = string.substring(pos + 1) || '';
         string = string.substring(0, pos);
      }

      // extract protocol
      if (string.substring(0, 2) === '//') {
         // relative-scheme
         parts.scheme = null;
         string = string.substring(2);
         // extract "user:pass@host:port"
         string = Parser.parseAuthority(string, parts);
      } else {
         pos = string.indexOf(':');
         if (pos > -1) {
            parts.scheme = string.substring(0, pos) || '';
            if (parts.scheme && !parts.scheme.match(Parser.protocol_expression)) {
               // : may be within the path
               parts.scheme = undefined;
            } else if (parts.scheme === 'file') {
               // the file scheme: does not contain an authority
               string = string.substring(pos + 3);
            } else if (string.substring(pos + 1, pos + 3) === '//') {
               string = string.substring(pos + 3);

               // extract "user:pass@host:port"
               string = Parser.parseAuthority(string, parts);
            } else {
               string = string.substring(pos + 1);
               parts.urn = true;
            }
         }
      }

      // what's left must be the path
      parts.path = string;
      if ( parts.path.length > 0 ) {

         parts.directory = '';
         parts.file = '';
      }

      parts.relative = parts.path + ( parts.query.length > 0 ? '?' + parts.query : '' );

      // and we're done
      return parts;
   };

   Parser.parseHost = function(string, parts) {
      // extract host:port
      var pos = string.indexOf('/');
      var bracketPos;
      var t;

      if (pos === -1) {
         pos = string.length;
      }

      if (string.charAt(0) === '[') {
         // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
         // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
         // IPv6+port in the format [2001:db8::1]:80 (for the time being)
         bracketPos = string.indexOf(']');
         parts.host = string.substring(1, bracketPos) || '';
         parts.port = string.substring(bracketPos + 2, pos) || '';
         if (parts.port === '/') {
            parts.port = '';
         }
      } else if (string.indexOf(':') !== string.lastIndexOf(':')) {
         // IPv6 host contains multiple colons - but no port
         // this notation is actually not allowed by RFC 3986, but we're a liberal parser
         parts.host = string.substring(0, pos) || '';
         parts.port = '';
      } else {
         t = string.substring(0, pos).split(':');
         parts.host = t[0] || '';
         parts.port = t[1] || '';
      }

      if (parts.host && string.substring(pos).charAt(0) !== '/') {
         pos++;
         string = '/' + string;
      }

      parts.authority = ( parts.host || '' ) + ( parts.port ? ':' + parts.port : '' )

      return string.substring(pos) || '/';
   };

   Parser.parseAuthority = function(string, parts) {
      string = Parser.parseUserinfo(string, parts);
      return Parser.parseHost(string, parts);
   };

   Parser.parseUserinfo = function(string, parts) {
      // extract username:password
      var firstSlash = string.indexOf('/');
      /*jshint laxbreak: true */
      var pos = firstSlash > -1
         ? string.lastIndexOf('@', firstSlash)
         : string.indexOf('@');
      /*jshint laxbreak: false */
      var t;

      // authority@ must come before /path
      if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
         t = string.substring(0, pos).split(':');
         parts.user = t[0] ? decodeURIComponent(t[0]) : '';
         t.shift();
         parts.password = t[0] ? decodeURIComponent(t.join(':')) : '';
         parts.userinfo = parts.user + '@' + parts.password;

         string = string.substring(pos + 1);
      } else {
         parts.userinfo = '';
         parts.user = '';
         parts.password = '';
      }

      return string;
   };


   function b64_hmac_sha1(k,d,_p,_z){
   // heavily optimized and compressed version of http://pajhome.org.uk/crypt/md5/sha1.js
   // _p = b64pad, _z = character size; not used here but I left them available just in case
   if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
   }


   return SimpleOAuth;

}));