// THIS CODE IS NOT APPROVED FOR USE IN/ON ANY OTHER UI ELEMENT OR PRODUCT COMPONENT. 
// Copyright (c) 2007 Renderspace. All rights reserved.


/************************************************/
// object base interfaces and object manager
/************************************************/


var OBJECT_CATEGORY_MANAGER = 1; // should be only one
var OBJECT_CATEGORY_RENDERABLE = 2; // object that is visual and interactive (e.g. Window)
var OBJECT_CATEGORY_HELPER = 3; // object that is not visual an interactive, but helps other objects (e.g. Timer)


var ObjectGarbageCollector = new Array();

/************************************************/


function ObjectBase(parent, id, name, category, type) { // (implicit) object creation, called for objects of every type
	this.id = id;
	this.name = name;
	this.category = category;
	this.type = type;
	this.canOccupateArea = true;
	this.isReleased = false;
	
	this.parentObject = parent;
	this.nextObject = null;
	this.prevObject = null;
	this.firstChildObject = null;
	
	if (parent != null) { // only add this new object to the tree, if parent exists
		// insert this object into a linked list
		var currObj = parent.firstChildObject;
		while ((currObj != null) && (currObj.nextObject != null)) currObj = currObj.nextObject;
		if (currObj != null) {
			this.prevObject = currObj;
			currObj.nextObject = this;
		}
		if (parent.firstChildObject == null) parent.firstChildObject = this;
		
		if (parent.onChildAppended) parent.onChildAppended(this);
	}
}


ObjectBase.prototype.release = function() { // object deletion and removal from the tree
	var currObject = this.firstChildObject;
	while (currObject != null) {
		var nextObject = currObject.nextObject;
		currObject.release();
		currObject = nextObject;
	}
	
	if (this.prevObject != null) this.prevObject.nextObject = this.nextObject; // unchain from list
	if (this.nextObject != null) this.nextObject.prevObject = this.prevObject; // unchain from list
	
	if (this.parentObject && (this.parentObject.firstChildObject == this)) {
		this.parentObject.firstChildObject = this.nextObject;
	}
	
	if (this.onRelease != null) this.onRelease();
	this.isReleased = true;
	
	// instead of "delete this", the object is inserted into a garbage collector, to be freed later
	ObjectGarbageCollector.push(this);
}


ObjectBase.prototype.setAsFirst = function() { // bring this object to the beginning of the linked list
	if (this.parentObject == null) return false; // cannot do
	if (this.parentObject.firstChildObject == this) return true; // already first
	
	if (this.prevObject != null) this.prevObject.nextObject = this.nextObject; // unchain from list
	if (this.nextObject != null) this.nextObject.prevObject = this.prevObject; // unchain from list
	
	this.prevObject = null;
	this.nextObject = this.parentObject.firstChildObject;
	this.parentObject.firstChildObject.prevObject = this;
	this.parentObject.firstChildObject = this; // bring to the beginning
	
	return true;
}


/************************************************/

// object that is visual and interactive (e.g. Window)
function RenderableObject(parent, id, name, type) {
	this.base = ObjectBase;
	this.base(parent, id, name, OBJECT_CATEGORY_RENDERABLE, type);
	this.isVisible = false;
	this.isEnabled = true;
	this.x = this.y = this.w = this.h = 0;
	this.opacity = 0;
	this.parentCanvas = null;
}

RenderableObject.prototype = new ObjectBase;


/************************************************/

// object that is not visual an interactive, but helps other objects (e.g. Timer)
function HelperObject(parent, id, name, type) {
	this.base = ObjectBase;
	this.base(parent, id, name, OBJECT_CATEGORY_HELPER, type);
}

HelperObject.prototype = new ObjectBase;


/************************************************/

// main object manipulator and root node of the object tree
function ObjectManager(id, name) {
	this.base = ObjectBase;
	this.base(null, id, name, OBJECT_CATEGORY_MANAGER, "manager");
}

ObjectManager.prototype = new ObjectBase;


ObjectManager.prototype.garbageCleanup = function() {
    // delete objects marked as to-be-deleted
	while (ObjectGarbageCollector.length > 0) {
		var obj = ObjectGarbageCollector.pop();
		if (obj != null) delete obj;
	}
}



ObjectManager.prototype.findObjectByID = function(parentObj, ID, type, doUseType) {
	var currObj = (parentObj == null) ? this.firstChildObject : parentObj.firstChildObject;
	while (currObj != null) {
		if ((!doUseType || (currObj.type == type)) && (currObj.id == ID)) return currObj;
		var foundObj = this.findObjectByID(currObj, ID, type, doUseType);
		if (foundObj != null) return foundObj;
		currObj = currObj.nextObject;
	}
	return null;
}



ObjectManager.prototype.findObjectByName = function(parentObj, name, type, doUseType) {
	var currObj = (parentObj == null) ? this.firstChildObject : parentObj.firstChildObject;
	while (currObj != null) {
		if ((!doUseType || (currObj.type == type)) && (currObj.name == name)) return currObj;
		var foundObj = this.findObjectByName(currObj, name, type, doUseType);
		if (foundObj != null) return foundObj;
		currObj = currObj.nextObject;
	}
	return null;
}


// recursively calls "funcName" on every object in the tree
ObjectManager.prototype.callFunction = function(parentObj, funcName, params, isHandled) {
	var currObj = (parentObj == null) ? this.firstChildObject : parentObj.firstChildObject;
	while (currObj != null) {
		var nextObj = currObj.nextObject;
		var isNodeHandled = this.callFunction(currObj, funcName, params, isHandled);
		if (isNodeHandled) isHandled = true;
		if (eval('currObj.' + funcName + ' != undefined')) {
			isNodeHandled = eval('currObj.' + funcName + '(params, isHandled)');
			if (isNodeHandled) isHandled = true;
		}
		currObj = nextObj;
	}
	return isHandled;
}


// recursively calls "funcName" on every object in the tree, until some returns a non-null value
ObjectManager.prototype.callFunctionUntilValue = function(parentObj, funcName, params) {
	var currObj = (parentObj == null) ? this.firstChildObject : parentObj.firstChildObject;
	while (currObj != null) {
		var nextObj = currObj.nextObject;
		var retVal = this.callFunctionUntilValue(currObj, funcName, params);
		if (retVal != null) return retVal;
		if (eval('currObj.' + funcName + ' != undefined')) {
			retVal = eval('currObj.' + funcName + '(params)');
			if (retVal != null) return retVal;
		}
		currObj = nextObj;
	}
	return null;
}


ObjectManager.prototype.findObjectByPos = function(parentObj, pos) {
	var currObj = (parentObj == null) ? this.firstChildObject : parentObj.firstChildObject;
	while (currObj != null) {
		var nextObj = currObj.nextObject;
		var objAtPos = this.findObjectByPos(currObj, pos);
		if (objAtPos != null) return objAtPos;
		
		if ((currObj.category == OBJECT_CATEGORY_RENDERABLE) && currObj.isVisible && currObj.canOccupateArea) {
			var objX = currObj.x, objY = currObj.y;
			if (currObj.parentCanvas != null) {
				objX += getVisualElementPosX(currObj.parentCanvas);
				objY += getVisualElementPosY(currObj.parentCanvas);
			}
			if ((pos.x >= objX) && (pos.y >= objY) && (pos.x < objX + currObj.w) && (pos.y < objY + currObj.h)) {
				return currObj;
			}
		}
		currObj = nextObj;
	}
	return null;
}



// most frequently called functions should be defined directly, because "eval" is very slow
ObjectManager.prototype.onUpdateFrame = function(parentObj, params, isHandled) {
	var currObj = (parentObj == null) ? this.firstChildObject : parentObj.firstChildObject;
	while (currObj != null) {
		var nextObj = currObj.nextObject;
		var isNodeHandled = this.onUpdateFrame(currObj, params, isHandled);
		if (isNodeHandled) isHandled = true;
		if (currObj.onUpdateFrame != undefined) {
			isNodeHandled = currObj.onUpdateFrame(params, isHandled);
			if (isNodeHandled) isHandled = true;
		}
		currObj = nextObj;
	}
	return isHandled;
}


ObjectManager.prototype.onMouseMove = function(parentObj, params, isHandled) {
	var currObj = (parentObj == null) ? this.firstChildObject : parentObj.firstChildObject;
	while (currObj != null) {
		var nextObj = currObj.nextObject;
		var isNodeHandled = this.onMouseMove(currObj, params, isHandled);
		if (isNodeHandled) isHandled = true;
		if (currObj.onMouseMove != undefined) {
			isNodeHandled = currObj.onMouseMove(params, isHandled);
			if (isNodeHandled) isHandled = true;
		}
		currObj = nextObj;
	}
	return isHandled;
}
