/*	VERSION:	3.2
3.2		Fixed the sanity check, so that this only parses strings, and returns non-strings as-is
3.1		Added resolve() usage to openBracketCloseBracket(),  just before using evalPath()
3.0		Completely re-written from scratch
			Any type of data can be passed into functions.  (indirectly via variable look-ups,  but the resolved data is a direct reference)
			Any type of data can be directly returned.  (if everything resolves to a single value and there's no surrounding text)

DEPENDENCIES:
	nestedEval.as
		evalPath.as
			getImpliedValue.as
			resolveContainer.as

USAGE:
	#include "functions/nestedEval.as"
	myObj.var1 = 1.1;
	string = "[Math.ceil([myObj.var1])]";
	result = nestedEval( string, "RAM", "_this" );

DESCRIPTION:	
	This evaluates variable paths contained in [], and outputs the result.
		"myvar = [myVar]"  ->  "myvar = value"
	This also calls functions contained within []
		"[Math.ciel(1.1)]"  ->  "2"
	
	This accepts both full paths, or relative paths.
	Full paths start with a global object.
	Relative paths are assumed to be located within the specified 'defaultContainer_str"
	"defaultContainer_str" is an optional parameter.
	If no "defaultContainer_str" is specified, relative paths are assumed to be local variables within "this"
	if "defaultContainer_str" is specified, then relative paths do NOT check the local scope unless they explicitly specify "this." in their path
	Full paths are automatically detected.
*/
#include "evalPath.as"
//		getImpliedValue.as
//		resolveContainer.as
function nestedEval( inputPath, defaultContainer_str, thisName )
{
	var thisScope = this;
	// Sanity check
	var inputType = typeof(inputPath);
	var inputIsValid = ( inputType === "string" );
	if( !inputIsValid )		return inputPath;		// Whatever it is,  leave it alone
	// var inputIsValid = ( typeof(myVar) !== "string" );
	// if( !inputIsValid )		return inputPath;		// Whatever it is,  leave it alone
	
	var placeHolderMarker = '¶';
	var placeHolderLength = 3;
	var resolvedValues = [];
	resolvedValues[ "xx" ] = "[";
	
	// multi-pass mutation,  replace every [) [( [] with placeholder values
	while(  inputPath.indexOf( "[" ) > -1  ){
		inputPath = replaceLastBracket( inputPath );
	} // while there are still [ in the path
	
	var output = resolve( inputPath );
	return output;
	
	
	
	// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // 
	// HELPER FUNCTIONS
	
	
	
	function replaceLastBracket( inputPath ){
		var thisPathAt = inputPath.lastIndexOf( "[" );
		var beforeText = inputPath.substr( 0, thisPathAt );
		var thisPath = inputPath.substr( thisPathAt );
		var output = thisPath.substr( 1 );		//  always remove the opening bracket
		
		// Any of these can mutate the output.  Only one will run.
		if( openBracketCloseParam() ){}
		else if( openBracketOpenParam() ){}
		else if( openBracketCloseBracket() ){} 
		else{
			// open bracket,  but no closer
		}
		return beforeText + output;
		
		
		// // // // // // // // // // // 
		
		
		function openBracketCloseParam(){
			var closeParamAt = thisPath.indexOf( ")" );
			var openParamAt = thisPath.indexOf( "(" );
			var closeBracketAt = thisPath.indexOf( "]" );
			//  we want to know if there's an unclosed bracket within a parameter set
			// // [myFunc(1,2,[,4)]
			// // there's either no closing bracket...
			// // ... or the closing param occurs before the closing bracket
			var thisApplies = ( closeBracketAt === -1  ||  closeParamAt < closeBracketAt )  &&  openParamAt === -1;
			if( closeParamAt === -1 )		thisApplies = false;
			if( !thisApplies )		return false;		// This did not mutate the output
			
			// replace open-bracket char with placeholder
			output = placeHolderMarker + "xx" + thisPath.substr( 1 );		// [,4)]		=>		#xx,4)]
			return true;		// This mutated the output
		} // openBracketCloseParam()
		
		
		
		function openBracketOpenParam(){
			var openParamAt = thisPath.indexOf( "(" );
			var closeBracketAt = thisPath.indexOf( "]" );
			// We want to know if there's an open param within a bracket-set
			// // [myFunc(1,2,3)]
			var thisApplies = ( closeBracketAt === -1  ||  openParamAt < closeBracketAt )  &&  openParamAt > -1;
			if( !thisApplies )		return false;		// This did not mutate the output
			
			// resolve the function path
			var funcPath = thisPath.substring( 1, openParamAt );
			funcPath = resolve( funcPath );
			var func = evalPath( funcPath, defaultContainer_str, thisName );
			var isFunction = (func instanceof Function);
			if( isFunction ){
				// gather the parameters
				var closeParamAt = thisPath.indexOf( ")", openParamAt );
				var params_str = thisPath.substring( openParamAt+1, closeParamAt );
				var paramsStr_ary = params_str.split( ',' );
				// resolve the parameters
				params_ary = [];
				for( var i=0; i<paramsStr_ary.length; i++ ){
					params_ary[ i ] = resolve( paramsStr_ary[ i ]  );
				} // for:  each parameter
				// call the function
				var funcResult = func.apply( thisScope, params_ary );
				var afterText = thisPath.substr( closeBracketAt+1 );
				var placeHolder_str = setPlaceHolder( funcResult );
				output = placeHolder_str + afterText;
				// store the result  +  replace the function & params with a placeholder
				return true;
			}
			else {
				// This is not a function, and was obviously used by mistake, so...
				// ... erase it from the output + announce an error via trace()
				var afterText = thisPath.substr( closeBracketAt+1 );
				// Keep this trace() function
				trace( "ERROR:  " + funcPath + "  is not a function!" );
				// ( "ERROR:  " + funcPath + "  is not a function!" );
				output = afterText;
				return true;
			} 
		} // openBracketOpenParam()
		
		
		
		function openBracketCloseBracket(){
			var closeBracketAt = thisPath.indexOf( "]" );
			var thisApplies = ( closeBracketAt > -1 );
			if( !thisApplies )		return false;		// This did not mutate the output
			
			var afterText = thisPath.substr( closeBracketAt + 1 );
			// this is a bracket set,  so resolve the contents + replace with a placeholder
			var resolveThis = thisPath.substring( 1, closeBracketAt );		// isolate the path inside of the [ ]
			var actualValue = evalPath( resolve(resolveThis) , defaultContainer_str, thisName );
			var placeHolder_str = setPlaceHolder( actualValue );
			output = placeHolder_str + afterText;
			return true;
		} // openBracketCloseBracket()
		
		
	} // replaceLastBracket()
	
	
	
	
	
	function resolve( inputPath ){
		var idLength = placeHolderLength-1;
		// find every placeholder => map its corresponding values
		var placeHolderStrings = inputPath.split( placeHolderMarker );
		var placeHolderOutput = [];
		placeHolderOutput[ 0 ]  =  placeHolderStrings[ 0 ];
		
		for( var i=1; i<placeHolderStrings.length; i++){
			var this_str = placeHolderStrings[i];
			var id_str = this_str.substr( 0, idLength );		// the ID after the placeHolder marker
			var placeHolderValue = getPlaceHolder( id_str );	//  retrieve the actual value associated with this ID
			var afterText = this_str.substr( idLength );		// Everything after the placeholder
			if( afterText.length > 0 ){
				placeHolderOutput[i] = String( placeHolderValue ) + afterText;		// store as a concatenated string
			} else {
				placeHolderOutput[i] = placeHolderValue;		//  no text = store raw value
			} 
		} // for:  each place-holder
		
		if( placeHolderOutput.length === 2  &&  placeHolderOutput[ 0 ]  === "" ){
			// The result is a single value,  then return that value in its actual form
			return placeHolderOutput[1];
		} else {
			// Else:  return the result as a concatenated string
			return placeHolderOutput.join("");		// return as a concatenated string
		} 
		
	} // resolve()
	
	
	
	
	
	function setPlaceHolder( storeThisValue ){
		// Add this value to the array
		resolvedValues.push( storeThisValue );
		// Return the index for this value
		var index_num = resolvedValues.length-1;
		var index_str = String( index_num );
		var idLength = placeHolderLength - 1;
		while( index_str.length < idLength ){
			index_str = "0" + index_str;
		} 
		return placeHolderMarker + index_str;		// #00
	} // setPlaceHolder()
	
	
	function getPlaceHolder( id_str ){
		var isNum = ( !isNaN( id_str ) );
		//  return the corresponding value
		if( isNum ){
			var id_num = Number( id_str );		// "02" => 2
			return resolvedValues[id_num];
		} else {
			return resolvedValues[id_str];
		} 
	} // getPlaceHolder()
	
	
	
} // nestedEval()