/* MHControls.js
   (c) 2010, Metric Halo
   
   Implementation of polymorphic control objects
   Requires prototype to be included first


 */
 
/**********************************************************************
	Global Functions
**********************************************************************/
 
function hookEvent(element, eventName, callback)
{
	if(typeof(element) == "string")
		element = document.getElementById(element);
	if(element == null)
		return;
	if(element.addEventListener)
	{
		if(eventName == 'mousewheel')
			element.addEventListener('DOMMouseScroll', callback, false);  
		element.addEventListener(eventName, callback, false);
	}
	else if(element.attachEvent)
		element.attachEvent("on" + eventName, callback);
}

function unhookEvent(element, eventName, callback)
{
	if(typeof(element) == "string")
		element = document.getElementById(element);
	if(element == null)
		return;
	if(element.removeEventListener)
	{
		if(eventName == 'mousewheel')
			element.removeEventListener('DOMMouseScroll', callback, false);  
		element.removeEventListener(eventName, callback, false);
	}
	else if(element.detachEvent)
		element.detachEvent("on" + eventName, callback);
}

function cancelEvent(e)
{
	e = e ? e : window.event;
	if(e.stopPropagation)
		e.stopPropagation();
	if(e.preventDefault)
		e.preventDefault();
	e.cancelBubble = true;
	e.cancel = true;
	e.returnValue = false;
	return false;
}

function dolog(x) {
	$('log').innerHTML = x;
}


/**********************************************************************
	MHControl
**********************************************************************/

 var MHControl = Class.create({
 	initialize: function(min, max, step, val, cmin, cmax) {
 		
 		if (min) 
 			this.min = min;
 		else 
 			this.min = 0;
 			
 		if (max)
	 		this.max = max;
 		else 
	 		this.max = 1;

 		if (step)
 			this.step = step;
 		else 
 			this.step = 0.0001;

 		if (val)
	 		this.val = val;
 		else 
	 		this.val = 0;

 		if (cmin)
 			this.cmin = cmin;
 		else 
 			this.cmin = 1;

 		if (cmax)
 			this.cmax = cmax;
 		else 
 			this.cmax = 2;
 			
 		this.cval = this.valToCVal(this.val);
 		this.defaultVal = this.val; 		
		this.ignoreChangeStatusTimeout = null;
		this.currentlyTracking = false;
		this.reporterField = null;
		this.fine = false;
 	},

	clearChangeStatusTimeout: function() {
		ignoreChangeStatusTimeout = null;
	},

	setReporterField : function(field) {
 		if (typeof(field) == "string") field = $(field);
 		this.reporterField = field;
 		this.updateDisplay();
 	},

	handleMouseWheel : function(e) {
		e = e ? e : window.event;
		var element = e.element();
		var wheelData = e.detail ? e.detail * -1 : (e.wheelDeltaY - e.wheelDeltaX) / 10;
		
			//do something
		if (this.ignoreChangeStatusTimeout) {
			clearTimeout(this.ignoreChangeStatusTimeout);
		}
		this.ignoreChangeStatusTimeout = setTimeout(this.clearChangeStatusTimeout.bind(this), 100);
		
		this.setVal(this.val + wheelData * this.step, true);
		
		return cancelEvent(e);
	},

	// subclasses must override 	
 	updateDisplay : function() {
 		if (this.reporterField) {
			var valText = "";
			var val = this.val;
			
			if (val >= 0) {
				valText = ""+Math.floor(val)+"."+ ( Math.floor( (val - Math.floor(val)) * 10));
			} else {
				valText = "-"+Math.floor(-val)+"."+ ( Math.floor( (-val - Math.floor(-val)) * 10));
			}
			 		
 			this.reporterField.value = valText;
 			this.reporterField.select();
 		}
 	},

	// subclasses must override 	
 	action : function() {
 		console.log("Action Method Should be Overriden!");
 	},
 	
 	valToCVal : function(val) {
 		val = ((val - this.min)/(this.max - this.min)) * (this.cmax - this.cmin) + this.cmin;
		if (val < this.cmin) val = this.cmin;
		if (val > this.cmax) val = this.cmax;
		
		return val;
 	},
 	
 	cValToVal : function(val) {
 		val = ((val - this.cmin)/(this.cmax - this.cmin)) * (this.max - this.min) + this.min;
		if (val < this.min) val = this.min;
		if (val > this.max) val = this.max;
		
		return val; 		
 	},
 	
 	quantizeVal : function(val) {
 		if (this.step != 0) {
 			val = val/this.step;
 			val = Math.round(val) * this.step;
 			if (val < this.min) val = this.min;
 			if (val > this.max) val = this.max;
 		}
 		
 		return val;
 	},
 	
 	setVal : function(newVal, doAction) {
 		var doIt = true;
 		
 		if (doAction) {
 			doIt = true;
 		} else {
 			doIt = ( (this.ignoreChangeStatusTimeout == null)  && (this.currentlyTracking == false) );
 		}
 			
 		if (doIt) {
			newVal = this.quantizeVal(newVal);
			if (newVal != this.val) {
				this.val = newVal;
				this.cval = this.valToCVal(newVal);
				this.updateDisplay();
				if (doAction && this.action) {
					this.action();
				}
			}
		}
 	},

 	setControlVal : function(newVal, doAction) {
 		var doIt = true;
 		
 		if (doAction) {
 			doIt = true;
 		} else {
 			doIt = ( (this.ignoreChangeStatusTimeout == null)  && (this.currentlyTracking == false) );
 		}
 			
 		
 		if (doIt) {
 			if (newVal < this.cmin) newVal = this.cmin;
	 		if (newVal > this.cmax) newVal = this.cmax;
			if (newVal != this.cval) {
				this.val = this.quantizeVal(this.cValToVal(newVal));
				this.cval = newVal;
				this.updateDisplay();
				if (doAction && this.action) {
					this.action();
				}
			}
		}
 	},
 	
 	getVal : function() {
 		return this.val;
 	},
 	
 	getControlVal : function() {
 		return this.cval;
 	},
 	
 	/////////////////////////////////////////////////////////
 	// 
 	// generic mouse handling
 	//
 	/////////////////////////////////////////////////////////
	
	respondToTouchStart : function(event) {
		var me = this; 
		dolog("Got TouchStart");
		document.ontouchmove	=	me.respondToTouchMoved.bind(this);
		document.ontouchend		=	me.respondToTouchEnd.bind(this);
		document.ontouchcancel	=	me.respondToTouchCancel.bind(this);
		document.body.focus();
		me.startX = event.touches[0].pageX;
		me.startY = event.touches[0].pageY;
		me.startVal = me.val;
		me.startCVal = me.cval;
		this.currentlyTracking = true;
		event.preventDefault();
		return false;
	},
	respondToTouchMoved : function(event) {
		var me = this; 
		dolog("Got TouchMoved ("+event.touches[0].pageX+", "+event.touches[0].pageY+")");
		var delta = ((event.touches[0].pageX - me.startX) + (-event.touches[0].pageY + me.startY))*me.step;
		
		if (this.fine) {
			delta = 0.1 * delta;
		}
		
		if (delta > 0 && delta < me.step) delta = me.step;
		if (delta < 0 && delta > -me.step) delta = -me.step;
		
		val = me.startVal;
		val = (val + delta);
		me.setVal(val, true);
		event.preventDefault();
		return false;
	},
	respondToTouchCancel : function(event) {
		var me = this; 
		dolog("Got TouchCancel");
		document.ontouchmove	=	null;
		document.ontouchend		=	null;
		document.ontouchcancel	=	null;
		this.currentlyTracking = false;
		me.action();	// force a final update
		event.preventDefault();
		return false;
	},
 	
	respondToTouchEnd : function(event) {
		var me = this; 
		dolog("Got TouchEnd");
		document.ontouchmove	=	null;
		document.ontouchend		=	null;
		document.ontouchcancel	=	null;
		this.currentlyTracking = false;
		me.action();	// force a final update
		event.preventDefault();
		return false;
	},
	
  	respondToMouseDown:function(event) {
		var me = this; 
		if (event.altKey) {
			me.setVal(this.defaultVal, true);
		} else {
			if (event.metaKey) {
				me.fine = true;
			} else {
				me.fine = false;
			}
			me.startX = event.pointerX();
			me.startY = event.pointerY();
			me.startVal = me.val;
			me.startCVal = me.cval;
			document.onmousemove	=	me.respondToMouseMoved.bind(this);
			document.onmouseup		=	me.respondToMouseUp.bind(this);
			document.body.focus();
			this.currentlyTracking = true;
		}
		return false;
	},
 	
 	// handles X-Y motion by default
 	// override for slider
	respondToMouseMoved : function(event) {
		var me = this;

		var delta = ((event.pointerX() - me.startX) + (-event.pointerY() + me.startY))*me.step;
		
		if (this.fine) {
			delta = 0.1 * delta;
		}
		
		if (delta > 0 && delta < me.step) delta = me.step;
		if (delta < 0 && delta > -me.step) delta = -me.step;
		
		val = me.startVal;
		val = (val + delta);
		me.setVal(val, true);
	},

	respondToMouseUp: function(event) {
		var me = this; 
		document.onmousemove=null;
		document.onmouseup=null;
		this.currentlyTracking = false;
		me.action();	// force a final update
	},
 }
);

/**********************************************************************
	MHFrameKnob : MHControl
**********************************************************************/
 
var MHFrameKnob = Class.create(
	MHControl,
	{
 	initialize: function($super, element, min, max, step, val, cmin, cmax, frameSrc) {
 		$super(min, max, step, val, cmin, cmax);
 		if (typeof(element) == "string") element = $(element);
		this.element = element;
		this.images = new Array;
		var i;
		for (i=cmin; i<= cmax; i++) {
			this.images[i] = new Image();
			this.images[i].src = "resources/"+frameSrc+"/"+this.formatFrameNumber(i)+".png";
		}
		
		if (this.element) {
			this.element.mhcontrol = this;
			
			this.element.src = this.images[Math.floor(this.cval)].src;
			this.element.observe('mousedown', this.respondToMouseDown.bind(this));
			this.element.observe('touchstart', this.respondToTouchStart.bind(this));
			this.element.observe('touchmove', this.respondToTouchMoved.bind(this));
			this.element.observe('touchend', this.respondToTouchEnd.bind(this));
			this.element.observe('touchcancel', this.respondToTouchCancel.bind(this));
			hookEvent(this.element, 'mousewheel', this.handleMouseWheel.bind(this));			
		}
 	},
 		
	formatFrameNumber :	function (i) {
		if (i <10) {
			return "0"+(""+i);
		} else  {
			return (""+i);
		}
	},
	
 	updateDisplay : function($super) {
 		$super();
 		if (this.element) {
 			var image = this.images[Math.floor(this.cval)];
 			if (image) {
				this.element.src = image.src;
			}
		}
 	},
}
);


/**********************************************************************
	MHBigKnob : MHFrameKnob : MHControl
**********************************************************************/

var MHBigKnob = Class.create(
	MHFrameKnob,
	{
 	initialize: function($super, element, min, max, step, val) {
 		$super(element, min, max, step, val, 1, 53, "med_knob_line");
 	},
 }
 );
 

/**********************************************************************
	MHHorizontalSlider : MHControl
**********************************************************************/

var MHHorizontalSlider = Class.create(
	MHControl,
	{
 	initialize: function($super, element, min, max, step, val) {
 		if (typeof(element) == "string") element = $(element);
		this.element = element;
		var elComponents = element.descendants();

		this.thumb = elComponents[0];
		this.lb = elComponents[2];
		this.rb = elComponents[3];
		this.cwidth = this.lb.width + this.rb.width;

 		$super(min, max, step, val, 0, this.cwidth);
		
		if (this.element) {
			this.element.mhcontrol = this;
			this.thumb.observe('mousedown', this.respondToMouseDown.bind(this));
			hookEvent(this.element, 'mousewheel', this.handleMouseWheel.bind(this));			
		}
 	},
 		
	respondToMouseMoved : function(event) {
		var me = this;

		var delta = ((event.pointerX() - me.startX));
		
		if (this.fine) {
			delta = 0.1 * delta;
		}

		val = me.startCVal;
		val = (val + delta);
		
		
		me.setControlVal(val, true);
	},

	
 	updateDisplay : function($super) {
 		$super();
 		if (this.element) {
 			this.lb.width = this.cval;
 			this.rb.width = this.cwidth - this.cval;
			this.thumb.style.left = this.cval + "px";
		}
 	},
}
);

/**********************************************************************
	MHVerticalSlider : MHControl
**********************************************************************/

var MHVerticalSlider = Class.create(
	MHControl,
	{
 	initialize: function($super, element, min, max, step, val) {
 		if (typeof(element) == "string") element = $(element);
		this.element = element;
		var elComponents = element.descendants();

		this.thumb = elComponents[0];
		this.lb = elComponents[2];
		this.rb = elComponents[3];
		this.cwidth = this.lb.height + this.rb.height;

 		$super(min, max, step, val, 0, this.cwidth);
		
		if (this.element) {
			this.element.mhcontrol = this;
			this.thumb.observe('mousedown', this.respondToMouseDown.bind(this));
			hookEvent(this.element, 'mousewheel', this.handleMouseWheel.bind(this));			
		}
 	},
 		
	respondToMouseMoved : function(event) {
		var me = this;

		var delta = -((event.pointerY() - me.startY));
		
		if (this.fine) {
			delta = 0.1 * delta;
		}

		val = me.startCVal;
		val = (val + delta);
		
		
		me.setControlVal(val, true);
	},

	
 	updateDisplay : function($super) {
 		$super();
 		if (this.element) {
 			this.lb.height = this.cwidth - this.cval;
 			this.rb.height = this.cval;
			this.thumb.style.top = (this.cwidth - this.cval - 15) + "px";
		}
 	},
}
);



/**********************************************************************
	MHButton
**********************************************************************/

var MHButton = Class.create(
	{
		initialize: function(element, imageSrc, onName, isOn) {
			if (typeof(element) == "string") element = $(element);
			this.element = element;
			this.unclicked = new Image;
			this.clicked = new Image;
			this.on = new Image;
			if (isOn) 
				this.state = isOn;
			else
				this.state = false;
			
			this.unclicked.src = "resources/" + imageSrc + "/Unclicked.png";
			this.clicked.src = "resources/" + imageSrc + "/Clicked.png";
			this.on.src = "resources/" + imageSrc + "/" + onName + ".png";
			
			this.boundMouseUp = this.respondToMouseUp.bind(this);
			if (this.element) {
				this.element.mhcontrol = this;
				this.refreshBackground();
				element.observe('mousedown', this.respondToMouseDown.bind(this));
				element.observe('mouseup', this.boundMouseUp);
				element.observe('mouseover', this.respondToMouseOver.bind(this));
				element.observe('mouseout', this.respondToMouseOut.bind(this));
			}
			this.hilight = false;
			this.tracking = false;
			
		},
		
		
		setVal : function(newState, doAction) {
			if (newState != this.state) {
				this.state = newState;
				this.refreshBackground();
				if (doAction) {
					this.action();
				}
			}
		},
		
		getVal : function() {
			return this.state;
		},
		
		setBackgroundImage : function(image) {
			this.element.setStyle( {backgroundImage: "url(" + image.src + ")" });
		},
		
		refreshBackground : function() {
			if (this.state) {
				this.setBackgroundImage(this.on);
			} else {
				this.setBackgroundImage(this.unclicked);
			}		
		},
		
		respondToMouseDown : function(event) {
			this.setBackgroundImage(this.clicked);
			
			this.hilight = true;
			this.tracking = true;
			
			document.body.focus();
			document.observe('mouseup', this.boundMouseUp);
			
			return false;
		},
		respondToMouseUp : function(event) {
			if (this.hilight) {
				// call action method
				this.state = !this.state;
				this.action();	
			}

			this.refreshBackground();
			
			document.stopObserving('mouseup', this.boundMouseUp);
			this.hilight = false;	
			this.tracking = false;
			
		},

		respondToMouseOver : function(event) {
			if (this.tracking) {
				this.setBackgroundImage(this.clicked);
				this.hilight = true;			
			}
			
		},

		respondToMouseOut : function(event) {
			if (this.tracking) {
				this.refreshBackground();
				this.hilight = false;
			}
		},
		
		action : function() {
			// handle action
			console.log("Button Action: " + this.state);
		}
		
	}
);

