/********************************************************************
	Obsessing.org : obsessing_ide.js
	Copyright 2008 Aubrey Anderson / Particle Programmatica, Inc
	http://particlewebsite.com | aubrey [at] particularplace [dot] com
	please be cool.
********************************************************************/

// dojo's lovely key code array.
var keys = {
	BACKSPACE: 8,
	TAB: 9,
	CLEAR: 12,
	ENTER: 13,
	SHIFT: 16,
	CTRL: 17,
	ALT: 18,
	PAUSE: 19,
	CAPS_LOCK: 20,
	ESCAPE: 27,
	SPACE: 32,
	PAGE_UP: 33,
	PAGE_DOWN: 34,
	END: 35,
	HOME: 36,
	LEFT_ARROW: 37,
	UP_ARROW: 38,
	RIGHT_ARROW: 39,
	DOWN_ARROW: 40,
	INSERT: 45,
	DELETE: 46,
	HELP: 47,
	LEFT_WINDOW: 91,
	RIGHT_WINDOW: 92,
	SELECT: 93,
	NUMPAD_0: 96,
	NUMPAD_1: 97,
	NUMPAD_2: 98,
	NUMPAD_3: 99,
	NUMPAD_4: 100,
	NUMPAD_5: 101,
	NUMPAD_6: 102,
	NUMPAD_7: 103,
	NUMPAD_8: 104,
	NUMPAD_9: 105,
	NUMPAD_MULTIPLY: 106,
	NUMPAD_PLUS: 107,
	NUMPAD_ENTER: 108,
	NUMPAD_MINUS: 109,
	NUMPAD_PERIOD: 110,
	NUMPAD_DIVIDE: 111,
	F1: 112,
	F2: 113,
	F3: 114,
	F4: 115,
	F5: 116,
	F6: 117,
	F7: 118,
	F8: 119,
	F9: 120,
	F10: 121,
	F11: 122,
	F12: 123,
	F13: 124,
	F14: 125,
	F15: 126,
	NUM_LOCK: 144,
	SCROLL_LOCK: 145
};

var Sketch = function() {
	this.name = "Unnamed Sketch";
	this.modDate = new Date().getTime();
	this.editorText = "";
};	

var ObsessingIDE = function() {
  	
   	this.cursorLine = 1;
		this.cursorPosition = 0;
		this.cursorLeftOffset = 54;
		this.cursorTopOffset = -11;
		this.fontWidth = 7.2;
		this.fontHeight = 16;
		this.tabSpacing = 2;
		this.editorText = "";
		this.insertPoint = 0;
		this.cursorLine = 0;
		this.renderEngine = "canvas";
		
		this.sketches = new Array(new Sketch());
		this.currentSketch = 0;
		
		var ob = this;
		this.cursorAnimation = setInterval(function(){ ob.cursorIntervalCallback(); }, 500);
		//this.updateStatus("hi there");
		
		__ObsessingEnvironment = "development";
		
		//this.relocateCursor();
		
		this.captureKeys = true;
		
		var ideRef = this;	
		
		$(window).keypress(function(evt) {
			
			if (ideRef.captureKeys) {
				
				// for charCode, I seem to only get a value on an ASCII char, i get 0 for return, delete, etc, so:
				var kCode = evt.charCode || evt.keyCode;
			
				//trace( evt.charCode );
				evt.stopPropagation();
				evt.preventDefault(); // oh webkit.
				ob.updateEditor(kCode, evt.keyCode);
			}
			//return false;
		});
		
		$("#editor").click(function(evt) {
			
			//trace('click cursor locate to line: ' + Math.round(evt.layerY / 15));
			
			if (ob.cursorLine > 1) {
				
				var clickLine = Math.round(evt.layerY / 16); // make the line height dynamic?
				if (clickLine < 1) clickLine = 1;
				var clickOffset = Math.round(evt.layerX / 7.28);
				
				var lines = ob.editorText.split("\n"); // losing the \n char in the val
				ob.cursorLine = Math.min(lines.length, clickLine); // can't locate past last line
				
				var currentLine = ob.cursorLine - 1; // index in the line array
				var charactersToMove = 1;
				
				//trace('lines: ' + ob.cursorLine);
				
				ob.cursorPosition = Math.min(lines[currentLine].length, clickOffset);
				
				
				trace("pos:" + ob.findCursor(ob.cursorLine, ob.cursorPosition));
				ob.insertPoint = ob.findCursor(ob.cursorLine, ob.cursorPosition);
				
				ob.relocateCursor();
			}
			
		});	       
  
  
  this.findCursor = function(line, linePos) {
  	var pos = 0;
  	var lines = this.editorText.split("\n");
  	for (i=0; i<line-1; i++) {
 			pos += (lines[i].length + 1); // add one for the newline we've split 	  	
  	}
  	pos += linePos;
  	return pos;
  };
  
  this.renderDefaultText = function() {

		
		this.updateEditor("\n");
		this.updateEditor("/*");
		this.updateEditor("\n");
		this.updateEditor("Welcome to Obsessing!");
		this.updateEditor("\n");
		this.updateEditor("Obsessing is all about bringing the Processing API/experience into the browser.");
		this.updateEditor("\n");
		this.updateEditor("Obsessing is VERY ALPHA and activly being developed, a lot of things are still seriously rough.");
		this.updateEditor("\n");
		this.updateEditor("Click that little info button in the top right for more....");
		this.updateEditor("\n");
		this.updateEditor("\n");
		this.updateEditor("to make anything happen you'll need to implement");
		this.updateEditor("\n");
		this.updateEditor("void draw()");
		this.updateEditor("\n");
		this.updateEditor("You can also implement");
		this.updateEditor("\n");
		this.updateEditor("void setup()");
		this.updateEditor("\n");
		this.updateEditor("for anything you need before animation starts....");
		this.updateEditor("\n");
		this.updateEditor("Here's a little test example:");
		this.updateEditor("\n");
		this.updateEditor("*/");
		this.updateEditor("\n");
		this.updateEditor("\n"); 
		
		this.updateEditor("PGraphics pg;"); 
		this.updateEditor("\n");
		this.updateEditor("\n");
		this.updateEditor("void setup() {"); 
		this.updateEditor("\n");
  	this.updateEditor("  size(200, 200);"); 
  	this.updateEditor("\n");
  	this.updateEditor("  pg = createGraphics(80, 80, P3D);"); 
  	this.updateEditor("\n");
		this.updateEditor("}"); 
		this.updateEditor("\n");
		this.updateEditor("\n");
		this.updateEditor("void draw() {"); 
		this.updateEditor("\n");
  	this.updateEditor("  fill(0, 12);"); 
  	this.updateEditor("\n");
  	this.updateEditor("  rect(0, 0, width, height);"); 
  	this.updateEditor("\n");
  	this.updateEditor("  fill(255);"); 
  	this.updateEditor("\n");
  	this.updateEditor("  noStroke();"); 
  	this.updateEditor("\n");
  	this.updateEditor("  ellipse(mouseX, mouseY, 60, 60);"); 
  	this.updateEditor("\n");
  	this.updateEditor("\n");
  	this.updateEditor("  pg.beginDraw();"); 
  	this.updateEditor("\n");
  	this.updateEditor("  pg.background(102);"); 
  	this.updateEditor("\n");
  	this.updateEditor("  pg.noFill();"); 
  	this.updateEditor("\n");
  	this.updateEditor("  pg.stroke(255);"); 
  	this.updateEditor("\n");
  	this.updateEditor("  pg.ellipse(mouseX-60, mouseY-60, 60, 60);"); 
  	this.updateEditor("\n");
  	this.updateEditor("  pg.endDraw();"); 
  	this.updateEditor("\n");
  	this.updateEditor("\n");
  
  	this.updateEditor("  image(pg, 60, 60); "); 
  	this.updateEditor("\n");
		this.updateEditor("}"); 
		this.updateEditor("\n");
		this.updateEditor("\n");
		
		
  
  };
  

	this.shouldEval = false;
	this.CODE_OK = 1;
	this.CODE_ERROR = 2;
	this.CODE_WARN = 3;
	this.criteriaString = "";
	this.editorText = "";
	this.defaultText = false;

	this.cursorIntervalCallback = function() {
		$('#cursor').toggle();
		
		// here's a test script eval on interval
		// should pop a warning if you leave an error for
		// more than 1000 milis
		if (this.shouldEval && ! this.codeCompleteIsShowing)
		{
			try
			{
				eval(this.editorText);
				this.updateStatus(this.CODE_OK);
				
			}
			catch(evalError)
			{
				this.updateStatus(this.CODE_ERROR, evalError);
			}
			
			this.shouldEval = false;
		}
		else this.shouldEval = true;
		
		if (! this.defaultText) {
			//this.renderDefaultText();
			//this.updateEditor("LOADING....");
			this.defaultText = true;
		}
		
	};

	this.relocateCursor = function() {
		$('#cursor').css("left", (this.cursorPosition * this.fontWidth) + this.cursorLeftOffset);
		$('#cursor').css("top", (this.cursorLine * this.fontHeight) + this.cursorTopOffset);
		
		$('#lines').empty();
	
		var lines = this.editorText.split("\n");
		for(var i=0; i<lines.length; i++)
		{
			$('#lines').append((i+1) + "<br />");
		}
		
		$('#line_number').empty();
		$('#line_number').append(this.cursorLine);
		
		$('#column_number').empty();
		$('#column_number').append(this.cursorPosition + 1);
			
	};
	
	this.clearEditor = function() {
		this.editorText = "";
		this.cursorLine = 1; // index in the line array
		this.cursorPosition = 0;
		this.insertPoint = this.findCursor(this.cursorLine, this.cursorPosition);
		this.relocateCursor();
		
		this.codeCompleteIsShowing = false;		
		
		$('#editor').empty();
		
	};

	// we'll get either the key or the char or a String for k
	// should probably branch on typeof just to be clear
	this.updateEditor = function(k, specialKey) {
	
		if ((k == keys.BACKSPACE || k == keys.DELETE) && specialKey) 
		{
			
			//trace('del')
			
			// hide code completion
			// TO DO: hook here for delete in paired down code complete list
			if (this.codeCompleteIsShowing)
			{
				$('#codeComplete').fadeOut(300);
				//$('#codeComplete').css("visibility", "hidden");
				this.codeCompleteIsShowing = false;
				this.criteriaString = "";
			}
			
			// handle wrap backwards
			if (this.cursorPosition < 1)
			{
				if (this.cursorLine > 1) 
				{
					
					this.deleteText();
					var lines = this.editorText.split("\n");
					this.cursorLine --;
					this.insertPoint --;
					this.cursorPosition = lines[this.cursorLine-1].length;
					//this.cursorPosition = lines[lines.length - 2].length + 1;
					
					
				}
				else
				{
					this.cursorPosition = 0;
					this.insertPoint = 0;
				}
			}
			
			else if (this.editorText.length > 0)
			{
				this.deleteText();
				this.cursorPosition --;
				this.insertPoint --;
			}
			else 
			{
				this.cursorLine = 1;
				this.cursorPosition = 0;
				this.insertPoint = 0;
			}
		}
		
		else if (k == keys.ESCAPE) 
		{
	
				//trace('esc')
				// hide code completion
				if (this.codeCompleteIsShowing)
				{
					$('#codeComplete').fadeOut(300);
					//$('#codeComplete').css("visibility", "hidden");
					this.codeCompleteIsShowing = false;
					this.criteriaString = "";
				}
		}
		
		else if (k == keys.ENTER)
		{
		
			//trace('enter')
			//this.cursorLine ++;
			//this.cursorPosition = -1;
			//this.editorText += k;
			
			// there's a bug when the first char is a new line so as a temp fix:
			//if (this.editorText.length < 1) 
			//{
			//	this.insertText(".\n");
			//}
			//else 
			//{
				this.cursorLine ++;
				this.cursorPosition = 0;
				
				this.insertText("\n");
				this.insertPoint ++;
			
			//}
		}
		
		else if (k == keys.RIGHT_ARROW && specialKey)
		{
			trace('right');
			
			var lines = this.editorText.split("\n");
			if (this.cursorPosition < lines[this.cursorLine-1].length) this.cursorPosition++; 
			
			this.insertPoint = this.findCursor(this.cursorLine, this.cursorPosition);
			this.relocateCursor();
			
			/*
			if (this.nextIsNewLine()) 
			{	
				this.cursorLine ++;
				this.cursorPosition = 0;
				this.insertPoint ++;
			}
			else
			{
				if (this.insertPoint < this.editorText.length)
				{
					this.cursorPosition ++;
					this.insertPoint ++;
				}
			}
			
			*/
		}
		else if (k == keys.LEFT_ARROW && specialKey)
		{
			
			trace('left');
			if (this.cursorPosition > 0) this.cursorPosition--;
			else this.cursorPosition = 0;
			this.insertPoint = this.findCursor(this.cursorLine, this.cursorPosition);
			this.relocateCursor();
			
			/*
			if (this.lastIsNewLine()) 
			{
				this.cursorLine --;
				this.insertPoint --;	
				var lines = this.editorText.split("\n");
				this.cursorPosition = lines[this.cursorLine-1].length;
			}
			else
			{
				if (this.cursorPosition > 0)
				{
					this.cursorPosition --;
					this.insertPoint --;
				}
			}
			
			*/
			
		}
		else if (k == keys.UP_ARROW && specialKey)
		{
			trace('up');
			
			if (this.cursorLine > 1)
			{
				this.cursorLine --;
				var lines = this.editorText.split("\n"); // losing the \n char in the val
				var currentLine = this.cursorLine - 1; // index in the line array
				var charactersToMove = 1;
				
				// lock to 0 of newline for now
				charactersToMove += this.cursorPosition;
				//this.insertPoint -= charactersToMove;
				if (lines[currentLine].length < this.cursorPosition) this.cursorPosition = lines[currentLine].length;
				
				this.insertPoint = this.findCursor(this.cursorLine, this.cursorPosition);
				
			}
		}
		else if (k == keys.DOWN_ARROW && specialKey)
		{
			
			trace('down');
			
			if (this.codeCompleteIsShowing) { // TBD!!
				// try to find the selected node and highlight?
				this.selectedCCItem ++;
				//trace($('#codeComplete').children())
				//.each(function() {
				//	this.addClass('ccOptionSelected');
				//});
				
			}
			
			else {
				
				trace(k);
				if (this.editorText.indexOf("\n", this.insertPoint) != -1) // i.e. there is another line
				{
					this.cursorLine ++;
					
					var lines = this.editorText.split("\n"); // kills the actual \n char in the val
					var currentLine = this.cursorLine - 1;
					var charactersToMove = 1; // 1 char for the new line
					// lock to 0 of newline for now
					charactersToMove += lines[currentLine-1].length - this.cursorPosition;
					//this.insertPoint += charactersToMove;
					//this.cursorPosition = 0;
					if (lines[currentLine].length < this.cursorPosition) this.cursorPosition = lines[currentLine].length;
					
					this.insertPoint = this.findCursor(this.cursorLine, this.cursorPosition);
				
				}
			}
		
		}
		
		else if (k == keys.TAB) {		
			var spaces = "";
			
			for (i=0;i<this.tabSpacing;i++) 
			{
				this.insertText(" ");
				this.insertPoint ++;
				this.cursorPosition ++;
			}
		}
		
		else if (typeof(k) == "number" && String.fromCharCode(k) == ".") {
			
			trace("dot")
			//this.editorText += k;
			this.insertText('.');
			this.cursorPosition ++;
			this.insertPoint ++;
			
			// TODO: there is a bug when the dot is in the middle of a line
			// arrow key support is still half-assed....
			
			var lines = this.editorText.split("\n");
			var currentLine = lines[this.cursorLine - 1];
			var lastString = currentLine.substring(0, this.cursorPosition - 1);
			lastString = lastString.substring(lastString.lastIndexOf(" "));
			//lastString = lastString.replace( /^\s+/g, "" ).replace( /\s+$/g, "" );
	
			this.codeComplete(lastString);
			
		}
		
		else {
			
			if (typeof(k) == "string" && k.indexOf("\n") > -1) {
				this.insertText(k);
				var lines = this.editorText.split("\n");			
				this.cursorLine = lines.length-1;
				this.cursorLine ++;
				this.insertPoint += k.length;
				this.cursorPosition = lines[this.cursorLine-1].length;
				
			}
			else {
				if (typeof(k) == "number") k = String.fromCharCode(k);
				this.insertText(k);
				this.cursorPosition += k.length;
				this.insertPoint += k.length;
			}
				
		}
		
		this.relocateCursor();
		
		// pair down code complete if we have it
		if (this.codeCompleteIsShowing && String.fromCharCode(k) != ".") {
			this.criteriaString += k;
			this.codeCompleteCriteria(this.criteriaString);
		}
		
	
		
		// re-rendererer
		$('#editor').empty();
		
		if (this.editorText.length > 0) {
			var htmlSaveText = this.editorText.replace(/\</g,"&lt;").replace(/\>/g,"&gt;");
			$('#editor').append(htmlSaveText).chili();
			
			
			//var reElClass = new RegExp( "\\b" + ChiliBook.elementClass + "\\b", "gi" );
			//$( ChiliBook.elementPath ).each( function() {
			//	var el = this;
			//	var recipe = ChiliBook.recipe( 'javascript' );
			//	makeDish( el, 'javascript' );
			//
			//} );
		}
		
	};


	// TO DO: convert these next 2 to regex
	this.nextIsNewLine = function() {
		var nextChar = this.editorText.substring(this.insertPoint, this.insertPoint + 1);
		if (nextChar == "\n") return true;
		else return false;
	};

	this.lastIsNewLine = function() {
		var lastChar = this.editorText.substring(this.insertPoint-1, this.insertPoint);
		if (lastChar == "\n") return true;
		else return false;
	};

	this.insertText = function(txt) {
		var t1 = this.editorText.substring(0, this.insertPoint);
		var t2 = this.editorText.substring(this.insertPoint, this.editorText.length);
		this.editorText = t1 + txt + t2;
		
	};

	this.deleteText = function()	{
		var t1 = this.editorText.substring(0, this.insertPoint-1);
		var t2 = this.editorText.substring(this.insertPoint, this.editorText.length);
		this.editorText = t1 + t2;
	};

	this.codeCompleteIsShowing = false;
  this.selectedCCItem = 0;
	this.methodArray = new Array();

	this.codeComplete = function(objectName) {
		trace("cc getting called");
		
		// we ideally need a way to evaluate written code on the page
		// as we go, probably wrap that up into a live interpreter
		// which is going at an interval?
	
		this.codeCompleteIsShowing = true;
		$('#codeComplete').empty();
	
		var objectRef = null;
		try { 
			objectRef = eval(objectName); 
			//trace(typeof(objectRef));
			//trace(objectRef);	
		}
		//try { objectRef = objectName; }
		catch(er) { 
			// shouldn't default to string though
			objectRef = ""; 
			trace("couldn't eval for code complete");
		}  
	
		this.methodArray = new Array();
	
		// this seems to be looping and or evaling down for each
		// object... curious
		//this.methodArray.push("AAAcompleting for: " + objectRef);
	
		for (x in objectRef) {
			var cc = x;
			var cct = typeof(objectRef[x]);
	
			if (cct == "function") cc += "()";
			else cc += " - " + cct;
			
			if (! cc.indexOf("_") == 0)
			this.methodArray.push(cc);
		}
	
		this.methodArray.sort();
	
		var ideRef = this;
		
		for (i=0; i<this.methodArray.length; i++) {
			var d = document.createElement('div');
			d.className = "ccOption";
			d.id = this.methodArray[i];
			d.innerHTML = this.methodArray[i];
			d.onclick = function(evt) { 
				
				var textToAdd = this.innerHTML.split(" - ")[0];
				
				//this.cursorPosition += textToAdd.length - 1;
				//updateEditor("");
				$('#codeComplete').fadeOut(300, function() { ideRef.updateEditor(textToAdd); });
				ideRef.codeCompleteIsShowing = false;
				
			};
			
			if (d) $('#codeComplete').append(d);
		
		}
		
		// position the completion box
		$('#codeComplete').css("left", (this.cursorPosition * this.fontWidth) + this.cursorLeftOffset);
		$('#codeComplete').css("top", (this.cursorLine * this.fontHeight) + this.cursorTopOffset);
		
		$('#codeComplete').css("visibility", "visible");
		$('#codeComplete').fadeIn(300);
		
	};

	this.codeCompleteCriteria = function(criteria) {
		$('#codeComplete').empty();
		
		var ideRef = this;
		
		for (i=0; i<this.methodArray.length; i++)
		{
			if (this.methodArray[i].indexOf(criteria) == 0)
			{
				var d = document.createElement('div');
				d.className = "ccOption";
				d.id = this.methodArray[i];
				d.innerHTML = this.methodArray[i];
				d.onclick = function(evt) { 
				
					var textToAdd = this.innerHTML.split(" - ")[0];
	
					//this.cursorPosition += textToAdd.length - 1;
					//updateEditor("");
					textToAdd = textToAdd.substring(criteria.length);
					
					$('#codeComplete').fadeOut(300, function() { ideRef.updateEditor(textToAdd); });
					ideRef.codeCompleteIsShowing = false;
				
				};
			
				if (d) $('#codeComplete').append(d);
			}
		}
	};

	this.updateStatus = function(status, err) {		
		if (status == this.CODE_OK) {
			$(".code-ok").show();
			$(".code-error").hide();
			$(".status-txt").empty().append("Code Ok");
		}
		else if (status == this.CODE_ERROR) {
			$(".code-ok").hide();
			$(".code-error").show();
			$(".status-txt").empty().append(err.message);
		}
	};

	// potential button handlers
	this.importContent = function() {
		var impContainer = document.createElement("div");
		$(impContainer).css("position","absolute");
		$(impContainer).css("width","430px");
		$(impContainer).css("height","460px");
		$(impContainer).css("left","150px");
		$(impContainer).css("top","100px");
		$(impContainer).css("background","#505973");
		
		var imp = document.createElement("textarea");
		imp.rows = "40";
		imp.cols = "70";
		
		$(imp).css("position","absolute");
		$(imp).css("width","400px");
		$(imp).css("height","400px");
		$(imp).css("left","10px");
		$(imp).css("top","10px");
		
		var ok = document.createElement("div");
		ok.innerHTML = "import";
		ok.onclick = function() {
			//this.editorText = imp.value;
			$(impContainer).remove();	
			this.updateEditor(imp.value);
			
			this.updateEditor("");
			
		}
		$(ok).css("position","relative");
		$(ok).css("left","10px");
		$(ok).css("top","430px");
		$(ok).css("width","40px");
		$(ok).addClass("genericButton");
		
		var canc = document.createElement("div");
		canc.innerHTML = "cancel";
		canc.onclick = function() {
			$(impContainer).remove();
		}
		$(canc).css("position","relative");
		$(canc).css("left","70px");
		$(canc).css("top","412px");
		$(canc).css("width","40px");
		$(canc).addClass("genericButton");
		
		$(impContainer).append(imp);
		$(impContainer).append(ok);
		$(impContainer).append(canc);
		$("#editorContainer").append(impContainer);
		imp.focus();
		
		
	};

	this.saveSketch = function() {
		
		try {
			this.updateStatus("SAVED");
		}
		catch(exp) {
			trace(exp);
		}
		
		// some UI fedback
		
	};
	
	this.getCode = function() {
		return this.editorText;
	};

	this.saveSkatchAs = function() {
		this.updateStatus("Save As");
	};

	this.newSketch = function() {
		this.updateStatus("NEW");
	};

	this.openSketch = function(code) {
		//this.updateStatus("OPEN");
		this.clearEditor()
		this.updateEditor(code);
	},

	this.previewSketch = function() {
		
		var exportPage = window.open("/build", "sketchExportWindow", 
			"toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=1,width=650,height=650,left=200,top=100");
		//var exportPage = window.open("export.html", "ref", "toolbar=1");
		var t = this.editorText;
		$("#exportSketchCode").attr("value", t);
		$("#exportSketchForm").submit();
		exportPage.focus();
		
	};
  

};



