var EvalExam2Version = "1.3";
var inputSize=7;

// EVENTS

function myPreventDefault(){
  this.returnValue = false;
}
function myStopPropagation(){
  this.cancelBubble = true;
}
function setEvent(elem,evtype,func) {
  if (elem.addEventListener) {
    elem.addEventListener(evtype,func,false);
  } else {
    elem["on"+evtype] = function (event) {
      event = event || window.event;
      if (! event.preventDefault) {
	event.preventDefault = myPreventDefault;
      }
      if (! event.stopPropagation) {
	event.stopPropagation = myStopPropagation;
      }
      func(event);
    };
  }
}

// OUTPUT
function clearOutput (){
  var div=document.getElementById("output");
  while(div.hasChildNodes()) { 
    div.removeChild(div.firstChild);
  }
  div.scrollTop=0;
}

function output(nodes) {
  var div=document.getElementById("output");
  if(document.getElementById("autoclear").checked) {
    clearOutput();
  }
  for(var i=0;i<nodes.length;i++) {
    //nodes[i].normalize();
    div.appendChild(nodes[i]);
  }
}

function outputText(string) {
  output([makeElem("pre",[makeText(string)]),makeElem("br")]);
}


function resizeInput(n) {
  if (n === undefined) {n=1;}
  inputSize+=n;
  if (inputSize<2) {inputSize=2;}
  var inp = document.getElementById("input");
  inp.rows = inputSize;
  inp.focus();
}


// HISTORY
var result;
var prevHistory=[];
var prevHistoryPtr=0;

function remember(str,val,type,functionp,depthIndex) {
  if (prevHistoryPtr+1<prevHistory.length) {
    prevHistory.length=prevHistoryPtr+1;
  }   
  prevHistory.push([str,val,type,functionp,depthIndex]);
  prevHistoryPtr=prevHistory.length-1;
  result=val;
}

function goHistory(n) {
  if(n===undefined) {n = -1;}
  prevHistoryPtr+=n;
  if (prevHistoryPtr<0) {prevHistoryPtr=0;}
  if (prevHistoryPtr>prevHistory.length-1) {
    prevHistoryPtr=prevHistory.length-1;
  }
  var prev=prevHistory[prevHistoryPtr];
  document.getElementById("input").value=prev[0];
  update(prev[1],prev[2],prev[0]);
  result=prev[1];
  document.getElementById("functionp").checked=prev[3];
  document.getElementById("depth").selectedIndex=prev[4];
  document.getElementById(prev[2]).focus();
}

function getHistory(n) {
  if (n<0 || n>prevHistoryPtr) { n=0; }
  return prevHistory[n];
}

// DOM ELEMENT CREATION FUNCTIONS

function makeElem(tag,contents,attributes) {
  var node = document.createElement(tag);
  if (contents) {
    if ((contents === null || typeof contents != "object") && 
        typeof contents != "function") {
      node.appendChild(document.createTextNode(""+contents));
    }
    if (contents.length) {
      for(var i=0;i<contents.length;i++) {
        node.appendChild(contents[i]);
      }
    }
  }
  if (attributes) {
    for(i=0;i+1<attributes.length;i+=2) {
      node.setAttribute(attributes[i],attributes[i+1]);
      if (attributes[i]=="class") { node.className=attributes[i+1];}
      if (attributes[i].match(/^on/)) { // event-handler needed for IE
	var action=attributes[i+1];
	setEvent(node,attributes[i].substring(2),function(event){
	  if (!Function(action)()) {event.preventDefault();} });
      }
    }
  }
  return node;
}

function makeText(text) {
  return document.createTextNode(text);
}

// DOM Examinator

// returns list of Nodes(div class="childnode")
function examDOMChildren(obj,depth,path) {
  var reslist=[];
  for(var i=0;i<obj.childNodes.length;i++) {
      var edom=examDOMNode(obj.childNodes[i],depth,path+".childNodes["+i+"]");
      var div = makeElem("div",edom,["class","childnodes"]);
      //      div.style.marginLeft="1em";
      reslist.push(div);
  }
  return reslist;
}

// returns list of Nodes (typically Text[TAG]Node[Div:Childnodes]Text[/TAG])
function examDOMNode(obj,depth,path) {
  if (!path) {path="result";}
  if (typeof obj != "object") {throw ("ERROR: Non-object: "+obj);}
  if (!obj.nodeType) {throw "ERROR: Non-DOM node";}
  if (obj._exam_mark) {depth=0;}
  if (obj.nodeType == 3) { //Text Node
    return [makeText("Text["+(obj.data)+"]")];
  }
  if (obj.nodeType == 10) { //DOCTYPE Node
    return [makeText("<!DOCTYPE "+obj.nodeName+" \""+obj.publicId+"\" \""+obj.systemId+"\">")];
  }
  if (obj.nodeType == 9) { // Document node
    return examDOMChildren(obj,depth,path);
  }
  if (obj.nodeType == 1) { // Normal node;
    var tag=obj.tagName;
    var a = makeElem("a",[makeText(tag)],["href",""]);
    setEvent(a,"click", function(event){exploreExpr(path);event.preventDefault();}); 
    if (depth<=0) { 
      return [makeText("<"),
	      a,
	      makeText((obj.id?" ID=\""+obj.id+"\"":"")+
		       " ... > ... </"+tag+">")];
    }
    var reslist = [makeText("<"),a];
    var res="";
    if (obj.attributes) {
      for(i=0;i<obj.attributes.length;i++) {
	if (obj.attributes[i] && obj.getAttribute(obj.attributes[i].name)) {
	  res += " " + obj.attributes[i].name + "=\"" + 
	    (obj.getAttribute(obj.attributes[i].name)) + "\"";
	}
      }
    }
    res+=">";
    reslist.push(makeText(res));
    obj._exam_mark=true;
    try {
      var children = examDOMChildren(obj,depth-1,path);
      reslist=reslist.concat(children);
    } catch (e) {obj._exam_mark=undefined;throw e;}
    obj._exam_mark=undefined;
    reslist.push(makeText("</"+tag+">"));
    return reslist;
  }
  return [makeText("Unknown node type: "+obj.nodeType)];
}


function examDOM(obj,depth,path) {
  if (!path) {path = "result";}
  var reslist = examDOMNode(obj,depth,path);
  
  if (obj.previousSibling) {
    var prevSiblingButton = makeElem("button",
				     "[<< previousSibling]",
				     ["class","navigation","type","button"]);
    setEvent(prevSiblingButton,"click",function(event) {
      exploreExpr(path+".previousSibling");
    });
    reslist = [prevSiblingButton,makeElem("br")].concat(reslist);
  }
  if (obj.parentNode) {
    var parentButton = makeElem("button",
				"[^^ parentNode]",
				["class","navigation","type","button"]);
    setEvent(parentButton,"click",function (event) {
      exploreExpr(path+".parentNode");
    });
    reslist = [parentButton,makeElem("br")].concat(reslist);
  }
  if (obj.nextSibling) {
    var nextSiblingButton = makeElem("button",
				     "[>> nextSibling]",
				     ["class","navigation","type","button"]);
    setEvent(nextSiblingButton,"click",function (event) {
      exploreExpr(path+".nextSibling");
    });
    reslist = reslist.concat([makeElem("br"),nextSiblingButton]);
  }

  return reslist;
}

function HTMLEscape(s) {
  if (typeof s == "string") {
    s=s.replace(/&/g,"&amp;");    
    s=s.replace(/</g,"&lt;");
    s=s.replace(/>/g,"&gt;");
  }
  return s;
}


function bracket(str) {
  if (!(/^\s*\(.*\)\s*$/).test(str)) {return "("+str+")";}
  return str;
}

function examError(error,depth) {
  var errorString="";
  if (error.name) {errorString=error.name;}
  if (error.message) {errorString+=": "+error.message;}
  if (!errorString) {errorString=error.toString();}
  if (depth<=1 && errorString.length>25) {
    errorString=errorString.substr(0,25)+" ...";
  }
  return makeText("<"+(errorString)+">");
}

function examProperty(index,value,depth,path) {
  var examRes;
  try {
    examRes = exam(value,depth-1,path+"[\\'"+index+"\\']");
  } catch (e) {
    examRes = [examError(e,depth)];
  }
  var a = makeElem("a",[makeText(index)],["href",""]);
  setEvent(a,"click", function(event){examExpr("result[\'"+index+"\']");event.preventDefault();}); 
  var td1=makeElem("td",[a]);
  var td2=makeElem("td",examRes);
  var tr=makeElem("tr",[td1,td2]);
  return (tr);
}

function examObj(obj,depth,path) {
  var attrs=[];
  for (var i in obj) {
    if (i == "_exam_mark") {continue;}
    attrs.push(i);
  }
  attrs.sort();
  var nums=false;
  var reslist=[];
  try {
    obj._exam_mark=true; 
  } catch (e) {} // Cross fingers and hope!
  for (i in attrs) {
    var index=attrs[i];
    var typ="";
    if (index == "0") {nums=true;}
    try {
      var val = obj[index];
      typ = "(" + typeof val +")";
      reslist.push(examProperty(index,val,depth,path));
    } catch (e) {
      if (typ) {reslist.push(makeText(typ));}
      reslist.push(makeElem("tr",[makeElem("td",[makeText(index)]),makeElem("td",[examError(e,depth)])]));
    }
   }
  for (i=0;!nums && obj[i] && i<100;i++) {
    reslist.push(examProperty(""+i,obj[i],depth,path));
  }
  try {
    obj._exam_mark=undefined;
  } catch (e){}

  var table=makeElem("table",[makeElem("tbody",reslist)]);
  return table;
}

function asString(val) {
  if (val === undefined) {return "undefined";}
  if (val === null) {return "null";}
  if (val.toString) {
    if (typeof val.toString == "function") {return val.toString();}
    if (typeof val.toString == "string") {return val.toString;} //sanity check
  }
  return "[no string representation]";
}

function exam(val,depth,path) {
  if (!depth) {depth=0;}
  if (!path) {path="result";}
  var typ = "(" + typeof val + ") ";
  var valString = asString(val);
  var reslist = [];
  if ((typeof val == "object")) {
      reslist.push(makeText(typ+valString));
      reslist.push(makeElem("br",[]));
      if (depth == 1) {reslist.push(examObj(val,depth,path));}
      // Objects show content at normal detail level
      // as they have no other content
  } else if ((typeof val) == "string") {
    reslist.push(makeText((typ+"\"" + valString + "\" ")));
  } else if ((typeof val) == "function") {
    if (depth<=0) {
      reslist.push(makeText(typ+"function () { [code] }"));
    } else {
      reslist.push(makeElem("span",[makeText(typ+valString)],["class","code"]));
    }
  } else {
      reslist.push(makeText(typ+valString));
  }
  if (depth>1) {reslist.push(examObj(val,depth,path));}
  return reslist;
}


function exploreExpr(expr) {
  document.getElementById("input").value=expr;
  document.getElementById("explore").click();
}

function examExpr(expr) {
  document.getElementById("input").value=expr;
  document.getElementById("exam").click();
}

function evalExpr(expr) {
  document.getElementById("input").value=expr;
  document.getElementById("eval").click();
}

var evalError=false;

function evaluateExpr(_input,_functionp) {
  if (linkedWindow && !linkedWindow.closed) {
    linkedWindow["_original"] = window;
    _input = "with(linkedWindow){"+_input+"}";   
  }
  if (_functionp) {
    return Function(_input)();
  } else {
    return eval(_input);
  }
}

function handleInput(input,functionp,type,depthIndex,capture) {
   outputText("evaluating expression");
   var prev = getHistory(prevHistoryPtr);
    
   if (type == "insertHTML") {
     val = input;
   } else if (type == prev[2] || input != prev[0]) {
     var val=evaluateExpr(input,functionp); 
   } else {
     val = prev[1];
   }
   update(val,type,input,capture);
   remember(input,val,type,functionp,depthIndex);
}

function parseInput(type) {
  var input = document.getElementById("input").value;
  var depthIndex = document.getElementById("depth").selectedIndex;
  var functionp = document.getElementById("functionp").checked;
  var capture = document.getElementById("errorcap").checked;
  evalError=false;
  if (capture) {
   try { 
     handleInput(input,functionp,type,depthIndex,capture);
   } catch (e) {evalError=true;outputText(e.toString());}
  } else {
     handleInput(input,functionp,type,depthIndex,capture);
  }
}


/* INSERTING CONTENT */
function clearInsert() {
  var insert = document.getElementById("insert");
  while(insert.hasChildNodes()){
    insert.removeChild(insert.firstChild);
  }
  insert.style.border="none";
}

function closeLinkedWindow() {
  if (linkedWindowType == "linkedWindow" && 
      linkedWindow && 
      !linkedWindow.closed) {
    linkedWindow.close();
  }
}

function clearAllContent() {
  clearInsert();
  closeLinkedWindow();
  linkedWindow = undefined;
}

var linkedWindow = undefined;
var linkedWindowType = undefined;


function insertContent(content,type) {
  var typeSel = document.getElementById("linkedwin");
  clearInsert();
  var insert = document.getElementById("insert");
  if (type == "innerHTML") {
    closeLinkedWindow();
    insert.innerHTML = content;
    if (content) {
      insert.style.border = "2px #c0c0c0 ridge";
    }
  } else {
    if (type=="iframe") {
      closeLinkedWindow();

      var ifr = document.createElement("iframe");
      ifr.style.width = "100%";
      insert.style.border = "2px #c0c0c0 ridge";
      insert.appendChild(ifr); 
      ifr.src="";

      if (ifr.contentWindow) {
        linkedWindow = ifr.contentWindow;
      } else if (ifr.contentDocument) {
        linkedWindow = undefined;
        var doc = ifr.contentDocument;
        doc.open();
        doc.write("<script type='text/javascript'>parent.linkedWindow=window</script>");
        doc.close();
      }
    } else { // type = linked window;
      clearInsert();
      linkedWindow = window.open("","EvalExamLinkedWindow");
      window.focus();
    }
    if (linkedWindow) {
      var ldoc = linkedWindow.document;
      ldoc.open();
      ldoc.write(content);
      ldoc.close();
    }
  }
  linkedWindowType=type;      
}

function doUpdate(val,type,input,depth) {
  var out;
  if (type == "eval") {
    out = [makeElem("pre",[makeText(val)])];
  } else if (type == "exam") {
    outputText("examining value");
    out = exam(val,depth);
  } else if (type == "explore") {
    outputText("examining DOM structure");
    out = examDOM(val,depth);
  } else if (type == "insertHTML") {
    var linkSel = document.getElementById("linkedwin");
    var linType = linkSel.options[linkSel.selectedIndex].value;
    insertContent(val,linType);
    out=[];
  } else {
    out = "ERROR: Unknown operation";
  }
  return out;
}

function update(val,type,input,capture) {
  var depth=1;
  var depthin=document.getElementById("depth");
  depth=+depthin.options[depthin.selectedIndex].value;
  var out;
  if (capture) {
    try {
      out = doUpdate(val,type,input,depth);   
    } catch (e) {
      evalError=true;
      out=[makeElem("pre",[makeText(e.toString())])];
    }
  } else {
    out = doUpdate(val,type,input,depth);   
  }
  output(out);  
}


function init(){
  remember("null",null,"eval",false,1);
}
