//therendStudio 2009
//V 0.1.1

var MC = {
	_debugLevel: 0,
	setDebugLevel: function(dLevel){
		MC._debugLevel = dLevel;
	},
	trace: function(msg){
		if(MC._debugLevel == 2)
			alert(msg);
	},
	_classes:{Components:{},Behaviors:{}},
	_components: [],
	_triggers:[],
	Utils:{},
	Adapter:{}
};
MC.Class = function(bases,constructor){
	if(constructor)
		var cls = constructor;
	else	//No constructor - call the default base constructors
		if(bases && bases.length)
			if(bases.length == 1)	//one base - pass the arguments
				var cls = function(){bases[0].apply(this,arguments);}
			else	//multiple bases - default constructors
				var cls = function(){
					for(var ind=0;ind < bases.length;ind++)
						bases[ind].apply(this);
				}
		else	//No base
			var cls = function(){};
	//Inherit class properties
	if(bases && bases.length){
		for(var ind=0;ind < bases.length;ind++){
			for(var prop in bases[ind].prototype)
				cls.prototype[prop] = bases[ind].prototype[prop];
		}
	}
	
	cls.classProp = function(mtdName,value){
		if(typeof value != 'undefined')
			return cls.prototype[mtdName] = value;
		return cls.prototype[mtdName];
	}
	cls.classProp('setClassProp',function(mtdName,value){
		return cls.prototype[mtdName] = value;
	});
	return cls;
}
MC.Utils.withEach = function(arr,callback){
	for(var ind = 0;ind < arr.length;ind++)
		callback.call(arr[ind],ind,arr);
};
MC.Utils.getObjProp = function(obj,prop,defValue){
	return typeof obj[prop] != 'undefined' ? obj[prop] : defValue;
};
MC.Utils.decodePluses = function(str){	//[+]->[ ], [\+]->[+]
	return str.replace(/\\\+/g,"\0").replace(/\+/g,' ').replace(/\0/g,'+')
};
MC.Utils.mergeArrays = function(baseArray,overrideArray){
	var resArray = [];
	for(var prop in baseArray)
		resArray[prop] = baseArray[prop];
	for(var prop in overrideArray)
		resArray[prop] = overrideArray[prop];
	return resArray;
};
MC.Utils.validateEnumValue = function(value,enList,message){
	for(var ind = enList.length;ind;ind--)
		if(enList[ind-1] == value)
			return true;
	throw message.replace('%',value);
};
MC.Utils.validateDefined = function(value,message){
	if(typeof value == 'undefined')
		throw message;
};
MC.Utils.describeElement = function(node){
	if(!node.nodeType || node.nodeType != 1)
		throw node + " is not an element node";
	return "\n[\n\tnodeName: "+
		node.nodeName +
		"\n\tid: " +
		node.id +
		"\n\tclassName: " +
		node.className +
		"\n\ttitle: " +
		node.title+
		"\n\tparentNodeName: " +
		node.parentNode.nodeName +
		"\n]\n";
}
MC.Utils.atUnload = function(eventHandler){	//Gets executed when the window un(re)loads
	MC.Adapter.atEvent(window,'unload',eventHandler);
};
MC.Utils.log =function (message) {
    if (!MC.Utils.log.window_ || MC.Utils.log.window_.closed) {
        var win = window.open("", null, "width=400,height=200," +
                              "scrollbars=yes,resizable=yes,status=no," +
                              "location=no,menubar=no,toolbar=no");
        if (!win) return;
        var doc = win.document;
        doc.write("<html><head><title>Debug Log</title></head>" +
                  "<body></body></html>");
        doc.close();
        MC.Utils.log.window_ = win;
    }
    var logLine = MC.Utils.log.window_.document.createElement("div");
    logLine.appendChild(MC.Utils.log.window_.document.createTextNode(message));
    MC.Utils.log.window_.document.body.appendChild(logLine);
};
MC.Utils.stopEvent = function(e) {
    if (!e) e = window.event;
    if (e.stopPropagation) {
        e.stopPropagation();
    } else {
        e.cancelBubble = true;
    }
}
MC.Utils.cancelEvent = function(e) {
    if (!e) e = window.event;
    if (e.preventDefault) {
        e.preventDefault();
    } else {
        e.returnValue = false;
    }
}
MC.Adapter.atEvent = function(element,eventName,eventHandler){  //Registers an event callback
	if(element.addEventListener)
		element.addEventListener(eventName,eventHandler,false);
	else
		if (element.attachEvent)
			element.attachEvent('on'+eventName,eventHandler);
		else
			throw 'No event handler interface found';
};

MC._classes.Behaviors.Base = new MC.Class(null,function(element,params,necessaryParams,behaviorName){
	var self = this;
	this._el = element;
	this._bhName = behaviorName;
	MC.Utils.withEach(necessaryParams,function(){
		if(typeof params[this] == 'undefined')
			self.throwErr("Missing `"+this+"` parameter");
	});
	this._params = params;
});
MC._classes.Behaviors.Base.classProp('getParam',function(prmName,defVal){
	return typeof this._params[prmName] != 'undefined' ? this._params[prmName] : defVal;
});
MC._classes.Behaviors.Base.classProp('throwErr',function(errMsg){
	throw errMsg + " for behavior "+this._bhName+" of element: " + MC.Utils.describeElement(this._el);
});

MC._classes.Behaviors.EventDriven = new MC.Class([MC._classes.Behaviors.Base],function(element,params,necessaryParams,behaviorName){
	MC._classes.Behaviors.Base.call(this,element,params,necessaryParams,behaviorName);
	var self = this;
	if(element.nodeName == 'A' && typeof params['voidLink'] != 'undefined')	//#TODO:put in the base class
		element.href = 'javascript:void(0);';
	MC.Utils.withEach(this.getParam('event','click').split(','),function(){
		var evt = this.split(':');
		var keys = evt[0].substr(0,3) == 'key' && typeof evt[1] != 'undefined' ? evt[1].split(';') : null;
		//#TODO:mouse buttons
		MC.Adapter.atEvent(element,evt[0],function(e){
			if(keys && keys.indexOf(String(e.which)) == -1)
				return;
			return self.onEvent(e);
		});
	});
});
MC._classes.Behaviors.EventDriven.classProp('onEvent',function(e){throw 'MC._classes.Behaviors.EventDriven.onEvent(): Not Implemented';});

MC._classes.Behaviors.InputFormat = new MC.Class([MC._classes.Behaviors.Base],function(element,params){
	MC._classes.Behaviors.Base.call(this,element,params,['type'],'InputFormat');
	var self = this;
	self._oldValue = element.value;	//#FIXME: no need of oldValue
	self._re = [];
	self._range = typeof params['range'] != 'undefined' ? params['range'].split(',') : undefined;
	self._invalidClass = MC.Utils.getObjProp(params,'invalidClass','mc_invalidInputFormat');
	if((element.nodeName != 'INPUT' || element.type != 'text') && element.nodeName != 'TEXTAREA')
		throw "Only INPUT[type=text] and TEXTAREA elements can have the InputFormat behavior [node:" + element.nodeName + "]";
	self._validator = MC.Utils.getObjProp(self.__validatorTypes,params['type'],null);
	if(!self._validator)
		throw "Unregistered InputFormat behavior type: " + self._type;
	self._validator('init',element);
	element.value = self._validator('format',element.value);
	MC.Adapter.atEvents(self._el,['select','focus','keyup'],function(event){
		if(!self._validator('validate',element.value))
			this.select();
	});
	MC.Adapter.atEvent(self._el,'blur',function(event){
		element.value = self._validator('format',element.value);
	});
	MC.Adapter.atEvent(self._el,'keyup',function(event){
		element.value = self._validator('format',element.value);
		if(!self._validator('validate',element.value))
			self._el.value = self._oldValue;
	});
	MC.Adapter.atEvent(self._el,'keypress',function(event){
		if(MC.Adapter.isSpecialPressKey(event))
			return true;	//Allow special keys
		self._oldValue = self._el.value;
		if(self._validator('filter',MC.Adapter.eventCharCode(event))){
			if(!self._validator('validate',element.value))	//Replace
				this.value = '';
			return true;
		}
		return MC.Adapter.eventStop(event);
	});
});
MC._classes.Behaviors.InputFormat.classProp('__validatorTypes',{});
MC._classes.Behaviors.InputFormat.classProp('__validatorClasses',{});
MC._classes.Behaviors.InputFormat.classProp('__validatorClasses').Base = new MC.Class([MC._classes.Behaviors.Base],function(){
});	//#TODO: make all validators subclasses,use the same classes for the InputValidate behavior
MC._classes.Behaviors.InputFormat.classProp('__validatorTypes').integer = function(action,arg){
//#FIXME: this is `natural` type
	switch(action){
		case 'init':
			this._re[0] = new RegExp(/^[0-9]*$/);
			break;
		case 'format':
			if(this._el.__mc_promptInactive)
				return arg;
			return arg.replace(/[^0-9]*/g,'').replace(/^0+/,'0');
		case 'filter':
			arg = String.fromCharCode(arg);
		case 'validate':
			return this._re[0].test(arg);
	}
	return true;
}
MC._classes.Behaviors.InputFormat.classProp('__validatorTypes').ip = function(action,arg){
	switch(action){
		case 'init':
			this._re[0] = new RegExp(/\d\d\d-\d\d\d-\d\d\d-\d\d\d$/);
			arg.maxLength = 15;
		case 'format':
			if(this._el.__mc_promptInactive)
				return arg;
			return "___-___-___-___";
		case 'validate':
			return this._re[0].test(arg.value);
		case 'filter':
			return arg >= 48 && arg <= 57;
	}
	return true;
}
MC._classes.Behaviors.InputFormat.classProp('__validatorTypes').oneword = function(action,arg){
	switch(action){
		case 'init':
			this._re[0] = new RegExp(/^\S*$/);
			break;
		case 'format':
			if(this._el.__mc_promptInactive)
				return arg;
			return arg.replace(/\s*/g,'');
		case 'filter':
			arg = String.fromCharCode(arg);
		case 'validate':	
			return this._re[0].test(arg);
	}
	return true;
}

MC._classes.Behaviors.InputFocus = function(element){
	if(element.focus)
		element.focus();
};

MC._classes.Behaviors.NoContextMenu = function(element){
	element.oncontextmenu = function() {return false;};
};

MC._classes.Behaviors.NonSelectable = function(element){
	element.onselectstart = function() {
        return false;
    };
    element.unselectable = "on";
    element.style.MozUserSelect = "none";
    element.style.cursor = "default";
};

MC._classes.Behaviors.InputValidate = new MC.Class([MC._classes.Behaviors.Base],function(element,params){
	MC._classes.Behaviors.Base.call(this,element,params,['type'],'InputValidate');
	var self = this;
	self._oldValue = element.value;
	self._re = [];
	self._invalidClassName = typeof params['invalidClassName'] != 'undefined' ? params['invalidClassName'] : 'mc_invalidInputFormat';
	if(typeof params['type'] == 'undefined')
		throw "Missing type parameter to InputValidate behavior of element: " + element;
	if((element.nodeName != 'INPUT' || element.type != 'text') && element.nodeName != 'TEXTAREA')
		throw "Only INPUT[type=text] and TEXTAREA elements can have the InputValidate behavior [node:" + element.nodeName + "]";
	self._validator = typeof self.__validatorTypes[params['type']] ? self.__validatorTypes[params['type']] : null;
	if(!self._validator)
		throw "Unregistered InputValidate behavior type: " + self._type;
	self.__validate();
	MC.Adapter.atEvents(self._el,['focus','keyup','blur','mousedown'],function(event){
		self.__validate();
	});
});
MC._classes.Behaviors.InputValidate.classProp('__validate',function(){
	MC.Adapter.manipulateElementClassName(this._el,
																	this._validator(this._el)
																	? 'remove' : 'add',this._invalidClassName);
});
MC._classes.Behaviors.InputValidate.classProp('__validatorTypes',{});
MC._classes.Behaviors.InputValidate.classProp('__validatorTypes').integer = function(element){
	if(typeof this._re[0] == 'undefined')
		this._re[0] = new RegExp(/^[0-9]*$/);
	return this._re[0].test(element.value);
}
MC._classes.Behaviors.InputValidate.classProp('__validatorTypes').ip = function(element,init){
	if(typeof this._re[0] == 'undefined')
		this._re[0] = new RegExp(/^(\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3})?$/);
	return this._re[0].test(element.value);
}

MC._classes.Behaviors.InputWholeSelect = function(element,params){
	if((element.nodeName != 'INPUT' || element.type != 'text' || element.type != 'password') && element.nodeName != 'TEXTAREA')
		throw "Only INPUT[type=text,password] and TEXTAREA elements can have the InputWholeSelect behavior [node:" + element.nodeName + "]";
	MC.Adapter.atEvent(element,'focus mouseup',function(){this.select();});
}

MC._classes.Behaviors.SelectChangeSubmit = function(element,params){
	if(element.nodeName != 'SELECT' || element.form == null)
		throw "Only SELECT elements inside forms can have the SelectChangeSubmit behavior [node:" + element.nodeName + "]";
	MC.Adapter.atEvent(element,'change',function(){element.form.submit();});
}

MC._classes.Behaviors.InputLimiter = function(element,params){
	if((element.nodeName != 'INPUT' || element.type != 'text') && element.nodeName != 'TEXTAREA')
		throw "Only INPUT[type=text] and TEXTAREA elements can have the InputLimiter behavior [node:" + element.nodeName + "]";
		var self = this;
	self._el = element;
	self._limit = typeof params['limit'] != 'undefined' ? params['limit'] : 255;
	self._elCounter	= typeof params['counterID'] != 'undefined' ? document.getElementById(params['counterID']) : null;
	if(self._elCounter)
		self._elCounter.innerHTML = self._limit - (typeof element.__mc_promptInactive == 'boolean' && element.__mc_promptInactive ? 0 : self._el.value.length);
	if(element.nodeName == 'INPUT')
		element.maxLength = self._limit;
	else
		MC.Adapter.atEvent(element,'keypress',function(event){
			if(MC.Adapter.isSpecialPressKey(event) && MC.Adapter.eventCharCode(event) != 13)
				return true;	//Allow special keys
			if(this.value.length >= self._limit)
				return MC.Adapter.eventStop(event);
		});
	MC.Adapter.atEvents(element,['keyup','blur','focus'],function(){self._trim(self);});
	MC.Adapter.atEvent(document,'mousemove',function(){self._trim(self);});
}
MC._classes.Behaviors.InputLimiter.prototype._trim = function(self){
	if(self._el.__mc_promptInactive){
		if(self._elCounter)
			self._elCounter.innerHTML = self._limit;
		return;
	}
	if(self._el.value.length > self._limit)
		self._el.value = self._el.value.substr(0,self._limit);
	if(self._elCounter)
		self._elCounter.innerHTML = self._limit - self._el.value.length;
};

MC._classes.Behaviors.InputPrompt = new MC.Class([MC._classes.Behaviors.Base],function(element,params){
//#TODO: put methods - getInputValue(), setInputValue(),reset()
	MC._classes.Behaviors.Base.call(this,element,params,['prompt'],'InputPrompt');
	if((element.nodeName != 'INPUT' || element.type != 'text') && element.nodeName != 'TEXTAREA')
		throw "Only INPUT[type=text] and TEXTAREA elements can have the InputPrompt behavior [node:" + element.nodeName + "]";
	var self = this;
	self._el = element;
	self._value = element.value;
	self._prompt = params['prompt'].replace(/[\+]/g,' ');
	if(self._prompt.charAt(0) == '@'){	//Take this attribute
		var attrName = self._prompt.length == 1 ? 'title' : self._prompt.substr(1);
		self._prompt =  element.getAttribute(attrName);	//Default to title
		if(this.getParam('clearAttribute'))
			MC.Adapter.changeElementAttribute(element,'clear',attrName);
	}else
		self._prompt = self._prompt.replace(/^\\/,'');	//#FIXME: unescape chars
	self._isActive = false;
	this.__mc_promptInactive = false;	//Internal flag
	self._mirrorField = null;
	if(typeof params['mirrorField'] != 'undefined'){
		if(params['mirrorField'].substr(0,1) == '#')
			self._mirrorField = document.getElementById(params['mirrorField'].substr(1));
		else if(element.form)
				self._mirrorField = element.form[params['mirrorField']];
			else
				throw "mirrorField property for InputPrompt behavior is not an id [name:"+params['mirrorField']+"] but the element [nodeType:" + element.nodeName + "] doesn't have a form";
		if(typeof self._mirrorField == 'undefined')
			throw "Couldn't resolve the mirrorField ["+params['mirrorField']+"] in InputPrompt behavior";
		if(self._mirrorField.nodeName != 'INPUT' && self._mirrorField.nodeName != 'TEXTAREA')
			throw "Only INPUT and TEXTAREA elements can be used in the InputPrompt behavior as mirrorFields [nodeType:" + self._mirrorField.nodeName + "]";
	}
	self._class = typeof params['class'] != 'undefined' ? params['class'] : 'mc_inactiveInputPrompt';
	MC.Adapter.atEvent(element,'keyup',function(){
		self._value = this.value;
		if(self._mirrorField)
			self._mirrorField.value = this.value;
	});
	MC.Adapter.atEvent(element,'change',function(){
		self._value = this.value;
	});
	//#FIXME: on menu paste value is lost
	// MC.Adapter.atEvent(element,'dblclick',function(){
		// this.form.submit();
	// });
	MC.Adapter.atEvent(element,'blur',function(){
		if(self._value != "")
			return;
		MC.Adapter.manipulateElementClassName(this,'add',self._class);
		this.__mc_promptInactive = true;
		this.value = self._prompt;
	});
	MC.Adapter.atEvent(element,'focus',function(){
		if(self._value != "")
			return;
		this.value = "";
		MC.Adapter.manipulateElementClassName(this,'remove',self._class);
		this.__mc_promptInactive = false;
	});
	MC.Utils.atUnload(function(){
		self._el.value = self._value;
	});
	if(element.form){
		if(params['resetOnSubmit'])
			MC.Adapter.atEvent(element.form,'submit',function(){
				self._el.value = self._value;
			});
		MC.Adapter.atEvent(element.form,'reset',function(event){
			MC.Adapter.manipulateElementClassName(self._el,'add',self._class);
			this.__mc_promptInactive = true;
			setTimeout(function(){
				self._el.value = self._prompt;
				self._value="";
				if(self._mirrorField)
					self._mirrorField.value="";
				},
			0);
		});
	}
	if(self._value == '')
		MC.Adapter.fireEvent(element,'blur')
});

MC._classes.Behaviors.Switch = new MC.Class([MC._classes.Behaviors.Base],function(element,params){
	MC._classes.Behaviors.Base.call(this,element,params,['targetSelector'],'InputValidate');
	var self = this;
	self._params = params;
	self._targetSelector = params['targetSelector'] == 'self' ? element : params['targetSelector'];
	self._action = typeof params['action'] != 'undefined' ? params['action'] : 'set';
	self._el = element;
	if(element.nodeName == 'A' && typeof params['voidLink'] != 'undefined')	//#TODO:put in the base class
		element.href = 'javascript:void(0);';
	var control = typeof params['control'] != 'undefined' ? params['control'] : 'cssProperties';
	if(typeof self.__controls[control] == 'undefined')
		throw "Control [type:"+control+"] not registered in Switch behavior of element: " + element;
	self.__controls[control](self,'init');
	var evt = (typeof params['event'] != 'undefined' ? params['event'] : 'click').split(':');
	self._keys = evt[0].substr(0,3) == 'key' && typeof evt[1] == 'string' ? evt[1].split(',') : null;
	//#TODO:mouse buttons
	MC.Adapter.atEvent(element,evt[0],function(event){
		if(self._keys && self._keys.indexOf(String(event.which)) == -1)
			return;
		self.__controls[control](self,'execute');
	});
});
MC._classes.Behaviors.Switch.prototype.__controls = {};
MC._classes.Behaviors.Switch.prototype.__controls.cssProperties = function(self,state){
	switch(state){
		case 'init':
			if(typeof self._params['properties'] == 'undefined')
				throw "Missing properties parameter to Switch behavior of element: " + self._el;
			self._params['properties'] = self._params['properties'].replace(/\+/g,' ');
			break;
		case 'execute':
			MC.Adapter.changeElementCssProperties(self._targetSelector,self._action,self._params['properties'],self._params['duration'],self._params['easing']);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.attribute = function(self,state){
	switch(state){
		case 'init':
			if(typeof self._params['name'] == 'undefined')
				throw "Missing name parameter to Switch behavior of element: " + self._el;
			if(typeof self._params['value'] == 'undefined')
				throw "Missing value parameter to Switch behavior of element: " + self._el;
			self._params['value'] = self._params['value'].replace(/\+/g,' ');
			break;
		case 'execute':
			MC.Adapter.changeElementAttribute(self._targetSelector,self._action,self._params['name'],self._params['value']);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.attributes = function(self,state){
	switch(state){
		case 'init':
			if(typeof self._params['attributes'] == 'undefined')
				throw "Missing attributes parameter to Switch behavior of element: " + self._el;
			self._params['attributes'] = self._params['attributes'].replace(/\+/g,' ');
			break;
		case 'execute':
			MC.Adapter.changeElementAttribute(self._targetSelector,self._action,self._params['name'],self._params['value']);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.className = function(self,state){
	switch(state){
		case 'init':
			if(typeof self._params['name'] == 'undefined')
				throw "Missing name parameter to Switch.className behavior of element: " + self._el;
			break;
		case 'execute':
			MC.Adapter.manipulateElementClassName(self._targetSelector,self._action,self._params['name']);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.visibility = function(self,state){
	switch(state){
		case 'init':
			self._action = typeof self._params['action'] != 'undefined' ? self._params['action'] : 'hide';
			break;
		case 'execute':
			switch(self._action){
				case 'hide':
					MC.Adapter.changeElementCssProperties(self._targetSelector,'set','visibility:hidden');
					break;
				case 'show':
					MC.Adapter.changeElementCssProperties(self._targetSelector,'set','visibility:visible');
					break;
				case 'toggle':
					MC.Adapter.changeElementCssProperties(self._targetSelector,'toggle','visibility:hidden');
					break;
				default:
					throw "Invalid Behaviors.Switch.visibility action ["+self._action+"] for element: " + self._el;
			}
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.display = function(self,state){
	switch(state){
		case 'init':
			self._action = typeof self._params['action'] != 'undefined' ? self._params['action'] : 'inline';
			break;
		case 'execute':
			switch(self._action){
				case 'none':
					MC.Adapter.changeElementCssProperties(self._targetSelector,'set','display:none');
					break;
				case 'block':
					MC.Adapter.changeElementCssProperties(self._targetSelector,'set','display:block');
					break;
				case 'inline':
					MC.Adapter.changeElementCssProperties(self._targetSelector,'set','display:inline');
					break;
				case 'toggle':
					MC.Adapter.changeElementCssProperties(self._targetSelector,'toggle','display:none');
					break;
				default:
					throw "Invalid Behaviors.Switch.visibility action ["+self._action+"] for element: " + self._el;
			}
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.opacity = function(self,state){
	switch(state){
		case 'init':
			if(typeof self._params['opacity'] == 'undefined')
				throw "Missing opacity parameter to Switch.opacity behavior of element: " + self._el;
			break;
		case 'execute':
			MC.Adapter.changeElementCssProperties(self._targetSelector,'set','opacity:'+self._params['opacity']);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.highlight = function(self,state){
	switch(state){
		case 'init':
			self._color = typeof self._params['color'] != 'undefined' ? self._params['color'] : '#FFFFFF';
			self._duration = typeof self._params['duration'] != 'undefined' ? self._params['duration'] : 500;
			self._count = typeof self._params['count'] != 'undefined' ? self._params['count'] : 1;
			break;
		case 'execute':
			MC.Adapter.effectHightlight(self._targetSelector,self._color,self._duration,self._count);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.destroy = function(self,state){
	switch(state){
		case 'init':
			break;
		case 'execute':
			MC.Adapter.destroyElements(self._targetSelector);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.create = function(self,state){
	switch(state){
		case 'init':
			break;
		case 'execute':
			MC.Adapter.destroyElements(self._targetSelector);
			break;
	}
}
MC._classes.Behaviors.Switch.prototype.__controls.content = function(self,state){
	switch(state){
		case 'init':
			self._action = typeof self._params['action'] != 'undefined' ? self._params['action'] : 'set';
			self._html = typeof self._params['html'] != 'undefined' ? MC.Utils.decodePluses(self._params['html']) : '';	//#TODO:add text attribute
			break;
		case 'execute':
			switch(self._action){
				case 'set':
					MC.Utils.withEach(MC.Adapter.selectElements(self._targetSelector),function(){
						this.innerHTML = self._html;
					})
					break;
				case 'clear':
					MC.Utils.withEach(MC.Adapter.selectElements(self._targetSelector),function(){
						this.innerHTML = '&nbsp;';
					})
					break;
				case 'empty':
					MC.Utils.withEach(MC.Adapter.selectElements(self._targetSelector),function(){
						this.innerHTML = '';
					})
					break;
				case 'toggle':
					MC.Utils.withEach(MC.Adapter.selectElements(self._targetSelector),function(){
						if(this.innerHTML == self._html)
							this.innerHTML = '&nbsp;';
						else
							this.innerHTML = self._html;
					})
					break;
				default:
					throw "Invalid Behaviors.Switch.content action ["+self._action+"] for element: " + self._el;
			}
			break;
	}
}

MC._classes.Behaviors.PopOpener = new MC.Class([MC._classes.Behaviors.EventDriven],function(element,params){
	//#TODO:center popup,close action
	this._url = params['url'].charAt(0) == '@' ? element.getAttribute(params['url'].substr(1)) : params['url']
	MC._classes.Behaviors.EventDriven.call(this,element,params,['url'],'PopOpener');
	if(element.nodeName == 'A' && params['url'] == '@href'){
		element.href = 'javascript:void(0);';
		element.target = '';	//#CHANGED
	}
	if(this.getParam('cursor',null))
		MC.Adapter.changeElementCssProperty(this._el,'set','cursor',this.getParam('cursor'));
});
MC._classes.Behaviors.PopOpener.classProp('onEvent',function(e){
	window.open(this._url,
						this.getParam('target','_blank'),
						this.getParam('winspecs','width=300,height=200')
						);
});
MC._classes.Behaviors.Overlay = new MC.Class([MC._classes.Behaviors.EventDriven],function(element,params){
	MC._classes.Behaviors.EventDriven.call(this,element,params,['action'],'Overlay');
});
MC._classes.Behaviors.Overlay.classProp('onEvent',function(e){
	switch(this.getParam('action')){
		case 'set':
			if(this.__elOverlay)
				return;
			this.setClassProp('__elOverlay',document.createElement('div'));
			this.__elOverlay.style.position="fixed";
			this.__elOverlay.style.backgroundColor=this.getParam("color","#555");
			this.__elOverlay.style.left=0;
			this.__elOverlay.style.top=0;
			this.__elOverlay.style.right=0;
			this.__elOverlay.style.bottom=0;
			this.__elOverlay.style.zIndex=this.getParam('zIndes',10);
			MC.Adapter.changeElementCssProperty(this.__elOverlay,'set','opacity',0);
			document.body.appendChild(this.__elOverlay);
			MC.Adapter.changeElementCssProperty(this.__elOverlay,this.getParam('duration',0) ? 'animate' : 'set','opacity',this.getParam('opacity',0.8),this.getParam('duration'));
			break;
		case 'remove':
			if(!this.__elOverlay)
				return;
			MC.Adapter.changeElementCssProperty(this.__elOverlay,this.getParam('duration',0) ? 'animate' : 'set','opacity',0,this.getParam('duration',0));
			var elOverlay = this.__elOverlay;
			setTimeout(function(){
				MC.Adapter.destroyElements(elOverlay);
			},this.getParam('duration',0));
			this.setClassProp('__elOverlay',null);
			break;
		default:
			this.throwErr("Invalid action parameter ["+this.getParam('action')+"]");
	}
});
MC._classes.Behaviors.Overlay.classProp('__elOverlay',null);

MC._classes.Behaviors.LoadScript = new MC.Class([MC._classes.Behaviors.EventDriven],function(element,params){
	MC._classes.Behaviors.EventDriven.call(this,element,params,['src'],'LoadScript');
});
MC._classes.Behaviors.LoadScript.classProp('onEvent',function(e){
	var e=document.createElement('script');
	e.setAttribute('language','javascript');
	e.setAttribute('src',this.getParam('src'));
	document.body.appendChild(e);
});

MC._classes.Components = {};

MC._classes.Components.Base = new MC.Class(null,
	function(meta,classParams){
		this._meta = meta;
		this._classParams = classParams;
		this.onCreate();
	}
);
MC._classes.Components.Base.classProp('getClassParam',function(prmName,defVal,undefErrString){
	if(typeof this._classParams[prmName] != 'undefined')
		return this._classParams[prmName];
	if(typeof undefErrString == 'string')
		throw undefErrString;
	return defVal;
});

MC._classes.Components.Base.classProp('onCreate',function(){});
MC._classes.Components.Base.classProp('getID',function(){return this._meta['id'];});

MC._classes.Components.Switch = new MC.Class([MC._classes.Components.Base],
	function(meta,classParams,slots){
		MC._classes.Components.Base.apply(this,meta,classParams);
		this._slots = slots;
	}
);
MC._classes.Components.Switch.classProp('InvokeSlot',function(slot,sgnParams){
	var params = this._classParams;
	if(slot !== null){
		if(typeof this._slots[slot] == 'undefined')
			throw "Invalid slot [name:"+slot+"] for component [id:"+this._meta['id']+"]";
		params = MC.Utils.mergeArrays(params,this._slots[slot].getParams(sgnParams));
	};
	params = MC.Utils.mergeArrays(params,sgnParams);
	this.Execute(params);
});
MC._classes.Components.Switch.classProp('Execute',function(params){
	switch(params['control']){
		case 'attribute':
			if(typeof params['selector'] == 'undefined')
				throw "Missing selector parameter invoking component [id:"+this._meta['id']+"]";
			MC.Utils.validateEnumValue(params['action'],['set','clear','toggle','alternate'],"Invalid action parameter [%] executing component [id:"+this._meta['id']+"]");
			MC.Adapter.changeElementAttribute(params['selector'],params['action'],params['name'],params['value']);
			break;
		case 'className':
			if(typeof params['selector'] == 'undefined')
				throw "Missing selector parameter invoking component [id:"+this._meta['id']+"]";
			MC.Utils.validateEnumValue(params['action'],['add','remove','toggle'],"Invalid action parameter [%] executing component [id:"+this._meta['id']+"]");
			MC.Adapter.manipulateElementClassName(params['selector'],params['action'],params['name']);
			break;
		case 'cssProperty':
			if(typeof params['selector'] == 'undefined')
				throw "Missing selector parameter invoking component [id:"+this._meta['id']+"]";
			MC.Utils.validateEnumValue(params['action'],['set','clear','toggle','animate','alternate'],"Invalid action parameter [%] executing component [id:"+this._meta['id']+"]");
			params['duration'] = typeof params['duration'] != 'undefined' ? params['duration'] : 500;
			MC.Adapter.changeElementCssProperty(params['selector'],params['action'],params['name'],params['value'],params['duration'],params['easing']);
			break;
		case 'cssProperties':
			if(typeof params['selector'] == 'undefined')
				throw "Missing selector parameter invoking component [id:"+this._meta['id']+"]";
			MC.Utils.validateEnumValue(params['action'],['set','clear','toggle','animate','alternate'],"Invalid action parameter [%] executing component [id:"+this._meta['id']+"]");
			params['duration'] = typeof params['duration'] != 'undefined' ? params['duration'] : 500;
			MC.Adapter.changeElementCssProperties(params['selector'],params['action'],params['properties'],params['duration'],params['easing']);
			break;
		default:
			throw "Invalid control parameter ["+params['control']+"] executing component [id:"+this._meta['id']+"]";
	}
});

MC._classes.Components.Clock = new MC.Class([MC._classes.Components.Base]);
MC._classes.Components.Clock.classProp('onCreate',function(){
	var self = this;
	self._date = self.getClassParam('startTimestamp',null) !== null
									? (new Date(isNaN(self.getClassParam('startTimestamp')) ? self.getClassParam('startTimestamp') : Number(self.getClassParam('startTimestamp'))))
									: new Date();
	var update = function(now){
		if(now !== true)	//Don't update date obj
			self._date.setTime(self._date.getTime() + Number(self.getClassParam('updateInterval',1000)));
		MC.Utils.withEach(MC.Adapter.selectElements(self._classParams['selector']),function(){
			this.innerHTML = self._date.toString();
		});
	};
	update(true);
	setInterval(update,this.getClassParam('updateInterval',1000));
});
MC._classes.Components.NewPassword = new MC.Class([MC._classes.Components.Base]);
MC._classes.Components.NewPassword.classProp('onCreate',function(){
	var self = this;
	this._elPw1 = document.getElementById(this.getClassParam('pw1ID',null,'Missing pw1ID class parameter for NewPassword component [id:'+this.getID()));
	if(!this._elPw1)	//#TODO: accept `applyOnewordFormatting`
		throw "Element not found in NewPassword component [id:" + this.getClassParam('pw1ID') + "]";
	if((this._elPw1.nodeName != 'INPUT' || this._elPw1.type != 'text' && this._elPw1.type != 'password') && this._elPw1.nodeName != 'TEXTAREA')
		throw "Only INPUT[type=text,password] and TEXTAREA elements can be selected with pw1ID in the NewPassword component. Got " + MC.Utils.describeElement(this._elPw1) + " instead";
	if(this.getClassParam('pw2ID',null)){
		this._elPw2 = document.getElementById(this.getClassParam('pw2ID'));
		if(!this._elPw2)
		throw "Element not found in NewPassword component [id:" + this.getClassParam('pw2ID') + "]";
		if((this._elPw2.nodeName != 'INPUT' || this._elPw2.type != 'text' && this._elPw2.type != 'password') && this._elPw2.nodeName != 'TEXTAREA')
			throw "Only INPUT[type=text] and TEXTAREA elements can be selected with pw2ID in the NewPassword component. Got " + MC.Utils.describeElement(this._elPw2) + " instead";
	}else
		this._elPw2 = null;
	this._elPwOld = document.getElementById(this.getClassParam('pwOldID',null));
	if(this._elPwOld && (this._elPwOld.nodeName != 'INPUT' || this._elPwOld.type != 'text' && this._elPwOld.type != 'password') && this._elPwOld.nodeName != 'TEXTAREA')
		throw "Only INPUT[type=text,password] and TEXTAREA elements can be selected with pw1ID in the NewPassword component. Got " + MC.Utils.describeElement(this._elPw1) + " instead";
	this._elEnabledButtonID = document.getElementById(this.getClassParam('enabledButtonID'));
	if((this._elEnabledButtonID.nodeName != 'INPUT' || this._elEnabledButtonID.type != 'submit' && this._elEnabledButtonID.type != 'reset' && this._elEnabledButtonID.type != 'button') && this._elEnabledButtonID.nodeName != 'BUTTON')
		throw "Only INPUT[type=submit,reset,button] and BUTTON elements can be selected with enabledButtonID in the NewPassword component. Got " + MC.Utils.describeElement(this._elPw1) + " instead";
	this._bMeasureStrength = this.getClassParam('measureStrength',null);
	if(this._bMeasureStrength){
		this._elStrengthIndicator = document.getElementById(this.getClassParam('strengthIndicatorID',null));
		MC.Adapter.atEvents(this._elPw1,['keyup','change'],function(){
			self._bStrengthOk = self.measurePasswordStrength(self._elPw1.value);
			var matchOk = true;
			if(self._elPw1 && self._elPw2)
				matchOk = self.checkPasswordMatch();
			if(self._elEnabledButtonID)
				self._elEnabledButtonID.disabled = !(self._bStrengthOk && matchOk);
		});
		this._minPasswordLength = this.getClassParam('minPasswordLength',0)
		this._minPasswordStrength = this.getClassParam('minPasswordStrength',0)
		this._elStrengthMsg = this.getClassParam('strengthMessageID') ? document.getElementById(this.getClassParam('strengthMessageID')) : null;
		this._elStrengthStylebox = this.getClassParam('strengthStyleboxID') ? document.getElementById(this.getClassParam('strengthStyleboxID')) : null;
		this._lastPwClassName = '';
		if(this._elStrengthStylebox || this._elStrengthMsg)
			MC.Adapter.manipulateElementClassName(this._elStrengthStylebox || this._elStrengthMsg,'add','mc_pwMsg');
		this._bStrengthOk = false;
	}else
		this._bStrengthOk = true;
	this._bMatchPasswords = this.getClassParam('matchPasswords',null);
	if(this._elPw2 && this._bMatchPasswords){
		this._elMatchIndicator = document.getElementById(this.getClassParam('matchIndicatorID',null,'Missing matchIndicatorID class parameter for NewPassword component [id:'+this.getID()));
		if(!this._elMatchIndicator)
			throw "Can't find element [id:"+ this.getClassParam('matchIndicatorID')+"]";
		MC.Adapter.atEvents(this._elPw2,['keyup','change'],function(){
			var matchOk = self.checkPasswordMatch();
			if(self._elEnabledButtonID);
				self._elEnabledButtonID.disabled = !(self._bStrengthOk && matchOk);
		});
		this._strNoMatchMsg = this.getClassParam('noMatchMsg',"Passwords don't match");
	};
	MC.Adapter.fireEvent(this._elPw1,'change');
	if(this._bMeasureStrength)
		MC.Adapter.fireEvent(this._elPw2,'change');
})
MC._classes.Components.NewPassword.classProp('measurePasswordStrength',function(password){
	//#FIXME: correct strength/invalid class/color mess
	var strengthClass = 0;	//No password
	var strength = 0;	//No password
	if(password.length){
		var nLCL = password.match(/[a-z]*/g).join('').substr(0,3).length;
		var nUCL = password.match(/[A-Z]*/g).join('').substr(0,3).length;
		var nN = password.match(/[0-9]*/g).join('').substr(0,3).length;
		var nSC = password.match(/[^a-zA-Z0-9]*/g).join('').substr(0,3).length;
		var strength = (password.length > 5 ? 1 : 0) + (nLCL + nUCL + nN + nSC) / 2 + (nLCL * nUCL ? 1 : 0) + ((nLCL + nUCL) * nN ? 1 : 0) + ((nLCL + nUCL + nN) * nSC ? 1 : 0) ;
		var strength = Math.floor(strength);
		if(password.length < this._minPasswordLength)	//Too short
			strengthClass = 0;
		else if(strength <= 4)	//Weak
			strengthClass = 1;
		else if(strength <= 6)	//Mediocre
			strengthClass = 2;
		else if(strength <= 8)	//Strong
			strengthClass = 3;
		else								//>8 - Very Strong
			strengthClass = 4;
		if(this._elStrengthMsg)
			MC.Adapter.changeElementCssProperty(this._elStrengthStylebox,'set','visibility','visible');
	}else
		if(this._elStrengthMsg)
			MC.Adapter.changeElementCssProperty(this._elStrengthStylebox,'set','visibility','hidden');
	if(this._elStrengthIndicator)
		this._elStrengthIndicator.innerHTML = strength;
	if(this._elStrengthMsg)
		this._elStrengthMsg.innerHTML = ['Password too short','Weak','Mediocre','Strong','Very Strong'][strengthClass] + " ("
			+ (function(){
				if(this._elPwOld && this._elPwOld.value.length && this._elPwOld.value == password)	/*modified*/
					return 'Unacceptable - same as old password';
				if(!strengthClass)
					return 'Unacceptable';	//Too short
				if(strength >= this._minPasswordStrength)
					return 'Acceptable';
				else
					return 'Unacceptable - too weak';
			}).call(this)
			+ ")";
	if(this._elStrengthStylebox || this._elStrengthMsg){
		if(this._lastPwClassName)
			MC.Adapter.manipulateElementClassName(this._elStrengthStylebox || this._elStrengthMsg,'remove',this._lastPwClassName);
		this._lastPwClassName = ['mc_pwTooShort','mc_pwWeak','mc_pwMediocre','mc_pwStrong','mc_pwVeryStrong'][strengthClass]
		MC.Adapter.manipulateElementClassName(this._elStrengthStylebox || this._elStrengthMsg,'add',this._lastPwClassName);
	}
	return strengthClass > 0 && strength >= this._minPasswordStrength && (!this._elPwOld || this._elPwOld.value != password);
});
MC._classes.Components.NewPassword.classProp('checkPasswordMatch',function(){
	var havePrompt = this._elPw1.__mc_promptInactive || this._elPw2.__mc_promptInactive;
	var haveValues = this._elPw1.value.length && this._elPw2.value.length;
	//#TODO:replace with show/hide
	this._elMatchIndicator.innerHTML = !havePrompt && haveValues && this._elPw1.value != this._elPw2.value ? this._strNoMatchMsg : '';
	MC.Adapter.manipulateElementClassName(this._elMatchIndicator,!havePrompt && haveValues && this._elPw1.value != this._elPw2.value ? 'add' : 'remove','mc_active');
	return !havePrompt && haveValues && this._elPw1.value == this._elPw2.value;
});

MC._classes.Trigger = function(element,signals){
	for(var eventName in signals)
		MC.Adapter.atEvent(element,eventName,function(){
			MC.Utils.withEach(signals[eventName],function(){
				this.Emit();
			});
		});
};
MC._classes.Slot = function(param_overrides,argument_overrides){
	this._param_overrides = param_overrides;
	this._argument_overrides = argument_overrides;
	this.getParams = function(sgnParams){
		var params = MC.Utils.mergeArrays(this._param_overrides,[]);
		for(var prop in this._argument_overrides)
			if(typeof sgnParams[this._argument_overrides[prop]] != 'undefined')
				params[prop] = sgnParams[this._argument_overrides[prop]];
		return params;
	};
};
MC._classes.Signal = function(componentID,slotName,params){
	this._component = MC._components[componentID];
	if(typeof this._component == 'undefined')
		throw "Unexisting componentID [" + componentID + "]";
	this._slotName = slotName;
	this._params = params;
	this.Emit = function(){
		try{
			MC.trace(this._slotName);
			this._component.InvokeSlot(this._slotName,this._params);
		}catch(e){
			var errMsg = "#Exception occured while invoking a slot [" + this._slotName + "] on component [id:" + this._component.getID() + "]#\n" +
								"=>{" + (typeof e == 'string' ? e : e.name + ":" + e.message) + "}\n" +
								"Signal params:\n";
			for(var param in this._params)
				errMsg += "\t" + param + ":[" + this._params[param] + "]\n";
			alert(errMsg);
		}
	}
};
MC._compileComponents = function(){
	MC.Utils.withEach(MC.Adapter.selectElements('.mc_component'),function(){
		var meta = [];//
		MC.Utils.withEach(MC.Adapter.selectElements('.meta',this),function(){
			meta[this.title] = MC.Adapter.strTrim(this.innerHTML);
		});
		if(typeof meta['id'] == 'undefined')
			throw "Component doesn't have an id meta info";
		var classParams = [];//
		var classMeta = MC.Adapter.selectElements('.component-class',this,true);
		MC.Utils.withEach(MC.Adapter.selectElements('.class-param',classMeta),function(){
			classParams[this.title] = MC.Adapter.strTrim(this.innerHTML);
		});
		var slots = [];//
		MC.Utils.withEach(MC.Adapter.selectElements('.slot',this),function(){
			var param_overrides = [];
			var argument_overrides = [];
			MC.Utils.withEach(MC.Adapter.selectElements('.param-override',this),function(){
				if(this.nodeName == 'CITE')	//Bind to the signal argument list
					argument_overrides[this.title] = MC.Adapter.strTrim(this.innerHTML);
				else
					param_overrides[this.title] = MC.Adapter.strTrim(this.innerHTML);
			});
			slots[this.title] = new MC._classes.Slot(param_overrides,argument_overrides);
		});
		var classProto = MC._classes.Components[classMeta.title];//
		if(typeof classProto == 'undefined')
			throw "Class [" + classMeta.title + "] referenced in component [" + meta['id'] + "] is undefined";
		MC._components[meta['id']] = new classProto(meta,classParams,slots);
	});
};
/*
	_connectTriggers: function(){
		MC.Utils.withEach(MC.Adapter.selectElements('.mc_trigger'),function(){
			var signals = [];
			var signalRE = /^mc_signal@(.\w+)#([\w_-]+)(\^[\w_-]+)?(\?([^?=]+=[^?=]+)+)?$/i;
			MC.Utils.withEach(this.className.split(/\s+/),function(){
				if(this.substr(0,9) != 'mc_signal')
					return;
				var sgnMatch = signalRE.exec(this);
				if(sgnMatch === null)
					throw 'Bad signal format [' + this + ']';
				var eventName = sgnMatch[1];
				var componentID = sgnMatch[2];
				var slot = typeof sgnMatch[3] != 'undefined' && sgnMatch[3].length ? sgnMatch[3].substr(1) : null;
				var params = [];
				if(typeof sgnMatch[4] != 'undefined' && sgnMatch[4].length)
					MC.Utils.withEach(sgnMatch[4].substr(1).split('&'),function(param){
						var paramPair = this.split('=');
						if(paramPair.length != 2)
							throw "Invalid signal param pair [" + this + "] (must be `prm=value`)";
						params[paramPair[0]] = paramPair[1];
					});
				if(typeof signals[eventName] == 'undefined')
					signals[eventName] = [];
				signals[eventName].push(new MC._classes.Signal(componentID,slot,params));
			});
			MC._triggers.push(new MC._classes.Trigger(this,signals));
		});
	},
*/
MC._compileBehaviors =function(){
		MC.Utils.withEach(MC.Adapter.selectElements('.mc_behaviors'),function(){	//#FIXME: no need of this class - use MA.$$classStartingWith()
			var behaviorRE = /^mc_behavior:(.\w+)(\?([^?=]+(=[^?=]+)?)+)?$/i;
			var element = this;
			var valid = false;
			MC.Utils.withEach(this.className.split(/\s+/),function(){
				if(this.substr(0,12) != 'mc_behavior:')
					return;
				valid = true;
				var bhvrMatch = behaviorRE.exec(this);
				if(bhvrMatch === null)
					throw 'Bad behavior format [' + this + ']';
				var behaviorName = bhvrMatch[1];
				if(typeof MC._classes.Behaviors[behaviorName] == 'undefined')
					throw "Invalid behavior name:" + behaviorName;
				var params = [];
				if(typeof bhvrMatch[2] != 'undefined' && bhvrMatch[2].length)
					MC.Utils.withEach(bhvrMatch[2].substr(1).split('&'),function(param){
						var paramPair = this.split('=');
						params[paramPair[0]] = paramPair.length >=  2 ? paramPair.slice(1).join('=') : true;
					});
				new MC._classes.Behaviors[behaviorName](element,params);
			});
			if(!valid)
				throw "The element [nodeName:"+this.nodeName+",className:"+this.className+"] has mc_behaviors class set but no valid mc_behavior:bhName?... argument strings found";
		});
	},
MC._onLoad = function(){
	MC._compileComponents();
	//MC._connectTriggers();
	MC._compileBehaviors();	//#FIXME:make public
};

MC.Adapter.atEvent(window,'load',MC._onLoad);
