dojo.provide("dojox.io.OAuth");
dojo.require("dojox.encoding.digests.SHA1");

dojox.io.OAuth = new (function(){
	//	summary:
	//		Helper singleton for signing any kind of Ajax request using the OAuth 1.0 protocol.
	//	description:
	//		dojox.io.OAuth is a singleton class designed to allow anyone to sign a request,
	//		based on the OAuth 1.0 specification, made with any of the Dojo Toolkit's Ajax
	//		methods (such as dojo.xhr[verb], dojo.io.iframe, etc.).
	//
	//		The main method of dojox.io.OAuth is the sign method (see documentation for .sign);
	//		the idea is that you will "sign" the kwArgs object you'd normally pass to any of 
	//		the Ajax methods, and then pass the signed object along.  As long as the token
	//		object used is valid (and the client's date and time are synced with a public
	//		time server), a signed object should be passed along correctly.
	//
	//		dojox.io.OAuth does not deal with the OAuth handshake process at all.
	//
	//		This object was developed against the Netflix API (OAuth-based service); see
	//		http://developer.netflix.com for more details.
	var encode = this.encode = function(s){
		if(!s){ return ""; }
		return encodeURIComponent(s)
			.replace(/\!/g, "%21")
			.replace(/\*/g, "%2A")
			.replace(/\'/g, "%27")
			.replace(/\(/g, "%28")
			.replace(/\)/g, "%29");
	};

	var decode = this.decode = function(str){
		//	summary:
		//		Break apart the passed string and decode.
		//		Some special cases are handled.
		var a=[], list=str.split("&");
		for(var i=0, l=list.length; i<l; i++){
			var item=list[i];
			if(list[i]==""){ continue; }	//	skip this one.
			if(list[i].indexOf("=")>-1){
				var tmp=list[i].split("=");
				a.push([ decodeURIComponent(tmp[0]), decodeURIComponent(tmp[1]) ]);
			} else {
				a.push([ decodeURIComponent(list[i]), null ]);
			}
		}
		return a;
	};

	function parseUrl(url){
		//	summary:
		//		Create a map out of the passed URL.  Need to pull any
		//		query string parameters off the URL for the base signature string.
        var keys = [
				"source","protocol","authority","userInfo",
				"user","password","host","port",
				"relative","path","directory",
				"file","query","anchor"
			],
			parser=/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
			match=parser.exec(url),
			map = {},
			i=keys.length;

		//	create the base map first.
		while(i--){ map[keys[i]] = match[i] || ""; }

		//	create the normalized version of the url and add it to the map
		var p=map.protocol.toLowerCase(),
			a=map.authority.toLowerCase(),
			b=(p=="http"&&map.port==80)||(p=="https"&&map.port==443);
		if(b){
			if(a.lastIndexOf(":")>-1){
				a=a.substring(0, a.lastIndexOf(":"));
			}
		}
		var path=map.path||"/";
		map.url=p+"://"+a+path;

		//	return the map
		return map;
	}

	var tab="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
	function nonce(length){
		var s="", tl=tab.length;
		for(var i=0; i<length; i++){
			s+=tab.charAt(Math.floor(Math.random()*tl));
		}
		return s;
	}
	function timestamp(){
		return Math.floor(new Date().valueOf()/1000)-2;
	}
	function signature(data, key, type){
		if(type && type!="PLAINTEXT" && type!="HMAC-SHA1"){
			throw new Error("dojox.io.OAuth: the only supported signature encodings are PLAINTEXT and HMAC-SHA1.");
		}

		if(type=="PLAINTEXT"){
			return key;
		} else {
			//	assume SHA1 HMAC
			return dojox.encoding.digests.SHA1._hmac(data, key);
		}
	}

	function key(args){
		//	summary:
		//		return the key used to sign a message based on the token object.
		return encode(args.consumer.secret) 
			+ "&" 
			+ (args.token && args.token.secret ? encode(args.token.secret) : "");
	}

	function addOAuth(/* dojo.__XhrArgs */args, /* dojox.io.__OAuthArgs */oaa){
		//	summary:
		//		Add the OAuth parameters to the query string/content.
		var o = {
			oauth_consumer_key: oaa.consumer.key,
			oauth_nonce: nonce(16),
			oauth_signature_method: oaa.sig_method || "HMAC-SHA1",
			oauth_timestamp: timestamp(),
			oauth_version: "1.0"
		}
		if(oaa.token){
			o.oauth_token = oaa.token.key;
		}
		args.content = dojo.mixin(args.content||{}, o);
	}

	function convertArgs(args){
		//	summary:
		//		Because of the need to create a base string, we have to do
		//		some manual args preparation instead of relying on the internal
		//		Dojo xhr functions.  But we'll let dojo.xhr assemble things
		//		as it normally would.
		var miArgs = [{}], formObject;

		if(args.form){
			if(!args.content){ args.content = {}; }
			var form = dojo.byId(args.form);
			var actnNode = form.getAttributeNode("action");
			args.url = args.url || (actnNode ? actnNode.value : null);
			formObject = dojo.formToObject(form);
			delete args.form;
		}
		if(formObject){ miArgs.push(formObject); }
		if(args.content){ miArgs.push(args.content); }

		//	pull anything off the query string
		var map = parseUrl(args.url);
		if(map.query){ 
			var tmp = dojo.queryToObject(map.query);
			//	re-encode the values.  sigh
			for(var p in tmp){ tmp[p] = encodeURIComponent(tmp[p]); }
			miArgs.push(tmp);
		}
		args._url = map.url;

		//	now set up all the parameters as an array of 2 element arrays.
		var a = [];
		for(var i=0, l=miArgs.length; i<l; i++){
			var item=miArgs[i];
			for(var p in item){
				if(dojo.isArray(item[p])){
					//	handle multiple values
					for(var j=0, jl=item.length; j<jl; j++){
						a.push([ p, item[j] ]);
					}
				} else {
					a.push([ p, item[p] ]);
				}
			}
		}

		args._parameters = a;
		return args;
	}

	function baseString(/* String */method, /* dojo.__XhrArgs */args, /* dojox.io.__OAuthArgs */oaa){
		//	create and return the base string out of the args.
		addOAuth(args, oaa);
		convertArgs(args);

		var a = args._parameters;

		//	sort the parameters
		a.sort(function(a,b){
			if(a[0]>b[0]){ return 1; }
			if(a[0]<b[0]){ return -1; }
			if(a[1]>b[1]){ return 1; }
			if(a[1]<b[1]){ return -1; }
			return 0;
		});

		//	encode.
		var s = dojo.map(a, function(item){
			return encode(item[0]) + "%3D" + encode(item[1]||"");
		}).join("%26");

		var baseString = method.toUpperCase()
			+ "&" + encode(args._url) 
			+ "&" + s;
		return baseString;
	}

	function sign(method, args, oaa){
		//	return the oauth_signature for this message.
		var k = key(oaa),
			message = baseString(method, args, oaa),
			s = signature(message, k, oaa.sig_method || "HMAC-SHA1");
		args.content["oauth_signature"] = s;
		return args;
	}
	
	/*=====
	 	dojox.io.OAuth.__AccessorArgs = function(key, secret){
			//	key: String
			//		The key or token issued to either the consumer or by the OAuth service.
			//	secret: String
			//		The secret (shared secret for consumers, issued secret by OAuth service).
			this.key = key;
			this.secret = secret;
		};
		dojox.io.OAuth.__OAuthArgs = function(consumer, sig_method, token){
			//	consumer: dojox.io.OAuth.__AccessorArgs
			//		The consumer information issued to your OpenAuth application.
			//	sig_method: String
			//		The method used to create the signature.  Should be PLAINTEXT or HMAC-SHA1.
			//	token: dojox.io.OAuth.__AccessorArgs?
			//		The request token and secret issued by the OAuth service.  If not
			//		issued yet, this should be null.
			this.consumer = consumer;
			this.token = token;
		}
	=====*/

	/*	
	 *	Process goes something like this:
	 *	1. prepare the base string
	 *	2. create the key
	 *	3. create the signature based on the base string and the key
	 *	4. send the request using dojo.xhr[METHOD].
	 */

	this.sign = function(/* String*/method, /* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs */oaa){
		//	summary:
		//		Given the OAuth access arguments, sign the kwArgs that you would pass
		//		to any dojo Ajax method (dojo.xhr*, dojo.io.iframe, dojo.io.script).
		//	example:
		//		Sign the kwArgs object for use with dojo.xhrGet:
		//	|	var oaa = {
		//	|		consumer: {
		//	|			key: "foobar",
		//	|			secret: "barbaz"
		//	|		}
		//	|	};
		//	|
		//	|	var args = dojox.io.OAuth.sign("GET", myAjaxKwArgs, oaa);
		//	|	dojo.xhrGet(args);
		return sign(method, args, oaa);
	};


	//	TODO: handle redirect requests?
	this.xhr = function(/* String */method, /* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs */oaa, /* Boolean? */hasBody){
		/*	summary:
		 *		Make an XHR request that is OAuth signed.
		 *	example:
		 *	|	var dfd = dojox.io.OAuth.xhrGet({ 
		 *	|		url: "http://someauthdomain.com/path?foo=bar",
		 *	|		load: function(response, ioArgs){ } 
		 *	|	}, 
		 *	|	{
		 *	|		consumer:{ key: "lasdkf9asdnfsdf", secret: "9asdnfskdfysjr" }
		 *	|	});
		 */
		sign(method, args, oaa);
		return dojo.xhr(method, args, hasBody);
	};

	this.xhrGet = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
		return this.xhr("GET", args, oaa);
	};
	this.xhrPost = this.xhrRawPost = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
		return this.xhr("POST", args, oaa, true);
	};
	this.xhrPut = this.xhrRawPut = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
		return this.xhr("PUT", args, oaa, true);
	};
	this.xhrDelete = function(/* dojo.__XhrArgs */args, /* dojox.io.OAuth.__OAuthArgs*/ oaa){
		return this.xhr("DELETE", args, oaa);
	};
})();
