﻿// What		: AS3 Developer Console	(v1.01);
// Author	: Corey Zeke (Torrunt) Womack

// Type 'help' in console for help.
// If your testing your project in the Flash IDE make sure 'Disable Keyboard Shortcuts' is ticked.
// When testing your project through the flash IDE you have to click the swf at least once to be able to see the text cursor.

package classes {
	import flash.display.Sprite;
	import flash.text.*;
	import flash.events.KeyboardEvent;
	import flash.events.TextEvent;
	import flash.utils.describeType;
	
	public class developerconsole extends Sprite {
		
		// Customisation
		public var maxSuggestions:Number = 14;
		public var showTypes:Boolean = true;
		public var returnFunctions:Boolean = true;
		
		public var help:String = " - Type 'clear' to clear the console.\n - Type 'credits' to see the credits\n - Use the Up/Down arrow keys to go through your previous used commands or suggestions\n - Use Quotations when you want enter string literal with spaces (\")\n - Use Square Brackets when you want to assign an array with an array literal or use an array literal as a parameter (e.g:[0,1]).\n - Calculations are allowed when assigning or in parameters (+,-,*,/,%). BIMDAS is not supported.\n - Use pgUp and pgDown on your keyboard to scroll up and down";
		public var credits:String = "Torrunt's AS3 Developer Console was programmed by Corey Zeke (Torrunt) Womack\ntorrunt.newgrounds.com\ntorrunt.wordpress.com";
		
		// Creating + Defaults
		var main;
		public var suggesttext:TextField;
		public var consoleTextFormat:TextFormat;
		public var historytext:TextField;
		public var inputtext:TextField;
		
		public var cmdsuggest:Array = new Array();
		public var cmdhistory:Array = new Array();
		var cicle:Array;
		var hpos:Number = -1;
		
		public var opened:Boolean = false;
		
		// Constructor
		function developerconsole(main_set){
			main = main_set;	// Start off point for seeing/using variables and functions
			
			// Customise Look
				// Text Format
			consoleTextFormat = new TextFormat();
			consoleTextFormat.size = 14;
			consoleTextFormat.font = "Arial";
			consoleTextFormat.color = 0xFFFFFF;
			
				// History Textbox
			historytext = new TextField();
			addChild(historytext);
			
			historytext.width = main.stage.stageWidth;
			historytext.height = 85;
			historytext.alpha = 0.9;
			historytext.selectable = false;
			historytext.multiline = true;
			historytext.wordWrap = true;
			historytext.defaultTextFormat = consoleTextFormat;
			historytext.background = true;
			historytext.backgroundColor = 0x000000;
			
				// Input Textbox
			inputtext = new TextField();
			inputtext.type = TextFieldType.INPUT;
			addChild(inputtext);
			
			inputtext.width = main.stage.stageWidth;
			inputtext.height = 20;
			inputtext.y = 85;
			inputtext.x = 0;
			inputtext.alpha = 0.8;
			inputtext.defaultTextFormat = consoleTextFormat;
			inputtext.background = true;
			inputtext.backgroundColor = 0x000000;
			
				// Suggest/auto-complete Textbox
			suggesttext = new TextField();
			addChild(suggesttext);
			
			suggesttext.width = 150;
			suggesttext.height = 20;
			suggesttext.y = 105;
			suggesttext.alpha = 0.85;
			suggesttext.selectable = false;
			suggesttext.defaultTextFormat = consoleTextFormat;
			suggesttext.visible = false;
			suggesttext.background = true;
			suggesttext.backgroundColor = 0x000000;
			suggesttext.autoSize = TextFieldAutoSize.LEFT;
			
			// Startup Message
			echo("Welcome to Torrunt's AS3 Developer Console");
			
			visible = false;	// Hide self
        }
		
		//////////////////////////
		//		Open/Close		//
		/////////////////////////
		
		public function open():void {
			visible = true;
			opened = true;
			main.stage.focus = inputtext;
			
			main.parent.addEventListener(KeyboardEvent.KEY_DOWN, keydown);
			inputtext.addEventListener(TextEvent.TEXT_INPUT,typed);
		}
		
		public function close():void {
			visible = false;
			opened = false;
			inputtext.text = "";
			hpos = -1;
			
			main.parent.removeEventListener(KeyboardEvent.KEY_DOWN, keydown);
			inputtext.removeEventListener(TextEvent.TEXT_INPUT,typed);
		}
		
		//////////////////////////
		//	  Misc Functions	//
		/////////////////////////
		
			// Messages
		public function echo(str, colour:String = "FFFFFF"):void {
			historytext.htmlText = historytext.htmlText + "<font color=\"#"+ colour +"\">" + str + "</font>\n";
			historytext.scrollV = historytext.maxScrollV;
		}
		public function error(str):void {
			echo(str,"FF0000");
		}
		
			// Common String related functions
		public function stringReplaceAll(str:String, r:String, rw:String = ""):String {
			do {
				str = str.replace(r,rw);
			}
			while(str.search(r) != -1);
			
			return str;
		}
		
		public function stringReplaceButExclude(str:String, r:String, exl:String, rw:String = ""):String {
			// Replaces all 'r's in a string with 'rw' excluding 'r's inside 'exl's
			if (str.indexOf(exl) > -1){
				var s = 0;
				var temp1;
				var temp2;
				var temp3;
				while (s > -1){
					temp1 = str.slice(0,s);
					temp2 = str.slice(s,str.indexOf(exl));
					temp3 = str.slice(str.indexOf(exl),str.length);
					temp2 = stringReplaceAll(temp2, r, rw);
					str = temp1 + temp2 + temp3;
					str = str.replace(exl,"");
					s = str.indexOf(exl);
					str = str.replace(exl,"");
				}
			} else {
				str = stringReplaceAll(str, r, rw);
			}
			return str;
		}
		
		//////////////////////////
		//		Controls		//
		/////////////////////////
		
		function keydown(e:KeyboardEvent){
			// Enter
			if (e.keyCode == 13 && inputtext.text != ""){
				echo(inputtext.text);
				
				if (cmdhistory[cmdhistory.length-1] != inputtext.text){
					cmdhistory.push(inputtext.text);
					hpos = -1;
				}
				
				interpret(inputtext.text);
				
				inputtext.text = "";
				hidesuggestions();
			}
			
			// Backspace
			if (e.keyCode == 8){
				if(inputtext.length-1 <= 0){
					hidesuggestions();
				} else {
					showsuggestions(inputtext.text.substr(0,inputtext.length-1));
				}
			}
			
			// Up and Down
				// Pick array to go through
			if (suggesttext.visible){
				cicle = cmdsuggest;
			} else {
				cicle = cmdhistory;
			}
				// Up
			if (e.keyCode == 38 && cicle[cicle.length-1-(hpos+1)] != null){
				hpos++;
				if (cicle == cmdsuggest && inputtext.text.indexOf(".") > -1){
					inputtext.text = inputtext.text.substr(0,inputtext.text.lastIndexOf(".")+1); // Remove text after last '.'
					inputtext.appendText(cicle[cicle.length-1-hpos]); // Appened suggestion
				} else {
					inputtext.text = cicle[cicle.length-1-hpos]; // Replace text
				}
				inputtext.setSelection(inputtext.length,inputtext.length);
			}
				// Down
			if (e.keyCode == 40){
				if (cicle[cicle.length-1-(hpos-1)] != null){
					hpos--;
					if (cicle == cmdsuggest && inputtext.text.indexOf(".") > -1){
						inputtext.text = inputtext.text.substr(0,inputtext.text.lastIndexOf(".")+1); // Remove text after last '.'
						inputtext.appendText(cicle[cicle.length-1-hpos]); // Appened suggestion
					} else {
						inputtext.text = cicle[cicle.length-1-hpos]; // Replace text
					}
					inputtext.setSelection(inputtext.length,inputtext.length);
				} else if (cicle == cmdhistory) {
					inputtext.text = "";
					hpos = -1;
				}
			}
			
			// Scrolling Up and Down
				// Page Up
			if (e.keyCode == 33){
				historytext.scrollV--;
			}
				// Page Down
			if (e.keyCode == 34){
				historytext.scrollV++;
			}
		}
		
		//////////////////////////
		//		Suggesting		//
		/////////////////////////
		
		// Defaulting
		var areSuggestions:Boolean = false;
		var hitMax:Boolean = false;
		
		// Update while typing
		function typed(e:TextEvent){
			showsuggestions(inputtext.text+e.text);
		}
		
		function showsuggestions(str:String){
			// reset to defaults
			hidesuggestions();	// hide previous
			areSuggestions = false;
			hitMax = false;
			
			// If still writing var/func name
			if (inputtext.text.indexOf("=") == -1 && inputtext.text.indexOf("(") == -1){
				try {
					var ob = main;
					var stre = str;
					
					if (str.indexOf(".") > -1){
						var o:Array = str.split(".");
						for (var i:Number = 0; i < o.length-1; i++){
							ob = ob[o[i]];
						}
						stre = o[o.length-1];
					}
					
					var description:XML = describeType(ob);
					
					var type = "";
					
					// Vars
					for each (var v:XML in description.variable){
						if (suggesttext.numLines < maxSuggestions){
							if(v.@name.indexOf(stre) == 0){
								if (showTypes) type = ":" + v.@type;
								
								cmdsuggest.push(v.@name);
								suggesttext.appendText(v.@name + type + "\n");
								areSuggestions = true;
							}
						} else {
							hitMax = true;
							break;
						}
					}
					// Accessors
					for each (var a:XML in description.accessor){
						if (suggesttext.numLines < maxSuggestions){
							if(a.@name.indexOf(stre) == 0){
								if (showTypes) type = ":" + a.@type;
								
								cmdsuggest.push(a.@name);
								suggesttext.appendText(a.@name + type + " (accessor)\n");
								areSuggestions = true;
							}
						} else {
							hitMax = true;
							break;
						}
					}
					// Methods
					for each (var m:XML in description.method){
						if (suggesttext.numLines < maxSuggestions){
							if(m.@name.indexOf(stre) == 0){
								if (showTypes) type = ":" + m.@returnType;
								
								suggesttext.appendText(m.@name + "(");
								areSuggestions = true;
								// Parameters
								if (m.parameter != undefined) {
									for each (var p:XML in m.parameter){
										suggesttext.appendText(p.@type+",");
									}
									suggesttext.text = suggesttext.text.slice(0,suggesttext.text.length-1);
									cmdsuggest.push(m.@name+"(");
								} else {
									cmdsuggest.push(m.@name+"();");
								}
								suggesttext.appendText(")" + type +"\n");
							}
						} else {
							hitMax = true;
							break;
						}
					}
					
					// If there are suggestions
					if (areSuggestions){
						// if there were more then what can be displayed; add a last item called "..."
						if (hitMax){
							suggesttext.appendText("...");
						}
						suggesttext.visible = true;
						hpos = cmdsuggest.length;
					}
				}
				catch(er:Error){
					// do nothing
				}
			}
		}
		
		function hidesuggestions():void {
			suggesttext.visible = false;
			suggesttext.text = "";
			suggesttext.height = 20;
			cmdsuggest = new Array();
			hpos = -1;
		}
		
		//////////////////////////
		//		Interpreting	//
		/////////////////////////
		
		function interpret(str:String):void {
			// Remove ';' and spaces outside of quotes
			str = stringReplaceAll(str, ";");
			str = stringReplaceButExclude(str," ","\"");
			
			// Assigning
			if (str.indexOf("=") > 0){
				var ar = str.split("=");
				ar = checkShorthandCalculations(ar);
				ar[1] = stringToValWithCalculation(ar[1]);
				changeVar(ar[0],ar[1]);
			} else 
			// Calling functions
			if (str.indexOf("(") > 0 && str.indexOf(")") > str.indexOf("(")){
				echo("returned: "+stringToFunc(str));
			} else {
				// Console-only Commands and getVar
				switch(str){
					case "clear": historytext.text = ""; break;
					case "help": echo(help,"0099CC"); break;
					case "credits": echo(credits,"0099CC"); break;
					default: getVar(str); break;
				}
			}
		}
		
		function stringToPars(str:String):Array {
			// change commas but leave commas inside array literals
			str = str.replace("[","|");
			str = str.replace("]","|");
			str = stringReplaceButExclude(str,",","|","`");
			// split the parameters
			var pars = str.split("`");
			
			// Convert pars to vals/funcs if they are
			for (var i:Number = 0; i < pars.length; i++){
				try {
					pars[i] = stringToValWithCalculation(pars[i]);
					
					// convert to array if nessesary
					if (pars[i].indexOf(",") > -1){
						pars[i] = stringToArray(pars[i],false);
					}
				}
				catch (er:Error){
					// do nothing
				}
			}
			return pars;
		}
		
		function stringToArray(str,needSquareBrackets:Boolean = true){
			var res = str;
			if (str.indexOf("[") == 0 && str.lastIndexOf("]") == str.length-1){
				str = str.substr(1,str.length-2);
				res = new Array(str.split(","));
			} else
			if (!needSquareBrackets){
				res = new Array(str.split(","));
			}
			return res;
		}
		
		function stringToFunc(str:String, setval:Boolean = false){
			str = stringReplaceAll(str, ")");
			var ar = str.split("(");
			var pars;
			
			if (ar[1] == ""){
				pars = new Array();
			} else {
				pars = stringToPars(ar[1]);
			}
			
			if (!setval){
				callFunc(ar[0],pars);
			}
			return stringToVal(ar[0]).apply(null,pars);
		}
		
		function stringToVal(varname:String){
			var v:Array = varname.split(".");
			
			var ob = main;
			for (var i:Number = 0; i < v.length-1; i++){
				ob = ob[v[i]];
			}
			return ob[v[v.length-1]];
		}
		
		function checkShorthandCalculations(ar:Array):Array {
			if (ar[0].indexOf("+") == ar[0].length-1 || ar[0].indexOf("-") == ar[0].length-1 ||
				ar[0].indexOf("/") == ar[0].length-1 || ar[0].indexOf("*") == ar[0].length-1 ||
				ar[0].indexOf("%") == ar[0].length-1){
				ar[1] = ar[0] + ar[1];
				ar[0] = ar[0].substr(0,ar[0].length-1);
			}
			return ar;
		}
		
		function stringToValWithCalculation(str) {
			// Convert stuff to vals/funcs if they are and
			// Do calculations (-,+,/,*,%) for Numbers
			if (str.indexOf("+") > 0 || str.indexOf("-") > 0 || str.indexOf("/") > 0 || str.indexOf("*") > 0 || str.indexOf("%") > 0){
				var p:Array = new Array();
				var n:Number = 0;
				for (var s:Number = 0; s < str.length; s++){
					switch (str.charAt(s)){
						case "-": p[n] = 0; n++; break;
						case "+": p[n] = 1; n++; break;
						case "/": p[n] = 2; n++; break;
						case "*": p[n] = 3; n++; break;
						case "%": p[n] = 4; n++; break;
					}
				}
				str = str.replace("-","||");
				str = str.replace("+","||");
				str = str.replace("/","||");
				str = str.replace("*","||");
				str = str.replace("%","||");
				
				var t:Array = str.split("||");
				for (var c:Number = 0; c < t.length; c++){
					try {
						if (t[c].indexOf("(") > 0 && t[c].indexOf(")") > 0){
							t[c] = stringToFunc(t[c],true);
						} else {
							t[c] = stringToVal(t[c]);
						}
					}
					catch (er:Error){
						if (!isNaN(t[c])){
							t[c] = Number(t[c]);
						}
					}
				}
				
				str = t[0];
				n = 0;
				for (var o:Number = 1; o < t.length; o++){
					switch (p[n]){
						case 0: str -= t[o]; break;
						case 1: str += t[o]; break;
						case 2: str /= t[o]; break;
						case 3: str *= t[o]; break;
						case 4: str %= t[o]; break;
					}
					n++;
				}
			} else {
				try {
					if (str.indexOf("(") > 0 && str.indexOf(")") > str.indexOf("(")){
						str = stringToFunc(str,true);
					} else {
						str = stringToVal(str);
					}
				}
				catch (er:Error){
					// do nothing
				}
			}
			return str;
		}
		
		function changeVar(varname,vset):void {
			try {
				var v:Array = varname.split(".");
				
				var ob = main;
				for (var i:Number = 0; i < v.length-1; i++){
					ob = ob[v[i]];
				}
				
				// check if it's an array
				try {
					vset = stringToArray(vset);
				}
				catch(er:Error){
					
				}
				// check if it's a boolean
				if (vset == "true") vset = true;
				if (vset == "false") vset = false;
				
				// assign
				ob[v[v.length-1]] = vset;
			}
			catch(er:Error){
				error(er.message);
			}
		}
		
		function getVar(varname):void {
			var rstring:String = varname + " is ";

			try {
				rstring += stringToVal(varname);
				echo(rstring);
			}
			catch(er:Error){
				error(er.message);
			}
		}
		
		function callFunc(func,pars:Array):void {
			try {
				stringToVal(func).apply(null,pars);
			}
			catch(er:Error){
				error(er.message);
			}
		}
		
	}
}
