var HURLANT = window.HURLANT || {};

HURLANT.jsobject = (function(){

	// inline the pieces of SWFObjec we need in here. XXX

	// Globals. this library has a few.
	var FlashPackage = window.FlashPackage = function FlashPackage(name) {
		this.toString = function(){return "[FlashPackage "+name+"]";};
	};
	window.FlashPackages = new FlashPackage("");
	var FlashObject = window.FlashObject = function FlashObject(id, r) {
		this.toString = function(){ return "[FlashObject "+r.name+"]"; };
		if (id) this.__jsobject__ref = id; 
		// loop through r.instance
		applyGetterSetter(this, id, r.instance);
	};
	var FlashFunction = window.FlashFunction = function FlashFunction(id, r) {
		return FlashCallable(id, r, "FlashFunction", FlashFunction);
	}
	FlashFunction.prototype = FlashFunction;
	var FlashClass = window.FlashClass = function FlashClass(id, r) {
		return FlashCallable(id, r, "FlashClass", FlashClass);
	}
	FlashClass.prototype = FlashClass;
	function FlashCallable(id, r, str, c) {
		var obj = function() {
			var a = Array.prototype.slice.call(arguments);
			for (var i=0;i<a.length;i++) {
				a[i]=wrap(a[i]);
			}
			if (this.constructor==obj) {
				// called with "new"
				return unwrap(bridge.newRef(id, a));
			} else {
				// called normally
				return unwrap(bridge.callRef(id, a));
			}
			// XXX call domproxy::releaseRef() or something like it!@!!
		};
		obj.toString = function(){ return "["+str+" "+r.name+"]"; };
		obj.__proto__ = c; // gecko only. "instanceof" will probably not work on other browsers
		if (id) obj.__jsobject__ref = id; 
		applyGetterSetter(obj, id, r["static"]);
		return obj;
	}
	
	
	// stuff

	function populatePackages(builtins) {
		// convert builtins array into a map
		var map = {};
		for (var i=0;i<builtins.length;i++) {
			var items=builtins[i].split("::");
			if (items.length==1) {
				// no namespace
				(map[""]=map[""]||[]).push(items[0]);
			} else {
				var ns = items[0];
				(map[ns]=map[ns]||[]).push(items[1]);
			}
		}
		// build skeleton
		for (var ns in map) {
			var p = createPackage(ns, FlashPackages, '');
			var objects = map[ns];
			for (var i=0;i<objects.length;i++) {
				var object = objects[i];
				if (p.__defineGetter__) {
					p.__defineGetter__( object, function(ns, object,p){ var cache;return function() {
						if (!cache) try{ 
							cache = getAsGlobal(ns+"."+object); 
						}catch(e){
							// clean up things that aren't in this player version
							delete p[object]; 
						}
						return cache;
					}}(ns,object,p));
				} else { // no getters? no subtlelty.
					try{p[object] = getAsGlobal(ns+"."+object);}catch(e){}
				}
			}
		}
	}
	function createPackage(ns, parent, path) {
		if (ns=="") return parent;
		var chunk=ns.split(".");
		if (!parent[chunk[0]]) {
			parent[chunk[0]] = new FlashPackage(path+chunk[0]);
		}
		var p = parent[chunk[0]];
		path = path+chunk[0]+".";
		for (var i=1;i<chunk.length;i++) {
			p = createPackage(chunk[i], p, path);
			path += chunk[i]+".";
		}
		return p;
	}
	function getAsGlobal(path) {
		return unwrap(bridge.getGlobal(path));
	}
	
	////////////////////////////////////////////////
	// Generic mapping of items from Flash to JS  //
	////////////////////////////////////////////////
	function resolveAsThing(id, r) {
		if (r==null) return undefined; // the player knows nothing about this object
		switch (r.type) {
			case "Function": // a plain old function
				return new FlashFunction(id, r);
				break;
			default:
			case "Object": // an instance.
				return new FlashObject(id, r);
				break;
			case "Class":
				return new FlashClass(id, r);
				break;
			/*default:*/
				// undefined.
				return undefined;
		}
	}
		
	function applyGetterSetter(obj, id, from) {
		var g = from.getters;
		if (g) {
			for (var i=0;i<g.length;i++) {
				var f="get"+g[i].charAt(0).toUpperCase()+g[i].substr(1);
				try {
					obj[f] = bridgeGetter(id,g[i])
					obj.__defineGetter__(g[i], obj[f]);
				} catch (e) {
//					debugger;
				}
			}
		}
		var s = from.setters;
		if (s) {
			for (var i=0;i<s.length;i++) {
				var f="set"+s[i].charAt(0).toUpperCase()+s[i].substr(1);
				try {
					obj[f] = bridgeSetter(id,s[i])
					obj.__defineSetter__(s[i], obj[f]);
				} catch (e) {
//					debugger;
				}
			}
		}
		var m = from.constants;
		if (m) {
			for (var i=0;i<m.length;i++) {
				try {
					obj[m[i]] = unwrap(bridge.getRef(id, m[i]));
				} catch(e) {
//					debugger;
				}
			}
		}
		function bridgeGetter(id,prop) {
			var f = function() {
				try {
					return unwrap(bridge.getRef(id, prop));
				} catch(e) {
//					debugger;
				}
			}
			f.toString = function() {
				return "[FlashGetter "+prop+"]";
			}
			return f;
		}
		function bridgeSetter(id,prop) {
			var f = function(val) {
				try {
					return unwrap(bridge.setRef(id, prop, wrap(val)));
				} catch(e) {
//					debugger;
				}
			}
			f.toString = function() {
				return "[FlashSetter "+prop+"]";
			}
			return f;
		}
	}

	////////////////////////////////////////////////////////////////
	// marshalling of objects between JavaScript and ActionScript //
	////////////////////////////////////////////////////////////////
	var refs = { "nul":null };
	var idcount=0;
	
	function clean(keys) {
		var map = { "nul":1 };
		var total=0;
		var cleaned=0;
		for(var i=0;i<keys.length;i++) {
			map[keys[i]]=1;
		}
		for (var ref in refs) {
			if (!map[ref]) {
				cleaned++;
				try{ delete refs[ref].__js_object__id; } catch (e) {};
				delete refs[ref];
			} else {
				total++;
			}
		}
//		if (cleaned) 
//			alert("links="+total+" (cleaned "+cleaned+")");
	}

	function wrap(value) {
		if (value===undefined) return undefined;
		if (value===null) return null;
        var t = typeof value;
		if (value instanceof Array) {
			var a =[];
			for (var i=0;i<value.length;i++) {
				a[i]=wrap(value[i]);
			}
			return a;
		}
		if (t=="boolean"||t=="number") return value;
		if (t=="string") return "X"+value+"X";
		try {
			if (value.__jsobject__ref!=null) return {ref:value.__jsobject__ref};
		}catch(e){}
		var id = genId(value);
		return {id:id, callable:(t=="function")};
	}
	function genId(obj) {
	  // try to short-circuit the search
	  if (obj==null) return "nul";
	  try {
		  if (obj&&obj.__jsobject__id&&(refs[obj.__jsobject__id]===obj)) {
			return obj.__jsobject__id;
		  }
	  } catch (e){};
	  var id = "obj"+(idcount++);
	  refs[id] = obj;
	  // setup short-circuit for later lookups.
	  try { obj.__jsobject__id = id; } catch (e) {};
	  return id;
	}
	
	function unwrap(ref) {
		if (ref==null) {
			// this should only happen during an attempt to recursively call through the flash player, which not all browsers support.
			throw new Error("Reentrant Flash calls are not supported in your browser. Refactor your code with setTimeout to avoid this error.");
		}
		if (ref instanceof Array) {
			var a=[];
			for (var i=0;i<ref.length;i++) {
				a[i]=unwrap(ref[i]);
			}
			return a;
		}
		if (typeof ref == "object") {
			if (ref.ref) return refs[ref.ref];
			if (ref.nul) return null;
			return resolveAsThing(ref.id, ref.r);
		}
		return ref;
	}

	///////////////////////////////////////////////////////
	// JavaScript proxy implementation from JS to Flash  //
	///////////////////////////////////////////////////////
	function getGlobal() {
		return wrap(window);
	}
	function getReference(name, parent) {
		parent = parent?refs[parent]:window;
		var obj=null;
		try{ obj=parent[name]; }catch(e){}
		return wrap(obj);
	}
	function setReference(name, parent, obj) {
		refs[parent][name] = unwrap(obj);
	}
	function callReferenceById(name, parent, a) {
		parent = refs[parent];
		if (parent==null) {
			return wrap(name=="toString"?"null":null);
		}
		return wrap(makeFunction(parent[name]).apply(parent, unwrap(a)));
	}
	function delReference(name, parent) {
		try { return delete refs[parent][name]; }catch(e){}
		return false;
	}
	function hasReference(name, parent) {
		return (typeof refs[parent][name]!="undefined"); // not 100% correct. :-/
	}
	function getIndexes(parent) {
		parent = refs[parent];
		var items = [];
		for (var x in parent) {
			if (x!="__jsobject__id")
				items.push(x);
		}
		return items;
	}
	function callReference(ref, a) {
		return wrap(makeFunction(refs[ref]).apply(this, unwrap(a)));
	}
	function newReference(ref, a) {
		ref=refs[ref];
		a=unwrap(a);
		switch(a.length) {
			case 0: return wrap(new ref);
			case 1: return wrap(new ref(a[0]));
			case 2: return wrap(new ref(a[0],a[1]));
			case 3: return wrap(new ref(a[0],a[1],a[2]));
			case 4: return wrap(new ref(a[0],a[1],a[2],a[3]));
			case 5: return wrap(new ref(a[0],a[1],a[2],a[3],a[4]));
			case 6: return wrap(new ref(a[0],a[1],a[2],a[3],a[4],a[5]));
			case 7: return wrap(new ref(a[0],a[1],a[2],a[3],a[4],a[5],a[6]));
			default:
				alert("new JS from Flash with "+a.length+" parameters is not implemented.");
		}
	}
	function makeFunction(ff) {
		if(ff==null||ff.apply) return ff; // no point wrapping ff.
		var nf = function(a,b,c,d,e,f,g) {
			var id = "___obj"+(idcount++), ret;
			this[id]=ff;
			if (this.constructor == arguments.callee) {
				switch(arguments.length) {
					case 0: ret = new this[id](); break;
					case 1: ret = new this[id](a); break;
					case 2: ret = new this[id](a,b); break;
					case 3: ret = new this[id](a,b,c); break;
					case 4: ret = new this[id](a,b,c,d); break;
					case 5: ret = new this[id](a,b,c,d,e); break;
					case 6: ret = new this[id](a,b,c,d,e,f); break;
					case 7: ret = new this[id](a,b,c,d,e,f,g); break;
					default:
						alert(":(");
				}
			} else {
				switch(arguments.length) {
					case 0: ret = this[id](); break;
					case 1: ret = this[id](a); break;
					case 2: ret = this[id](a,b); break;
					case 3: ret = this[id](a,b,c); break;
					case 4: ret = this[id](a,b,c,d); break;
					case 5: ret = this[id](a,b,c,d,e); break;
					case 6: ret = this[id](a,b,c,d,e,f); break;
					case 7: ret = this[id](a,b,c,d,e,f,g); break;
					default:
						alert(":(");
				}
			}
			delete this[id];
			return ret;
		};
		nf.toString = function() { return ff+""; };
		return nf;
	}
	
	var apis = {
		getGlobal:getGlobal,
		getRef:getReference,
		setRef:setReference,
		callRefById:callReferenceById,
		delRef:delReference,
		hasRef:hasReference,
		getIndexes:getIndexes,
		callRef:callReference,
		newRef:newReference,
		clean:clean
	};
	
	// 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.
	// oh, how I wish the EcmaScript spec would call such things out clearly.
	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; 
	}

	// bridge initialization code
	var isReady = false;
	var readyListeners = [];
	var flashParent, flashWidth=1, flashHeight=1;
	var flashSwf = "Bridge.swf";
	var bridge;
	var bid = "HurlantJSObjectBridge";
	function ready(){
		bridge = document.getElementById(bid);
		if (!isReady) {
			isReady = true;
			for (var i=0;i<readyListeners.length;i++) {
				setTimeout(readyListeners[i],i);
			}
		} 
	}
	function loadBridge() {
		if (!flashParent) {
			flashParent = document.body;
		}
		if (typeof flashParent == "string") {
			flashParent = document.getElementById(flashParent);
		}
		var div = document.createElement("div");
		div.id = bid;
		flashParent.appendChild(div);
		swfobject.embedSWF(flashSwf, bid, flashWidth, flashHeight, "9.0.0", "expressInstall.swf");
	}
	swfobject.addDomLoadEvent(loadBridge);
	bridge_ready = ready;
	
	return {
		onReady:function(listener) {
			if (isReady) {
				listener();
			} else {
				readyListeners.push(listener);
			}
		},
		expose:function(arg) {
			if (!(arg instanceof Array)) arg=[arg];
			populatePackages(arg);
		},
		use:function (p,w,dontExpose) {
			w=w||window;
			var a=p.split("."),s=FlashPackages;
			while (a.length) {
				var n=a.shift();
				if (n=="*") {
					for (var k in s) {s[k]} // one loop to weed out bogus entries
					for (var k in s) { // one loop to assign the survivors
						w[k] = s[k];
					}
					return;
				} else {
					s=s[n];
				}
			}
			if (s==null && !dontExpose) {
				expose(p);
				return use(p,w,true);
			}
			if (s!=null) {
				w[n]=s;
			}
		},
		getCanvas: function() {
			return unwrap(bridge.getRoot());
		},
		putCanvas:function(parent,width,height) {
			flashParent = parent;
			flashWidth = width;
			flashHeight = height;
		},
		setSwf:function(swf) {
			flashSwf = swf;
		},
		call: function() {
			var args = Array.prototype.slice.call(arguments); 
			var f = makeFunction(bridge[args.shift()]);
			if (f) {
				return f.apply(bridge,args);
			}
		}
		
	}
})();