//<-- jsonLib.js ---------------------------------------------------
//
// JSON -- a simple class for making HTTP requests
// using dynamically generated script tags and JSON,
// and support for Webshots API deserialisation.
// It uses: output=JSON&callback=function - parameters to retrieve JSON formatting. 
//
// Author: Yuri Brodsky, CNET Inc.
// Date: February 20th, 2007
//
// Sample Usage:
//
// JSON -- a simple class for making HTTP requests
// using dynamically generated script tags and JSON
// Sample Usage:
//
// < script type="text/javascript" src="json.js">< /script>
// 
// function makeCall() {
//    var frm = document.forms[0];
//	  JSON.Call( "http://client.webshots.com/client/co1.fcgi?a=getAllAlbums2", 
//		{ 'v': frm.v.value,
//		  'c': frm.c.value,
//		  'u': frm.u.value, 
//		  'p': frm.p.value, 
//		  'username':frm.username.value },
//		function( obj ) { 
//        alert( "Result:"+ obj );
//      },
//	    function( obj ) { alert( obj.description ); }	
//    );
//  }
//

var JSON = {
    //Call counter to inforce callId uniqness
	callCount: 0,
	//Storage for callback functions, allow simultaneous method invocation 
   	responders: {},
   	//Remote call invocation to url with parameters as an name/value hash map.
   	//Method accept an array for parameters with multiple values
	Call: function (url, params, onSuccess, onError) {
		    var id = (++this.callCount)+"_"+(new Date()).getTime();
		    this.responders[id] = new Object();
			 
			var urlStr = url ;
   			urlStr += ( urlStr.indexOf("?") > 0 ? "&": "?") + "output=JSON&callback=JSON.responders['"+id+"'].success"; 
	   			 
			var script = document.createElement('script');
			script.type = 'text/javascript';
			//Construct parameter string 
			for( var p in params) {
			    var value = params[p];
			    var name = encodeURIComponent(p);
			    if(value instanceof Array) {
					for(var i=0; i< value.length; i++)
						urlStr += "&" + name + "=" + encodeURIComponent(params[p])     
			    } else {
					urlStr += "&" + name + "=" + encodeURIComponent(params[p])
				}
			}
			script.src = urlStr;
			//Enclosure: script, id, onError, onSuccess 
			var cleanUp = function() {
				script.parentNode.removeChild(script);	
				delete JSON.responders[id];		
			}
			var errorHandler = function() {
				if(JSON.responders[id]) {
					var theError = new Error("Connection error")
					theError.number = -1;
					onError( theError );					
					cleanUp();
				}
			} 
			//Callback event, it is expecting  JSON RPC format:
			//     "{'result': result,'error':error,'id':id}" 
			this.responders[id].success = function( obj ) {
			    //Server error detection
				if(obj && obj.error ) {
					var theError = new Error(obj.error.description)
					theError.number = obj.error.number;
					onError( theError );
				} else if( obj && obj.result != undefined ) {
					onSuccess(obj.result);
				} else {
					var theError = new Error("Invalid format")
					theError.number = -2;
					onError( theError );
				}
				cleanUp();
			}
			//Catch invalid urls or empty content (no call of success function in respond).
	    	//    Use onload event to make sure that call was competed with callback 
			//       and raise error handler and removeScriptTag if it failed.
			script.onerror = script.onload = errorHandler;
			script.onreadystatechange = function() {
				if (this.readyState == 'loaded' || this.readyState == 'complete') {
						errorHandler();
				}
			};

			document.getElementsByTagName('head')[0].appendChild(script);
		}
}	

//This function parse Webshots client API and return JSON object or an Error object. 
//The fuction could rise the "Invalid response format" error
function webshotsApi( respond ) {
	var result = respond.split("\n");
	var status = result[0];
	var ret = new Object();
	for(var i=1; i<result.length; i++) {
		//Skip empty lines if they exist
		if( /\S/.test( result[i]) ) {
		    var parser = result[i].match(/^(.*)\|\|(.*)$/);
			if( ! parser ) {
				throw new Error("Invalid response format: line "+ i +" has no name:" + result[i]);
			} else {
				var name = parser[1];
				var val  = parser[2];
				if( ret[name] != undefined )
					throw new Error("Invalid response format: line "+ i +" has duplicated name:" + name); 
				ret[name] = val;
			} 
		} 
	}
	if(	0 == status ) {
		var theError = new Error(ret.Error);
		theError.number = status;
		theError.URL = ret.URL;
		theError.URLMessage = ret.URLMessage
		alert(ret.Error);
		return theError;
	} else if( 1 != status || 1 != ret.success ){
		throw new Error("Invalid response format: unrecognised success status:"+ dump(ret) );
	}	   

	//Convert Object to Arrray is it needed
	if( ret.count ) {
		var arr = new Array(ret.count*1);
		arr.success = ret.success;
		for(var en in ret ) {
		    if( en != "count" && en != "success") {
				var parser = en.match(/^(.*)_(\d+)$/);
				if( ! parser ) { //Not indexed property 
					 throw new Error("Invalid response format: property "+ en +" has not been indexed"); 
				} else {
					var name = parser[1];
					var index = +parser[2];
					if( index <0 || index > ret.count )
					   throw new Error("Invalid response format: property index out of  bounds "+ en +"(count="+ ret.count +")");		
				    var obj = arr[ index ];
				    if( !obj ) {
				    	obj  = new Object();
				    	arr[ index ] = obj;
				    }
				 	obj[ name ] = ret[en];
				}
			}
		}
		ret = arr;
	}
	return ret;
}
//Debug function.
function dump( obj , indent ) {
    var str= "";
	if(! indent )
    	indent = "";
    if( typeof(obj) == "object"  ) { 
		for(var e in obj) {
    		str +=  indent+ e +"="+ dump( obj[e], indent+"\t" ) +"\n";
    	}
	} else {
		str = obj;
	}
	return str;
} 