// Template Class

function Template(nTemplateNode, aButtons) {this._init(nTemplateNode, aButtons);}
Template.prototype = new Object();
Template.prototype.toString = function() {return "Template";};

Template.prototype.addRepetitionBlock = function(index) {
	api.writeCall("in Template.addRepetitionBlock: index: " + index);
	if(! this.parentNode) {return;}
	if(this.parentNode.nodeType != 1) {return;}	
	if(this.iReplicaCount >= this.repeatMax) {return null;}
	var nReplica = this._buildReplica();
	this._insertBlock(nReplica, index);
	if (wfRepetitionBlock_afterAddNode) {wfRepetitionBlock_afterAddNode(nReplica);}
	this.iReplicaCount++;
	this.repetitionIndex++;
	this._updateButtons();
	if(this.onAdded) {this._fireEvent("added", nReplica);}
	return nReplica;
};

Template.prototype.addRepetitionBlockByIndex = function(index) {
	if(this.repetitionIndex < index) {this.repetitionIndex = index;}
	return this.addRepetitionBlock();
};

Template.prototype.moveRepetitionBlock = function(iDistance, nToMove) {
	var nParent;
	// move from button to block
	while(nToMove = nToMove.parentNode) {
		if(api.getAttribute(nToMove, "repeat")) {
			nParent = nToMove.parentNode;
			break;
		}
	}
	if( ! nParent) {return;}
	if(nParent != this.parentNode) {api.warn("moveRepetitionBlock: nParent != this.parentNode");}
	var nTarget = nToMove;
	if(iDistance < 0) {	// move up
		if( ! nTarget.previousSibling) {return;}
		while(nTarget = this._getNextBlock(nTarget, "previousSibling")) {
			if(api.hasAttribute(nTarget, "repeat")) {
				if(api.getAttribute(nTarget, "repeatTemplate") != this.sId) {
					nTarget = nTarget.nextSibling;
					break;
				}
				else if( ! ++iDistance) {
					if (wfRepetitionBlock_beforeMoveNode) {wfRepetitionBlock_beforeMoveNode(nToMove);}
					nParent.insertBefore(nToMove, nTarget);
					if (wfRepetitionBlock_afterMoveNode) {wfRepetitionBlock_afterMoveNode(nToMove);}
					break;
				}
			}
		}
	}
	else {	// move down
		if( ! nTarget.nextSibling) {return;}
		while(nTarget = this._getNextBlock(nTarget, "nextSibling")) {
			if(api.hasAttribute(nTarget, "repeat")) {
				if(api.getAttribute(nTarget, "repeatTemplate") != this.sId) {
					nTarget = nTarget.previousSibling;
					break;
				}
				else if( ! --iDistance) {
					nTarget = nTarget.nextSibling;
					if (wfRepetitionBlock_beforeMoveNode) {wfRepetitionBlock_beforeMoveNode(nToMove);}
					nParent.insertBefore(nToMove, nTarget);
					if (wfRepetitionBlock_afterMoveNode) {wfRepetitionBlock_afterMoveNode(nToMove);}
					break;
				}
			}
		}
	}
};

Template.prototype._getNextBlock = function(nTarget, sAttribute) {
	while(nTarget = nTarget[sAttribute]) {
		if(api.hasAttribute(nTarget, "repeat")) {return nTarget;}
	}
	return null;
};

Template.prototype.removeRepetitionBlock = function(arg, nCurrent) {
	api.writeCall("in Template.removeRepetitionBlock");
	do {
		if( api.getAttribute(nCurrent, "repeatTemplate") == this.sId) {
				if (wfRepetitionBlock_beforeRemoveNode) {wfRepetitionBlock_beforeRemoveNode(nCurrent);}
				if (this.onBeforeRemoved) {this._fireEvent("beforeRemoved", nCurrent);}
				nCurrent.parentNode.removeChild(nCurrent);
				break;
		}
	}
	while(nCurrent = nCurrent.parentNode);
	this.iReplicaCount--;
	while(this.iReplicaCount < this.repeatMin) {
		this.addRepetitionBlock();
	}
	this._updateButtons();
	if(this.onRemoved) {this._fireEvent("removed", nCurrent);}
};

//////////////////////////////////////////////////////////////////////////////////////

// PRIVATE METHODS

Template.prototype._init = function(nTemplateNode, aButtons) {
	api.writeCall("in Template._init");
	this.aAttributesToIncrement = new Array("id", "name", "repeat", "template", "value");
	
	this.nOriginal = nTemplateNode;
	this.nPlaceholder = api.newNode("span");
	this.parentNode = this.nOriginal.parentNode;
	this.sId = this.nOriginal.id;
	this.onAdded = this._makeEventObject("added");
	this.onRemoved = this._makeEventObject("removed");
	this.onBeforeRemoved = this._makeEventObject("beforeRemoved");
	this.repeatMin = this._getWithDefault(this.nOriginal, "repeat-min", 0, "min");
	this.repeatMax = this._getWithDefault(this.nOriginal, "repeat-max", 1000000, "max");
	this.repeatStart = this._getWithDefault(this.nOriginal, "repeat-start", 1);
	this.aAddButtons = new Array();
	this.aRemoveButtons = new Array();
	this.aMoveUpButtons = new Array();
	this.aMoveDownButtons = new Array();
	this.iReplicaCount = 0;
	this.repetitionIndex = this._getRepetitionIndex(this.nOriginal);
	
	this.nOriginal.setAttribute("repeat-template", this.sId);
	this.nOriginal.setAttribute("repeatTemplate", this.sId);	
	this.nOriginal.setAttribute("repeat", "[" + this.sId + "]");
	
	api.removeAttribute(this.nOriginal, "id");
	api.removeAttribute(this.nOriginal, "repeat-min");
	api.removeAttribute(this.nOriginal, "repeat-max");
	api.removeAttribute(this.nOriginal, "repeat-start");
	
	this.parentNode.insertBefore(this.nPlaceholder, this.nOriginal);
	this.parentNode.removeChild(this.nOriginal);
	if(api.isArray(aButtons)) {
		for(var i = 0; i < aButtons.length; i++) {
			this._setButtons(aButtons[i]);
		}
	}
	
	this.aTemplateCollections = new Array();
	while(this.iReplicaCount < this.repeatStart && this.iReplicaCount < this.repeatMax) {
		this.addRepetitionBlock();
	}
	this._updateButtons();
};

Template.prototype._makeEventObject = function(sEvent) {
	var obj;
	sEvent = sEvent.toLowerCase();
	if(api.hasAttribute(this.nOriginal, "on" + sEvent)) {
		obj = new Object();
		obj.target = this;
		obj.type = sEvent;
		var sArgs = "";
		var sHandler = api.getAttribute(this.nOriginal, "on" + sEvent);
		if(sHandler.match(/^(.*?)\((.*)\)/)) {
			sHandler = RegExp.$1;
			sArgs = RegExp.$2;
		}
		obj.sHndlerInfoName = "a" + sEvent;
		obj.aHandlerInfo = new Array( {func: sHandler, arg: sArgs} );
	}
	return obj;
};

Template.prototype._fireEvent = function(sEvent, node) {
	sHandler = "on" + sEvent.capitalize();
	if( ! this[sHandler]) {return false;}
	var oEvent = this[sHandler];
	node[oEvent.sHndlerInfoName] = oEvent.aHandlerInfo;
	oEvent.target = node;
	if( ! node.repeat && api.hasAttribute(node, "repeat")) {
		node.repeat = api.getAttribute(node, "repeat")
	}
	api.eventsHandler(oEvent);		
};

Template.prototype._getRepetitionIndex = function(node) {
	api.writeCall("in Template._getRepetitionIndex");
	var iCount = 0;
	var maxIndex = 0;
	while(node = node.previousSibling) {
		if(api.hasAttribute(node, "repeat")) {
			if(node.getAttribute("repeat") == "template") {break;}
			if(api.isInteger(node.getAttribute("repeat"))) {
				if(api.hasAttribute(node, "repeat-template")) {
					if(node.getAttribute("repeat-template") != this.sId) {break;}
				}
				node.setAttribute("repeat-template", this.sId);
				node.setAttribute("repeatTemplate", this.sId);
				iCount++;
				maxIndex = (maxIndex > node.repeat) ? maxIndex : node.repeat;
				this._nodeWalker4Buttons(node, "avoidSiblings");
			}
		}
	}
	this.iReplicaCount = iCount;
	return ((maxIndex > iCount) ? ++maxIndex : iCount);
};

Template.prototype._buildReplica = function() {
	api.writeCall("in Template._buildReplica");
	var nReplica = this.nOriginal.cloneNode("deep");
	this._nodeWalker4Increment(nReplica);
	this.aTemplateCollections.push(new TemplateCollection(nReplica, "skip orphans"));
	this._nodeWalker4Buttons(nReplica);
	return nReplica;
};

Template.prototype._incrementReplica = function(node) {
	api.writeCall("in Template._incrementReplica");
	if(node.nodeType != 1) {return;}
	var sAttribute, oldValue, newValue;
	var reBracketedId = new RegExp("^(.*?)\\[" + this.sId + "\\](.*?)$", "g");
	var reProtected = new RegExp("^&#xFEFF;(.*?)", "gi");
	for(var i=0; i < this.aAttributesToIncrement.length; i++) {
		sAttribute = this.aAttributesToIncrement[i];
		if(api.hasAttribute(node, sAttribute)) {
			sValue = node.getAttribute(sAttribute)
			if(sValue.match(reProtected)) {
				sValue = RegExp.$1;
			}
			else {
				while(sValue.match(reBracketedId)) {
					sValue = RegExp.$1 + this.repetitionIndex + RegExp.$2;
				}
			}
			api.setNodeValue(node, sAttribute, sValue);
		}
	}
};

Template.prototype._setButtons = function(node, sCurrentBlockTemplate, iCurrentBlockIndex) {
	api.writeCall("in Template._setButtons");
	if(sCurrentBlockTemplate && sCurrentBlockTemplate != this.sId) {return;}
	if(api.hasAttribute(node, "template") && node.getAttribute("template") != this.sId) {
		return;
	}
	if(api.hasAttribute(node, "type")) {
		var bNoMatch = false;
		switch(node.getAttribute("type")) {
			case "add":
				node.setAttribute("templateType", "add");
				api.setHandlers(node, "click", this, "addRepetitionBlock", iCurrentBlockIndex);
			break;
			case "remove":
				node.setAttribute("templateType", "remove");
				api.setHandlers(node, "click", this, "removeRepetitionBlock");
			break;
			case "move-up":
				node.setAttribute("templateType", "moveUp");
				api.setHandlers(node, "click", this, "moveRepetitionBlock", -1);
			break;
			case "move-down":
				node.setAttribute("templateType", "moveDown");
				api.setHandlers(node, "click", this, "moveRepetitionBlock", 1);
			break;
		}
		var templateType = api.getAttribute(node, "templateType");
		if(templateType) {
			if(document.all && node.nodeName.toLowerCase() == "input") {
				node = this._fixExpandoInputButton(node);
			}
			else {node.setAttribute("type", "button");}
			node.setAttribute("template", this.sId);
			this["a" + templateType.capitalize() + "Buttons"].push(node);
		}
	}
};

Template.prototype._updateButtons = function() {
	if(this.iReplicaCount <= this.repeatMin) {this._disenableButtons("Remove", true);}
	else {this._disenableButtons("Remove", false);}
	if(this.iReplicaCount >= this.repeatMax) {this._disenableButtons("Add", true);}
	else {this._disenableButtons("Add", false);}
	
};

Template.prototype._disenableButtons = function(type, bDisable) {
	var aButtons = this["a" + type.capitalize() + "Buttons"];
	for(var i = 0; i < aButtons.length; i++) {
		aButtons[i].disabled = bDisable;
	}
};

Template.prototype._nodeWalker4Increment = function(node) {
	api.writeCall("in Template._nodeWalker4Increment");
	if(node.nodeType == 1) {
		this._incrementReplica(node);
	}
	if(node.nextSibling) {this._nodeWalker4Increment(node.nextSibling);}
	if(node.hasChildNodes()) {this._nodeWalker4Increment(node.firstChild);}
};

Template.prototype._nodeWalker4Buttons = function(node, bAvoidSiblings, sCurrentBlockTemplate, iCurrentBlockIndex) {
	api.writeCall("in Template._nodeWalker4Buttons");
	if(this._isReplicationBlock(node)) {
		if(api.hasAttribute(node, "repeat-template")) {
			sCurrentBlockTemplate = node.getAttribute("repeat-template");
		}
		else {
			sCurrentBlockTemplate = this.sId;
		}
		iCurrentBlockIndex = node.getAttribute("repeat");
	}
	if(node.nodeType == 1) {
		this._setButtons(node, sCurrentBlockTemplate, iCurrentBlockIndex);
	}
	if( ! bAvoidSiblings && node.nextSibling) {
		this._nodeWalker4Buttons(node.nextSibling, false, sCurrentBlockTemplate, iCurrentBlockIndex);
	}
	if(this._isTemplate(node)) {return;}
	if(node.hasChildNodes()) {
		this._nodeWalker4Buttons(node.firstChild, false, sCurrentBlockTemplate, iCurrentBlockIndex);
	}
};

Template.prototype._getWithDefault = function(node, sAttribute, iDefault, sMinOrMax) {
	var result = iDefault;
	if(api.hasAttribute(node, sAttribute)) {
		var iCurrentValue = api.getAttribute(node, sAttribute);
		switch(sMinOrMax) {
			case "min":
				result = (iCurrentValue > iDefault) ? iCurrentValue : iDefault;
			break
			case "max":
				result = (iCurrentValue < iDefault) ? iCurrentValue : iDefault;		
			break;
			default:
				result = iCurrentValue;
			break;
		}
	}
	return result;
};

Template.prototype._isReplicationBlock = function(node) {
	var value = api.getAttribute(node, "repeat");
	if(api.isInteger(value)) {return true;}
	return false;
};

Template.prototype._isTemplate = function(node) {
	api.writeCall("in Template._isTemplate");
	if(api.getAttribute(node, "repeat") == "template") {return true;}
	return false;
};

Template.prototype._insertBlock = function(nReplica, index) {
	api.writeCall("in Template._insertBlock");
	var bDone = false;
	var nCurrent = this.nPlaceholder;
	do {
		if(nCurrent.nodeType == 1) {
			if( api.hasAttribute(nCurrent, "repeatTemplate")) {
				if(nCurrent.getAttribute("repeattemplate") == this.sId) {
					if(! api.isInteger(index) || nCurrent.getAttribute("repeat") == index) {
						this.parentNode.insertBefore(nReplica, nCurrent.nextSibling);
						bDone = true;
						break;
					}
				}
			}
		}
	}
	while(nCurrent = nCurrent.previousSibling);
	if(! bDone) {this.parentNode.insertBefore(nReplica, this.nPlaceholder);}
};

Template.prototype._fixExpandoInputButton = function(nOld) {
	api.writeCall("in Templates._fixExpandoInputButton");
	var oSkipOver = {type:1, style:1};
	var nNew = api.newNode("button");
	nNew.className = nOld.className;	// because the class attribute is always undefined in IE
	var sAttribute, sValue;
	var aAttributeNames = nOld.attributes;
	for(var i = 0; i < aAttributeNames.length; i++) {
		sAttribute = aAttributeNames[i].name;
		if(oSkipOver[sAttribute]) {continue;}
		sValue = api.getAttribute(nOld, sAttribute);
		if(sValue == "null" || sValue == undefined) {continue;}
		if(typeof sValue != "object") {
			api.setNodeValue(nNew, sAttribute, sValue);
		}
		else if(api.isArray(sValue)) {
			var aTemp = sValue.shallowCopy();
			api.setNodeValue(nNew, sAttribute, aTemp);
		}
	}
	nOld.parentNode.insertBefore(nNew, nOld);
	nOld.parentNode.removeChild(nOld);
	return nNew;
};

