//___________________________________
//                                   \
// Objects handler.  version 1.2	$Id: objects.js 106 2008-07-18 21:22:07Z nilton $
//___________________________________/
/*
	This library lets you manipulate Objects.
	Handy (standalone) functions defined in this library (Object related):
		getObjType()
		CloneObj()
		FoldObj()
		UnFoldObj()
	Read the inline documentation for usage examples.
*/

// ====================================================================== //
/**
 * You can create and object, and then test the type with typeof/instanceof.
 * It will return 'object' if you actually need the type of the object, use this function.
 * This function also works for Sphere objects.
 * usage:
 * 	function myStuffObj(){}
 * 	var O = new myStuffObj(); //[object myStuffObj]
 * 	type = getObjType(O); //type=="myStuffObj"
 * 	if(typeof O == type) alwaystrue=true;
 * 	var O=33		//getObjType(O) returns 'number'
 * 	var O="33"		//returns 'string'
 * 	var O={a:1,b:2}		//returns 'Object', note the capital O.
 * 	var O=CreateColorMatrix(0,0,0,255, 0,0,255,0, 0,255,0,0); //returns 'colormatrix'
 */
function getObjType(v){
	if(typeof v !='object')return(typeof v);
	if(v==null)return 'null';
	var A=v.constructor.toString().match(/function (\w+)/); 
	if(A && A[1]!="Object") return A[1];
	A=v.toString().match(/\[object (\w+)\]/);
	if(A)return A[1];
	return typeof(v);
}

TypeOf = getObjType; // Alias

/**
 * Some commonly used objects
 */
function IsNumber(n) { return getObjType(n)=='number'; }
function IsString(n) { return getObjType(n)=='string'; }
function IsImage(n) { return getObjType(n)=='image'; }
function IsSurface(n) {return getObjType(n)=='surface'; }
function IsFont(n) { return getObjType(n)=='font'; }
function IsColor(n) { return getObjType(n)=='color'; }
function IsColormatrix(n) { return getObjType(n)=='colormatrix'; }
function IsWindowStyle(n) { return getObjType(n)=='windowstyle'; }
function IsSpriteset(n) { return getObjType(n)=='spriteset'; }
function IsByteArray(n) { return getObjType(n)=='byte_array'; }
// person (Createperson returns undefined), bytearray (not much used), 

// ====================================================================== //
/**
 *	Javascript copies normal variables (strings, numbers, booleans, etc) 
 *	by value and objects by reference.
 *	This means that if you do this:
 *		var SPRITE= {HP:40,Level:2}; //Defines an object SPRITE.HP==40 and SPRITE.Level==2
 *		var USER=SPRITE;
 *		USER.HP=6;
 *		Abort(SPRITE.HP);	//Returns 6, not 40!
 *	The solution:
 *		var USER=CloneObj(SPRITE);
 *	Note that most Sphere objects have a .clone() function. 
 *	CloneObj does not clone those object types because they are 'native' code. (it tries to use .clone() though)
 */
function CloneObj(what){

	function enclone(o){
		switch(getObjType(o)){
			// passed by value
			case 'null':
			case 'undefined':
			case 'boolean':
			case 'number':
			case 'string':
			case 'Array':
				return o;
				break;
			// native Sphere with .clone()
			case 'font':
			case 'image':
			case 'surface':
			case 'windowstyle':
			case 'byte_array':
			case 'spriteset':
				return o.clone();
				break;
			// no .clone ?
			case 'color':
				return CreateColor(o.red, o.green, o.blue, o.alpha);
				break;
			// Real 'object'
			case 'Object':
				return new CloneObj(o);
				break;
			default:
				return o;
				break;
		}
	};

	if(getObjType(what) == 'Object'){
		var O;
		eval("O=new "+getObjType(what)+"()");
		var i;
		for(i in what){
			O[i] = enclone(what[i]);
		}
		return O;
	};
	return enclone(what);
}


// ====================================================================== //
/**
 *	Merge objects into another object
 *
 * var Person = { name:'hero', stats:{hp:10, mp:10} };
 * MergeObj( Person, 
 *       { stats:{hp:200, agl:10} },  
 *       { bookslearned:{"Book of Power":true} } 
 * );
 * Abort(FoldObj(Person));
 * Or even: MergeObj( Person.stats, {hp:300});
 *
 * note: you can set the boolean inject.createstructure=false; this way, "bookslearned" is not added
 * because it doesnt exist in Person
 */
function MergeObj(){
	var O = arguments[0];
	var ie,len;
	for (ie = 1, len = arguments.length; ie < len; ++ie){
		inject.call(O, arguments[ie], '=');
	}
	return O;
}

/*
 * helper function, injects an object into "this", use in combination with call.
 * @param {object} o The object values to inject
 * @param {string} operation If we have to do something special with these injected values
 * @returns this
 *   = to set the value
 *   + to sum both values
 *   - to substract this from the injected value
 *   * to multiply
 *   / to divide
 *   < to let the injected value the the max
 *   > to let the injected value the the min
 *   ! to let the injected value be injected if the this value is undefined
 *   <undefined> Acts like an operation when the injected value contains an arithmetic operation. Else acts like '='
 *
 * If you have: Person = { stats:{hp:2, exp:323}, lvl:1 }
 * You call it like this:
 *   inject.call( Person, {stats:{hp:5}}, "+" );
 * or also:
 *   inject.call( Person.stats, {hp:5}, "+" );
 * or also:
 *   inject.call( Person.stats, {hp:"+5"} );
 * In all cases the hp will become 7.
 * Another example:
 *   Person = { stats:{hp:50, mp:18, exp:323}, lvl:1, max:{hp:100, mp:20} };
 * We give a potion to this person:
 *   inject.call( Person.stats, {hp:"+100"} );
 * Now we see that the hp=150, but the max = 100, so lets clamp it:
 *   inject.call( Person.stats, Person.max, "<" );
 * The persons hp=100 now... :)
 */
function inject(o, operation){
	var il;
	for(il in o){
		if(typeof o[il]=='object' && (o[il]!==null)){
			if(this[il])
				inject.call(this[il],o[il],operation);
			else
				if (inject.createstructure) {
					this[il]=new Object();
					inject.call(this[il],o[il],operation);
				}
		}else{
			//if(!this[il]) this[il] = 0;
			switch(operation){
				case '=': this[il]  = o[il]; break;
				case '+': this[il] += o[il]; break;
				case '-': this[il] -= o[il]; break;
				case '*': this[il] *= o[il]; break;
				case '/': this[il] /= o[il]; break;
				case '<': if(this[il]>o[il]) this[il]=o[il]; break;
				case '>': if(this[il]<o[il]) this[il]=o[il]; break;
				case '!': if(this[il]===undefined) this[il]=o[il]; break;
				case undefined:
					if(typeof this[il]=='number'){
						if(typeof o[il]=='string' && o[il][0].match(/[\+\-\*\/]/))
							this[il] = eval(this[il]+o[il]);
						else
							this[il] = o[il];
					}else
						this[il] = o[il];
				break;
				default: this[il] = o[il];
			}
		}
	};
	return this;
}
inject.createstructure = true;

// ====================================================================== //
/**	Javascript Marshaller/serializer.
 *	Converts an object to a big string which can be eval()-ed back to recreate the object.
 * @param {object} myObject The object to convert to string
 * @param {string} varname Optional variable name. If set, then unfold will create this variable.
 * @param {boolean} alsoFunctions True if functions also have to be folded, else they are skipped.
 *  examples:
 *	var M= {a:6,b:{s:"hi"},c:CreateColor(255,120,50, 128)}
 *
 *	Abort( FoldObj(M,"MM") ) //Returns: var MM={'a':6, 'b':{'s':"hi"}, 'c':CreateColor(255, 120, 50, 128)}
 *	Abort( FoldObj(M) )   //Returns: {'a':6, 'b':{'s':"hi"}, 'c':CreateColor(255, 120, 50, 128)}
 * To make ColorMatrices work, you need this piece of code:
 * if(typeof CreateColorMatrix_== 'undefined'){
 *   CreateColorMatrix_ = CreateColorMatrix;
 *   CreateColorMatrix = function(rn, rr, rg, rb,  gn, gr, gg, gb,  bn, br, bg, bb){
 *     var o = CreateColorMatrix_(rn, rr, rg, rb,  gn, gr, gg, gb,  bn, br, bg, bb);
 *     o.rn=rn; o.rr=rr; o.rg=rg; o.rb=rb;
 *     o.gn=gn; o.gr=gr; o.gg=gg; o.gb=gb;
 *     o.bn=bn; o.br=br; o.bg=bg; o.bb=bb;
 *     o.update = function(){
 *       return CreateColorMatrix(this.rn, this.rr, this.rg, this.rb,  this.gn, this.gr, this.gg, this.gb,  this.bn, this.br, this.bg, this.bb);
 *     };
 *     o.getColorArray = function(){
 *       return [this.rn, this.rr, this.rg, this.rb,  this.gn, this.gr, this.gg, this.gb,  this.bn, this.br, this.bg, this.bb];
 *     };
 *     o.toJSON = function(){}; //TODO
 *     return o;
 *   };
 * };
 *
 * To make bytearrays work:
 * if(typeof CreateByteArray_== 'undefined'){
 *   CreateByteArray_ = CreateByteArray;
 *   CreateByteArray = function(b){
 *     if (typeof b == 'number')
 *       return CreateByteArray_(b);
 *     var i = b.length;
 *     var ba = CreateByteArray_(i);
 *     while(i--){
 *       ba[i]=b[i];
 *     }
 *     return ba;
 *   }
 * }

 *
 *	var S=FoldObj(M); //S is a string.
 *	var N=UnFoldObj(S); //N is an object.
 *
 *	var T=FoldObj(M,"MM"); //T is a string.
 *	eval(T); //MM now exists as variable.
 *
 * You may also want to look into JSON.stringify(), it does the same thing.
 * TODO: bytearrays do not work yet, you need to redefine CreateByteArray() so when it receives an array it will convert it...
 */
function FoldObj(myObject, varname, alsoFunctions) { 
	var astr = varname?"var "+varname+"={":"{";
	var type = getObjType(myObject);
	if(type != 'Object')
		return enfold(type, myObject, alsoFunctions);

	function enfold(type, v, dofunc){
		switch(type){
			case "null":
			case 'undefined':
			case 'boolean':
			case 'number':
				return v;
				break;
			case 'string':
				return '"' + v.replace(/"/g,"\\\"") + '"';
				break;
			case 'Array':
				var str="["; 
				var i,l;
				for(i=0,l=v.length;i<l;++i){
					str += FoldObj(v[i],false, dofunc) + ","
				};
				return str=='[' ? '[]' : str.replace(/,$/,"]");
				break;
			case 'function':
				if(alsoFunctions) return v.toString().replace(/\n\s*/g," "); return "";
				break;
			case 'Object': 
				var str = "{";
				var ch;
				for (ch in v) {
					str += "'"+ch+"':";
					str += FoldObj(v[ch],false,dofunc);
					str += ", ";
				}
				return  str=="{" ? "{}" : str.slice(0,-2) + "}";
				break;
			case 'colormatrix':
				return "CreateColorMatrix("+v.rn+','+v.rr+','+v.rg+','+v.rb+','+v.gn+','+v.gr+','+v.gg+','+v.gb+','+v.bn+','+v.br+','+v.bg+','+v.bb+ ")";
				break;
			case 'color':
				return "CreateColor("+v.red+','+v.green+','+v.blue+','+v.alpha+")";
				break;
			case 'byte_array':
				var str="[";
				var i,l;
				for(i=0,l=v.length;i<l;++i){
					str+= v[i]+","
				};
				return str=='[' ? "CreateByteArray(0)" : 'CreateByteArray(' + str.replace(/,$/,"])");
				break;
			default:
				return "MergeObj(new "+getObjType(v)+"(), "+enfold('Object',v, dofunc)+")";
				break;
		}
	}

	var empty = true;
	var ik;
	for (ik in myObject) {
		type = getObjType(myObject[ik]);
		if(type != 'function' || alsoFunctions){
			empty = false;
			astr += "'"+ik+"':";
			astr += enfold(type, myObject[ik], alsoFunctions);
			astr += ", ";
		};
	};
	if(!empty)
		astr = astr.slice(0,-2);
	astr +=  varname ? "};" : "}";
	return astr.toString().replace(/\n\s*/g," ");
}

/**
 * Javascript unmarshaller/deserializer.
 * Returns the original object folded with FoldObj().
 * param {string} Objstr The object string
 * returns an object
 */
function UnFoldObj(Objstr) {
	if(Objstr[0]!='{')
		Objstr=Objstr.replace(/^var \S+=/,'');
	eval('var Obj='+Objstr);
	return Obj;
} 

