
var reflectMap={};
function reflect(flobj) {
	var key = ""+flobj;
	if (!reflectMap[key]) {
		reflectMap[key] = str2xml(FlashPackages.flash.utils.describeType(flobj));
	}
	return reflectMap[key];
}

function str2xml(str) {
	try {
		var xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
		xmlDoc.async = true;
		xmlDoc.loadXML(str);
		return xmlDoc;
	} catch(e) {
		try {
			var parser = new DOMParser();
			return parser.parseFromString(str, "text/xml");
    	} catch(e) {alert(e.message)}
	}
}

function enumPackages(p, base) {
	p = p || FlashPackages;
	base = base || "";
	var list = [];
	if (p===FlashPackages) {
		list.push({r:"global", p:FlashPackages});
	}
	for (var k in p) {
	    var o = p[k];
	    if (isPackage(o)) {
	    	// make sure the package isn't empty..
	    	var empty=true;
	    	for (var x in o){o[x]}
	    	for (var x in o){
	    		if ({"toString":1}[x]) continue;
	    		if (!isPackage(o[x])){
	    			empty=false;
	    			break;
	    		}
	    	}
	    	if (!empty) {
		    	list.push({r:base+k, p:o});
		    } 
		    list = list.concat( enumPackages(o,base+k+".") );
	    }
	}
	return list;
}

function isPackage(p) {
	// instanceof FlashPackage fails on IE. improvise
	return p&&p.constructor === FlashPackage
}

function isInterface(cl) {
    var xml = reflect(cl);
    if (xml.getElementsByTagName("type")[0].getAttribute("name")=="Object") return false; // Object special case.
    // interfaces don't extend anything.
	var factory = xml.getElementsByTagName("factory")[0];
	var items = factory.getElementsByTagName("extendsClass");
	return (items.length==0);
}
function isFunction(cl) {
	var n = ""+cl;
	return (n.indexOf("[FlashFunction ")==0);
}
function isObject(cl) {
	var n = ""+cl;
	return (n.indexOf("[FlashObject ")==0);
}

function enumClasses(pkg) {
	var out = {};
	var arr = [];
	var interfaces = [];
	var classes = [];
	var functions = [];
	var objects = [];
	var packageName = (pkg+"").split(" ")[1].split("]")[0]
	for (var i in pkg) {
		var c = pkg[i];
		var n = ""+c;
		if (n.indexOf("[Flash")!=0) continue;
		if (n.indexOf("[FlashPackage ")==0) continue;
		// we ve got one.
		var nn = (c+"").split(" ")[1].split("]")[0].split("::").join(".");
		var othern = (packageName?(packageName+"."):"")+i;
		out[nn] = {r:c, c:c};
		out[othern] = {r:c, c:c};
		arr.push(n);
		if (isFunction(c)) {
			functions.push("[FlashFunction "+packageName+"::"+i+"]");
		} else if (isObject(c)) {
			objects.push("[FlashObject "+packageName+"::"+i+"]");
		} else if (isInterface(c)) {
			interfaces.push(n);
		} else {
			classes.push(n);
		}
	}
	arr.sort();
	interfaces.sort();
	classes.sort();
	functions.sort();
	objects.sort();
	return {hash:out, array:arr, i:interfaces, c:classes, f:functions, o:objects};
}

var classCache = {};
function inspectClass(javaclass) {
  if (!classCache[javaclass]) {

  }
  return classCache[javaclass];
}

///////////

var savedHome = "";
var packages;
function load() {
  // handle bookmarks + back/forward navigation
  setInterval(checkForURL, 100);

  // command line.
  var input = document.getElementById("input");
  input.onkeydown = input_handler_down;
  input.onkeyup = input_handler_up;

  // populate package
  packages = enumPackages();
  //var t = packages.array;
  //var h = packages.hash;
  var t = packages;
  var l = t.length;
  var s="";
  for (var i=0;i<l;i++) {
    s+="<span class=item id=\""+t[i].r+"\" index=\""+i+"\" title='package "+t[i].r+"' onclick='explore(this)'>"+t[i].r+"</span>";
  }
  document.getElementById("packages").innerHTML=s;

  // save home
  savedHome = document.getElementById("inside").innerHTML;

  // focus
  input_focus();
}

function input_focus() {
  document.getElementById("input").focus();
}


function input_handler_down(e) {
  e=e||event;
  var input = document.getElementById("input");
  switch (e.keyCode) {
    case 13: // enter
    var cmd = input.value;
    input.value="";
    input_history.push(cmd);
    input_pointer = input_history.length;
    var console = document.getElementById("console");
    if (console.style.display=="none") {
      toggleConsole();
    }
    context_eval(cmd);
    setTimeout(function(){input.focus()},50);
    break;
    case 38: // up
    if (input_pointer>0) {
      if (input_pointer == input_history.length) {
        input_current = input.value;
      }
      input_pointer--;
      input.value = input_history[input_pointer];
    }
    input.selectionStart = input.selectionEnd = input.value.length;
    e.cancelBubble = true;
    break;
    case 40: // down
    if (input_pointer < input_history.length) {
      input_pointer++;
      if (input_pointer == input_history.length) {
        input.value = input_current;
      } else {
        input.value = input_history[input_pointer];
      }
    }
    input.selectionStart = input.selectionEnd = input.value.length;
    e.cancelBubble = true;
    break;
    case 27: // esc
    input.value = "";
    break;
  }
}
function input_handler_up(e) {
  e=e||event;
  var input = document.getElementById("input");
  switch (e.keyCode) {
    case 38:
    case 40:
    input.selectionStart = input.selectionEnd = input.value.length;
    e.cancelBubble = true;
    break;
  }
}

var context= {};
var input_history = [];
var input_pointer = 0;
var input_current = "";
var counter = 0;
function context_eval(cmd) {
  var console = document.getElementById("console");
  console.innerHTML+="<div class=input>&gt;&gt;"+cmd.split("<").join("&lt;")+"</div>";
  var output;
  try {
    with(context) {
      output = eval(cmd);
    }
    counter ++;
    context["$"+counter] = output;
    console.innerHTML+="<div class=output>$"+counter+"= "+(output+"").split("<").join("&lt;")+"</div>";
  } catch (e) {
    output = e.name+": "+e.message;
    console.innerHTML+="<div class=error>"+(output+"").split("<").join("&lt;")+"</div>";
  } finally {
    console.lastChild.scrollIntoView();
    input_focus();
  }
}

function clearConsole() {
  var console = document.getElementById("console");
  console.innerHTML="";
  input_history = [];
  input_pointer = 0;
  counter = 0;
  //context = {}; // should I?
  input_focus();
  return false;
}

function toggleConsole() {
  var console = document.getElementById("console");
  var inside = document.getElementById("inside");
  if (console.style.display=="") {
	console.style.display="none";
	inside.style.bottom = "20px";
  } else {
    console.style.display="";
    inside.style.bottom = "250px";
  }
  input_focus();
  return false;
}

var oldHash='';
function checkForURL() {
  var hash = location.hash;
  if (hash.charAt(0)=='#') hash=hash.substr(1);
  if (hash==oldHash) return;
  oldHash = hash;
  show(hash, true);
}

var selectedPackage= null;
function explore(link) {
  if (selectedPackage) {
    selectedPackage.className = "item";
    selectedPackage = null;
  }
  selectedPackage = link;
  selectedPackage.className = "item selected";
  document.getElementById("classes").innerHTML="";
  var p = packages[link.getAttribute("index")].p;
  var c = enumClasses(p);
  var s = "<div>"+link.innerHTML+"</div>";
  var h = c.hash;
  var t = c.i;
  var l = t.length;

  function dump(title, t) {
    var l = t.length;
    var s = "";
    if (l>0) {
      s+="<h6>"+title+"</h6>";
      for (var i=0;i<l;i++) {
        var fqcn = (t[i]+"").split("]")[0].split(" ")[1];
        var n = fqcn.split("::")[1]||fqcn;
        s+="<span class=item id=\""+n+"\" title='"+t[i]+"' onclick='show(\""+fqcn+"\")'>"+n+"</span>";
      }
    }
    return s;
  }

  s+=dump("Interfaces", c.i);
  s+=dump("Classes", c.c);
  s+=dump("Functions", c.f);
  s+=dump("Objects", c.o);
  document.getElementById("classes").innerHTML=s;

  input_focus();
}

var selectedClass = null;
var inspectCache = {};
function inspect(link) {
  if (selectedClass) {
    selectedClass.className = "item";
    selectedClass = null;
  }
  selectedClass = link;
  selectedClass.className = "item selected";

  function resolve(root, path) {
    path = path.split("::").join(".").split(".");
    while (path.length>0) {
      root = root[path.shift()];
    }
    return root;
  }
  function getInterfaces(cl) {
    var out=[];
    var xml = reflect(cl);
    var factory = xml.getElementsByTagName("factory")[0];
    if (factory) {
      var items = factory.getElementsByTagName("implementsInterface");
      for (var i=0;i<items.length;i++) {
        out.push(resolve(FlashPackages, items[i].getAttribute("type")));
      }
    }
    return out;
    
  }
  function getSupers(cl) {
  	var out = [cl];
    var xml = reflect(cl);
    var factory = xml.getElementsByTagName("factory")[0];
    if (factory) {
      var items = factory.getElementsByTagName("extendsClass");
      for (var i=0;i<items.length;i++) {
        var t = items[i].getAttribute("type")
        if (t!="Object") {
          out.push(resolve(FlashPackages, t));
        }
      }
    }
    out.reverse();
    return out;
  }

  function classLink(cl) {
    var xml = reflect(cl);
    var n = xml.getElementsByTagName("type")[0].getAttribute("name");
    return "<a class=type href='javascript:show(\""+n+"\")'>"+n+"</a>";
  }

  function shortClassLink(cl) {
    var xml = reflect(cl);
    var type = xml.getElementsByTagName("type")[0];
    var n = type.getAttribute("name").split("::").join(".");

    var i = n.lastIndexOf(".");

	var p,cc;
    if (i==-1) {
    	if (n=="null") return "<span class=type>void</span>";
    	p ='';
    	cc = n;
    } else {
	    p = n.substring(0,i);
	    cc = n.split("[]").join("").split("$")[0];
	    if (cc!=fqcn) {
		  imports[cc] = 1;
		}
    }
	var full = n.split("[]").join("");
	context[n.substr(i+1)] = resolve(FlashPackages, full);
    return "<a class=type href='javascript:show(\""+(n.split("$")[0])+"\")'>"+n.substr(i+1).split("$").join(".")+"</a>";
  }

  function dumpImports() {
  	var p = selectedPackage.innerHTML;
  	if (p=="global") p="";
    var s = "<div class=package><span class=keyword>package</span> "+p+" {</div>";
    s+= "<div class=inpackage>";
    for (var i in imports) {
      s+="<div class=import><span class=keyword>import</span> "+i+";</div>";
    }
    for (var i in namespaces) {
      s+="<div class=import><span class=keyword>namespace</span> "+namespaces[i]+" = \""+i+"\";</div>";
    }
    return s+"\n";
  }

  function displayModifiers(type) {
    var smod = "";
    if (type.getAttribute("isDynamic")=="true") smod+="<span class=keyword>dynamic</span> ";
    if (type.getAttribute("isFinal")=="true") smod+="<span class=keyword>final</span> ";
    //if (type.getAttribute("isStatic")=="true") smod+="<span class=keyword>static</span> ";    
    return smod;
  }

  function stringType(name) {
  	if (name=="*") return "*";
  	var cl = resolve(FlashPackages, name);
  	return shortClassLink(cl);
  }

  function displayConstructor(constructor, type) {
    var name = type.getAttribute("name").split("::").join(".");
    var i = name.lastIndexOf(".");
    if (i>-1) {
      name = name.substr(i+1);
    }
    name = name.split("$").join(".");
    //var mod = m.getModifiers();
    //var thraws = m.getExceptionTypes();
    var args = constructor.getElementsByTagName("parameter");
    var sargs = "";
    for (var i=0;i<args.length;i++) {
      var t = args[i].getAttribute("type");
      var index = args[i].getAttribute("index");
      sargs += "p"+index+":"+stringType(t);
      if (args[i].getAttribute("optional")=="true") sargs += "?";
      if (i<args.length-1) sargs+=", ";
    }
    return "<div class='constructor'><span class=keyword>public</span> <span class=keyword>function</span> "+name+"("+sargs+");"+"</div>";
  }

  function displayNs(x) {
  	var uri = x.getAttribute("uri");
  	var ns = "<span class=keyword>public</span> ";
  	if (uri!=null) {
  		if (!namespaces[uri]) {
  			namespaces[uri] = "ns"+(namespaceCount++);
  		} 
  		ns = namespaces[uri];
  	}
  	return ns;
  }
  
  function displayAccessor(a, type, isStatic) {
  	var className = type.getAttribute("name");
  	var name = a.getAttribute("name");
  	var type = a.getAttribute("type");
  	var access = a.getAttribute("access");
  	var declaredBy = a.getAttribute("declaredBy");
  	var derived = (className!=declaredBy)?" derived":"";
  	var qual = isStatic?"<span class=keyword>static</span> ":"";
  	var s="";
  	var hasGetter = access.indexOf("read")>-1;
  	var hasSetter = access.indexOf("write")>-1;
  	if (hasGetter)
  		s+="<div class='accessor"+derived+"'>"+qual+displayNs(a)+" <span class=keyword>function</span> <span class=keyword>get</span> "+name+"():"+stringType(type)+";</div>";
  	if (hasSetter)
  		s+="<div class='accessor"+derived+"'>"+qual+displayNs(a)+" <span class=keyword>function</span> <span class=keyword>set</span> "+name+"(value:"+stringType(type)+"):void;</div>";
  	return s;
  }
  function displayConstant(c, type, isStatic) {
  	var className = type.getAttribute("name");
  	var name = c.getAttribute("name");
  	var type = c.getAttribute("type");
  	var declaredBy = c.getAttribute("declaredBy") || className;
  	var derived = (className!=declaredBy)?" derived":"";
  	var qual = isStatic?"<span class=keyword>static</span> ":"";
  	
  	var val = ""; // fetch the constant value.. XXX
  	
  	return "<div class='constant"+derived+"'>"+qual+displayNs(c)+" <span class=keyword>const</span> "+name+":"+stringType(type)+val+";</div>";
  }
  function displayVariable(v, type, isStatic) {
  	var className = type.getAttribute("name");
  	var name = v.getAttribute("name");
  	var type = v.getAttribute("type");
  	var declaredBy = v.getAttribute("declaredBy") || className;
  	var derived = (className!=declaredBy)?" derived":"";
  	var qual = isStatic?"<span class=keyword>static</span> ":"";
  	
  	return "<div class='variable"+derived+"'>"+qual+displayNs(v)+" <span class=keyword>var</span> "+name+":"+stringType(type)+";</div>";
  }
  function displayMethod(m, type, isStatic) {
  	var className = type.getAttribute("name");
  	var name = m.getAttribute("name");
  	var type = m.getAttribute("returnType");
  	var declaredBy = m.getAttribute("declaredBy") || className;
  	var derived = (className!=declaredBy)?" derived":"";
  	var qual = isStatic?"<span class=keyword>static</span> ":"";
    var args = m.getElementsByTagName("parameter");
    var sargs = "";
    for (var i=0;i<args.length;i++) {
      var t = args[i].getAttribute("type");
      var index = args[i].getAttribute("index");
      sargs += "p"+index+":"+stringType(t);
      if (args[i].getAttribute("optional")=="true") sargs += "?";
      if (i<args.length-1) sargs+=", ";
    }
    return "<div class='method"+derived+"'>"+qual+displayNs(m)+" <span class=keyword>function</span> "+name+"("+sargs+"):"+stringType(type)+";</div>";
  }

  function displayClass(cl) {
    var code = "";
    var xml = reflect(cl);
    var type= xml.getElementsByTagName("type")[0];
    code = displayModifiers(type);
    code+="<span class=keyword>"+(isInterface(cl)?"interface":"class")+"</span> ";
    code+=shortClassLink(cl);
    if (sups&&sups.length>1) {
      code+=" <span class=keyword>extends</span> " +shortClassLink(sups[sups.length-2]);
    }
    if (interfaces.length>0) {
      code+=" <span class=keyword>implements</span> ";
      var l = interfaces.length;
      for (var i=0;i<l;i++) {
        code += shortClassLink(interfaces[i]);
        if (i<l-1) code+=", ";
      }
    }
    code +=" {<div class=members>";
    // constructors
    var constructor = type.getElementsByTagName("constructor")[0];
    if (constructor) {
      code+="<div class=comment>// Constructor</div>";
      code+=displayConstructor(constructor, type);
    }

	var factory = type.getElementsByTagName("factory")[0];
	
	var accessors = type.getElementsByTagName("accessor");
	if (accessors.length>0) {
		code+="<div class=comment>// Getters and Setters</div>";
		var l = accessors.length;
		for (var i=0;i<l;i++) {
			var el = accessors[i];
			code+=displayAccessor(el, type, (el.parentNode!=factory));
		}
	} 
	var constants = type.getElementsByTagName("constant");
	if (constants.length>0) {
		code+="<div class=comment>// Constants</div>";
		var l = constants.length;
		for (var i=0;i<l;i++) {
			var el = constants[i];
			code+=displayConstant(el, type, (el.parentNode!=factory));
		}
	}
	var variables = type.getElementsByTagName("variable");
	if (variables.length>0) {
		code+="<div class=comment>// Variables</div>";
		var l = variables.length;
		for (var i=0;i<l;i++) {
			var el = variables[i];
			code+=displayVariable(el, type, (el.parentNode!=factory));
		}
	}
	var methods = type.getElementsByTagName("method");
	if (methods.length>0) {
		code+="<div class=comment>// Methods</div>";
		var l = methods.length;
		for (var i=0;i<l;i++) {
			var el = methods[i];
			code+=displayMethod(el, type, (el.parentNode!=factory));
		}
	}
    code +="</div>}";
    return "<div class=class>"+code+"</div>";
  }

  function displayFunction(cl,name) {
    var code = "";
    var xml = reflect(cl);
    var type= xml.getElementsByTagName("type")[0];
    code = displayModifiers(type);
    code+="<span class=keyword>public</span> <span class=keyword>function</span> "+name+"():*;";
  	return "<div class=function>"+code+"</div>";
  }
  function displayObject(cl,name) {
    var code = "";
    var xml = reflect(cl);
    var type= xml.getElementsByTagName("type")[0];
    //code = displayModifiers(type);
    code ="<span class=keyword>public</span> <span class=keyword>const</span> "+name+":"+stringType(type.getAttribute("name"))+" = ";
    code +=" {<div class=members>";

	var accessors = type.getElementsByTagName("accessor");
	if (accessors.length>0) {
		code+="<div class=comment>// Getters and Setters</div>";
		var l = accessors.length;
		for (var i=0;i<l;i++) {
			var el = accessors[i];
			code+=displayAccessor(el, type);
		}
	} 
	var constants = type.getElementsByTagName("constant");
	if (constants.length>0) {
		code+="<div class=comment>// Constants</div>";
		var l = constants.length;
		for (var i=0;i<l;i++) {
			var el = constants[i];
			code+=displayConstant(el, type);
		}
	}
	var variables = type.getElementsByTagName("variable");
	if (variables.length>0) {
		code+="<div class=comment>// Variables</div>";
		var l = variables.length;
		for (var i=0;i<l;i++) {
			var el = variables[i];
			code+=displayVariable(el, type);
		}
	}
	var methods = type.getElementsByTagName("method");
	if (methods.length>0) {
		code+="<div class=comment>// Methods</div>";
		var l = methods.length;
		for (var i=0;i<l;i++) {
			var el = methods[i];
			code+=displayMethod(el, type);
		}
	}

    code +="</div>};";
  	return "<div class=object>"+code+"</div>";
  }

  var p = selectedPackage.innerHTML;
  var fqcn = '';
  if (p=="global") {
  	fqcn = selectedClass.innerHTML;
  } else {
  	fqcn = p+"."+selectedClass.innerHTML;
  }
  if (!inspectCache[fqcn]) {

    var imports = {};
    var namespaceCount = 0;
    var namespaces = {};

    document.getElementById("inside").innerHTML="<div class='loading'>loading...</div>";
    var s = "";
    var p = packages[selectedPackage.getAttribute("index")].p;
    var ec = enumClasses(p);
    var cl = ec.hash[fqcn].r;
    //var c = inspectClass(cl);
    s+="<div>"+selectedPackage.innerHTML+"</div>";

    // shortcut non-reflectable classes out
    if (typeof cl == "string") {
      s+="<h1>"+cl+"</h1>";
      s+="<div class='error'>Class cannot be reflected</div>";
    } else if (isFunction(cl)) { 
      s+="<h1 onclick='doc(\""+fqcn+"\")'>function "+selectedClass.innerHTML+"</h1>";
      var code = displayFunction(cl, selectedClass.innerHTML);
      s+="<code>"+dumpImports()+code+"</div>}</code>";
    } else if (isObject(cl)) {
      s+="<h1 onclick='doc(\""+fqcn+"\")'>object "+selectedClass.innerHTML+"</h1>";
      var code = displayObject(cl, selectedClass.innerHTML);
      s+="<code>"+dumpImports()+code+"</div>}</code>";
    } else {
      // show class hierarchy
      if (!isInterface(cl)) {
        s+="<h1 onclick='doc(\""+fqcn+"\")'>class "+selectedClass.innerHTML+"</h1>";
        s+="<pre>";
        s+="<a class=type href='javascript:show(\"Object\")'>Object</a>\n";
        var sups=getSupers(cl);
        var pad = "  ";
        for (var i=0;i<sups.length;i++) {
          s+= pad+"|\n";
          s+= pad+"+--"+classLink(sups[i])+"\n";
          pad+="     ";
        }
        s+="</pre>"
      } else {
        s+="<h1 onclick='doc(\""+fqcn+"\")'>interface "+selectedClass.innerHTML+"</h1>";
      }
      // show implemented interfaces
      var interfaces = getInterfaces(cl);
      if (interfaces.length>0) {
        s+="<b>Implemented interfaces:</b><blockquote>";
        var l = interfaces.length;
        for (var i=0;i<l;i++) {
          s+=classLink(interfaces[i]);
          if (i<l-1) s+=", ";
        }
        s+="</blockquote>";
      }
      s+="<button id='inherit' onclick='switchDerived()'>Show/Hide inherited members</button>";
      s+="<hr>";
      // class declaration
      var code = displayClass(cl);
      s+="<code>"+dumpImports()+code+"</div>}</code>";
	}
    inspectCache[fqcn] = s;
  }

  document.getElementById("inside").innerHTML=inspectCache[fqcn];
}

function switchDerived() {
  var sheet = document.styleSheets[2];
  var rules = sheet.cssRules || sheet.rules;
  var rule = rules[0];
  var show = (rule.style.display=="none");
  rule.style.display=show?"":"none";
  document.getElementById("inherit").innerHTML=show?"Hide inherited members":"Show inherited members";
}

function show(fullName, dontappend) {
  fullName = fullName.split("[]").join("");
  fullName = fullName.split("::").join(".");
  while (fullName.charAt(0)=='.') fullName=fullName.substr(1); 
  if (!dontappend) {
    location.hash = fullName;
    return;
  }
  if (fullName=="") {
    document.getElementById("inside").innerHTML = savedHome;
  	document.title = "Flash Explorer";
    input_focus();
    return;
  }
  // sanitize fullName since this could come from the URL..
  fullName = fullName.replace(/[^a-z0-9_$.]/ig,'');
  var i = fullName.lastIndexOf(".");
  var p,c;
  if (i==-1) {
  	p = "global";
  	c = fullName;
  } else {
  	p = fullName.substring(0,i);
  	c = fullName.substr(i+1);
  }
  var pelem = document.getElementById(p);
  if (!pelem) {
  	return show('');
  }
  explore(pelem);
  pelem.scrollIntoView();
  var celem = document.getElementById(c);
  if (!celem) {
  	return show('');
  }
  inspect(celem);
  celem.scrollIntoView();
  document.title = fullName+" - Flash Explorer";

  input_focus();
}

function doc(fqcn) {
  //var version = FlashPackages.flash.system.Capabilities.version+"";
  //version = version.split(" ")[1].split(",")[0];
  //var query = "flash "+version + " " + fqcn+" api";
  var query = "inurl:"+(fqcn.split(".").join("/")+" actionscript");
  var url = "http://www.google.com/search?hl=en&q="+query; //+"&btnI=I%27m+Feeling+Lucky"
  open(url, "_blank");
}

HURLANT.jsobject.onReady(load);