/**
 * The counterpart for DOMProxy.as
 *
 */

var HURLANT = window.HURLANT || {};

HURLANT.jsobject = (function(){
	var refs = { "nul":null };
	var idcount=0;
	var flo = null;

	function genId(obj) {
	  // try to short-circuit the search
	  try {
	  	  if (obj==null) {
	  	  	return "nul";
	  	  }
		  if (obj&&obj.__jsobject__id) {
		  	if (refs[obj.__jsobject__id]===obj) {
		  		return obj.__jsobject__id;
		  	}
		  }
	  } catch (e){};
	  // we used to loop over existing ids to avoid duplicates.
	  // that was slow. we don't do that now.
	  var id = "obj"+(idcount++);
	  refs[id] = obj;
	  // setup short-circuit for later lookups.
	  try {
	  	obj.__jsobject__id = id;
	  } catch (e) {
//	  	alert("can't setup short circuit for obj="+obj+" (id="+id+")");
	  };
	  return id;
	}
	function releaseRef(obj) {
		try {
			var id = obj.__jsobject__id;
			if (id) {
				delete refs[id];
			}
		} catch (e){};
	}

	if (window.jsobject_debug) {
		setInterval(function(){
			// count refs
			var i=0;
			for (var k in refs) i++;
			document.getElementById("counter").innerHTML=i;
		}, 2000);
	}
	
	function resolve(o) {
		if (o==null) { return o; }
		var val;
		switch(o.type) {
			case "ref":
				val = refs[o.id];
				break;
			case "func":
				val = resolveFunction(o.id);
				break;
			case "obj":
				val = resolveObject(o.id, o.str);
				break;
			case "value":
				val = o.val;
				break;
		}
		return val;
	}
	function resolveFunction(id) {
		var o = flo;
		var f = function() {
			var a=arguments;
			var b = [];
			for (var i=0;i<a.length;i++) {
				b[i] = returnRef(a[0]);
			}
			var v = o.jsobject(id, returnRef(this), b);
			// XXX a bit weird: We detect first arguments that look like "event" and release them as soon as we return.
			if (a[0]&&a[0].constructor.toString().toLowerCase().indexOf("event")>-1) {
				releaseRef(a[0]);
			}
			return resolve(v);
			//var t=(new Date).getTime();
			//document.getElementById("ms").innerHTML=(new Date).getTime()-t;
			return r;
		};
		f.toString = function() {
			return "function () {\n  [ScreamingDonkey compiled code]\n}"; // XXX hide the ugly.
		}
		return f;
	}
	function resolveObject(id, str) {
		// very primitive object mapping,
		// we don't generally have a Proxy equivalent on the js side.
		return {
			toString:function(){return str;},
			__jsobject__id:id
		};
	}
	function returnRef(obj) {
	  var t = typeof obj;
	  if (t=="string") {
	  	obj = "X"+obj+"X"; // externalinterface enjoys trimming whitespace from strings. this awesome hack convinces it not to. :'(
	  }
	  if (t=="number"||t=="string"||t=="boolean"||t==null) {
	  	return {value:obj};
	  }
	  if (obj==null) return null;
	  var id = genId(obj);
	  if (t=="function") {
		return {id:id, callable:true};
	  } else {
		return {id:id};
	  }
	}
	
	function callRefByValue(name, parent, args) {
		parent = refs[parent];
		if (parent==null) {
			switch(name) {
				case "toString": return returnRef("null");
				default:		 return returnRef(null);
			}
		}
		try {
			var obj = makeFunction(parent[name]).apply(parent, args);
		} catch (e) {
			alert("apply: "+(e.description||e));
			obj = null;
		}
		return returnRef(obj);
	}
	
	function makeFunction(ff) {
		if (ff==null) return ff; // not ===. we want to match undefined too. same applies to every other ==null here.
		if (ff.apply) { return ff; }
		// Yes, native functions on IE don't have an apply or call method
		// So here's our awesome workaround. enjoy!
		var nf = function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s) {
			if (this.constructor == arguments.callee) {
				return new ff (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s);
			} else {
				return ff  (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s);
			}
		};
		// hide the gross things we're doing for IE.
		var s = ff+"";
		nf.toString = function() { return s; };
		return nf;
	}
	
	function setup(id) {
		// swfobject doesn't play nice with ExternalInterface.objectID on Gecko browsers, so we cheat.
		flo = document.getElementById(id||"ScreamingDonkey"); // save a reference to the flash movie we're working with
		return returnRef(window);		// and pass a reference of our global object to the movie.
	}
	function getReference(name, parent) {
	  if (parent=="obj0" && name=="window") {
	  	// half-assed attempt to unbreak IE8. (window !== window.window. it hurts.)
		return {id:parent};
	  }
	  if (parent==null) { 
	  	alert("empty parent getRef call.");
	    parent=window;
	  } else {
	    parent = refs[parent];
	  }
	  var obj = null
	  try {
	  	obj = parent[name];
	  } catch (e) {};
	  var r = returnRef(obj);
	  return r;
	}
	function setRef(name, parent, o) {
		parent = refs[parent];
		parent[name] = resolve(o);
	}
	function callRefById(name, parent, args) {
		for (var i=0;i<args.length;i++) {
			args[i] = resolve(args[i]);
		}
		return callRefByValue(name, parent, args);
	}
	function delReference(name, parent) {
		parent = refs[parent];
		try {
			return delete parent[name];
		} catch (e) {
			return false;
		}
	}
	function hasReference(name, parent) {
		parent = refs[parent];
		return (typeof parent[name] != "undefined"); // not 100% correct, but close enough.
	}
	function getIndexes(parent) {
		parent = refs[parent];
		var items = [];
		for (var x in parent) {
			if (x!="__jsobject__id")
				items.push(x);
		}
		return items;
	}
	function callRef(ref, args) {
		for (var i=0;i<args.length;i++) {
			args[i] = resolve(args[i]);
		}
		ref = refs[ref];
		var obj = makeFunction(ref).apply(this, args);
		return returnRef(obj);
	}
	function newRef(ref, args) {
		for (var i=0;i<args.length;i++) {
			args[i] = resolve(args[i]);
		}
		ref = refs[ref];
		// XXX am I missing something, or is there no way to combine .apply with new? me sad.
		switch (args.length) {
			case 0: return returnRef(new ref);
			case 1: return returnRef(new ref(args[0]));
			case 2: return returnRef(new ref(args[0],args[1],args[2]));
			case 3: return returnRef(new ref(args[0],args[1],args[2],args[3])); 
			case 4: return returnRef(new ref(args[0],args[1],args[2],args[3],args[4])); 
			case 5: return returnRef(new ref(args[0],args[1],args[2],args[3],args[4],args[5])); 
			case 6: return returnRef(new ref(args[0],args[1],args[2],args[3],args[4],args[5],args[6])); 
			case 7: return returnRef(new ref(args[0],args[1],args[2],args[3],args[4],args[5],args[6],args[7]));
			default:
				alert("new func() with >7 parameters not implemented"); 
		}
	}
	
	var apis = {
		setup:setup,
		getReference:getReference,
		setRef:setRef,
		callRefById:callRefById,
		delReference:delReference,
		hasReference:hasReference,
		getIndexes:getIndexes,
		callRef:callRef,
		newRef:newRef
	};
	
	// initialization code
	HURLANT_jsobject_dispatch = function(id) {
		var args = Array.prototype.slice.call(arguments); 
		var f = apis[args.shift()];
		if (f) {
			return f.apply(this,args);
		}
	};
	
	// on IE and Safari, various global functions and constructors are, in fact, not functions.
	// that confuses my code. so we make them be functions.
	var weirdFunctions = [ "alert", "blur", "close", "confirm", "focus", "moveBy", "moveTo", "open", "print", "prompt", "resizeBy", "resizeTo", "scroll", "scrollBy", "scrollTo", "XMLHttpRequest", "ActiveXObject" ];
	for (var i=0;i<weirdFunctions.length;i++) {
		var f=weirdFunctions[i];
		var nf = makeFunction(window[f]);
		if (window[f]!==nf)
		window[f] = nf; 
	}

	return {};
})();
