// webkitdragdrop.js v1.0, Mon May 15 2010
//
// Copyright (c) 2010 Tommaso Buvoli (http://www.tommasobuvoli.com)
// No Extra Libraries are required, simply download this file, add it to your pages!
//
// To See this library in action, grab an ipad and head over to http://www.gotproject.com
// webkitdragdrop is freely distributable under the terms of an MIT-style license.


//Description
// Because this library was designed to run without requiring any other libraries, several basic helper functions were implemented
// 6 helper functons in this webkit_tools class have been taked directly from Prototype 1.6.1 (http://prototypejs.org/) (c) 2005-2009 Sam Stephenson

var webkit_tools = 
{
	//$ function - simply a more robust getElementById
		
	$:function(e)
	{
		if(typeof(e) == 'string')
		{
			return document.getElementById(e);
		}
		return e;
	},
	
	//extend function - copies the values of b into a (Shallow copy)
	
	extend:function(a,b)
	{
		for (var key in b)
		{
			a[key] = b[key];	
		}	
		return a;
	},
	
	//empty function - used as defaut for events
	
	empty:function()
	{
	
	},
	
	//remove null values from an array
	
	compact:function(a)
	{
		var b = []
		var l = a.length;
		for(var i = 0; i < l; i ++)
		{
			if(a[i] !== null)
			{
				b.push(a[i]);
			}
		}
		return b;
	},
	
	//DESCRIPTION
	//	This function was taken from the internet (http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/) and returns 
	//	the computed style of an element independantly from the browser
	//INPUT
	//	oELM (DOM ELEMENT) element whose style should be extracted
	//	strCssRule element
	
	getCalculatedStyle:function(oElm, strCssRule)
	{
		var strValue = "";
		if(document.defaultView && document.defaultView.getComputedStyle){
			strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
		}
		else if(oElm.currentStyle){
			strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
				return p1.toUpperCase();
			});
			strValue = oElm.currentStyle[strCssRule];
		}
		return strValue;
	},
	
	//bindAsEventListener function - used to bind events
	
	bindAsEventListener:function(f,object) 
	{
        var __method = f;
        return function(event) {
            __method.call(object, event || window.event);
        };
    },
    
    //cumulative offset - courtesy of Prototype (http://www.prototypejs.org)
    
    cumulativeOffset:function(element) 
    {
	    var valueT = 0, valueL = 0;
	    do {
	      valueT += element.offsetTop  || 0;
	      valueL += element.offsetLeft || 0;
	      if (element.offsetParent == document.body)
	        if (element.style.position == 'absolute') break;
	
	      element = element.offsetParent;
	    } while (element);
	
	    return {left : valueL, top : valueT};
  	},
  	
  	//getDimensions - courtesy of Prototype (http://www.prototypejs.org)
  	
	getDimensions: function(element) 
	{
	    var display = element.style.display;
	    if (display != 'none' && display != null) // Safari bug
	      return {width: element.offsetWidth, height: element.offsetHeight};
	
	    var els = element.style;
	    var originalVisibility = els.visibility;
	    var originalPosition = els.position;
	    var originalDisplay = els.display;
	    els.visibility = 'hidden';
	    if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
	      els.position = 'absolute';
	    els.display = 'block';
	    var originalWidth = element.clientWidth;
	    var originalHeight = element.clientHeight;
	    els.display = originalDisplay;
	    els.position = originalPosition;
	    els.visibility = originalVisibility;
	    return {width: originalWidth, height: originalHeight};
	},
	
	//hasClassName - courtesy of Prototype (http://www.prototypejs.org)
	
	hasClassName: function(element, className) 
	{
		var elementClassName = element.className;
		return (elementClassName.length > 0 && (elementClassName == className ||
		new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  	},

	//addClassName - courtesy of Prototype (http://www.prototypejs.org)
	
	addClassName: function(element, className) 
	{
		if (!this.hasClassName(element, className))
			element.className += (element.className ? ' ' : '') + className;
		return element;
	},

	//removeClassName - courtesy of Prototype (http://www.prototypejs.org)
	
	removeClassName: function(element, className) 
	{
		element.className = this.strip(element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
		return element;
	},
	
	//strip - courtesy of Prototype (http://www.prototypejs.org)
	
	strip:function(s)
	{
    	return s.replace(/^\s+/, '').replace(/\s+$/, '');
    }

}
//Description
// Droppable fire events when a draggable is dropped on them

var webkit_droppables = function()
{
	this.initialize = function()
	{
		this.droppables = [];
		this.droppableRegions = [];
	}
	this.add = function(root,instance_props)
	{
		root = webkit_tools.$(root);
	
		var default_props = {accept : [], hoverClass : null, onDrop : webkit_tools.empty, onOver : webkit_tools.empty, onOut : webkit_tools.empty};
		default_props = webkit_tools.extend(default_props, instance_props || {});
		this.droppables.push({r : root, p : default_props}); 		
	}
	this.remove = function(root)
	{
		root = webkit_tools.$(root);
		var d = this.droppables;
		var i = d.length;
		while(i--)
		{
			if(d[i].r == root)
			{
				d[i] = null;
				this.droppables = webkit_tools.compact(d);
				return true;
			}
		}
		return false;
	}
	//calculate position and size of all droppables
	this.prepare = function()
	{
		var d = this.droppables;
		var i = d.length;
		var dR = [];
		var r = null;
		while(i--)
		{
			r = d[i].r;			
			if(r.style.display != 'none')
			{
				dR.push({i : i, size : webkit_tools.getDimensions(r), offset : webkit_tools.cumulativeOffset(r)})			
			}
		}
		
		this.droppableRegions = dR;
	}
	this.finalize = function(x,y,r,e)
	{
		var indices = this.isOver(x,y);
		var index = this.maxZIndex(indices);
		var over = this.process(index,r);
		if(over)
		{
			this.drop(index, r,e);
		}
		this.process(-1,r);
		return over;	
	}
	this.check = function(x,y,r)
	{
		var indices = this.isOver(x,y);
		var index = this.maxZIndex(indices);
		return this.process(index,r);		
	}
	this.isOver = function(x, y)
	{
		var dR = this.droppableRegions;
		var i = dR.length;
		var active = [];
		var r = 0;
		var maxX = 0;
		var minX = 0;
		var maxY = 0;
		var minY = 0;
		
		while(i--)
		{
			r = dR[i];
			
			minY = r.offset.top;
			maxY = minY + r.size.height;
			
			if((y > minY) && (y < maxY))
			{
				minX = r.offset.left;
				maxX = minX + r.size.width;
				
				if((x > minX) && (x < maxX))
				{
					active.push(r.i);
				}			
			}		
		}
		
		return active;	
	}
	
	this.maxZIndex = function(indices)
	{
		var d = this.droppables;
		var l = indices.length;
		var index = -1;
		
		var maxZ = -100000000;
		var curZ = 0;
		
		while(l--)
		{
			curZ = parseInt(d[indices[l]].r.style.zIndex || 0);
			if(curZ > maxZ)
			{
				maxZ = curZ;
				index = indices[l];		
			}	
		}
		
		return index;	
	}
	
	this.process = function(index, draggableRoot)
	{
		//only perform update if a change has occured
		if(this.lastIndex != index)
		{
			//remove previous
			if(this.lastIndex != null)
			{
				var d = this.droppables[this.lastIndex]
				if(typeof d == 'object'){
					var p = d.p;
					var r = d.r;
					if(p.hoverClass)
					{
						webkit_tools.removeClassName(r,p.hoverClass);
					}
					p.onOut();
					this.lastIndex = null;
					this.lastOutput = false;
				}
			}
			
			//add new
			if(index != -1)
			{
				var d = this.droppables[index]
				var p = d.p;
				var r = d.r;
				
				if(this.hasClassNames(draggableRoot, p.accept))
				{
					if(p.hoverClass)
					{
						webkit_tools.addClassName(r,p.hoverClass);
					}
					p.onOver();				
					this.lastIndex = index;
					this.lastOutput = true;	
				}
			}	
		}
		return this.lastOutput;
	}
	
	this.drop = function(index, r, e)
	{
		if(index != -1)
		{
			this.droppables[index].p.onDrop(r,e);
		}
	}
	
	this.hasClassNames = function(r, names)
	{
		var l = names.length;
		if(l == 0){return true}
		while(l--)
		{
			if(webkit_tools.hasClassName(r,names[l]))
			{
				return true;
			}
		}
		return false;
	}
	
	this.initialize();
}

webkit_drop = new webkit_droppables();

//Description
//webkit draggable - allows users to drag elements with their hands

var webkit_draggable = function(r, ip)
{
	this.initialize = function(root, instance_props)
	{
		this.root = webkit_tools.$(root);
		var default_props = {scroll : false, revert : false, handle : this.root, zIndex : 1000, onStart : webkit_tools.empty, onEnd : webkit_tools.empty};		
		
		this.p = webkit_tools.extend(default_props, instance_props || {});
		default_props.handle = webkit_tools.$(default_props.handle);
		this.prepare();
		this.bindEvents();
	}
	
	this.prepare = function()
	{
		var rs = this.root.style;
		
		//set position
		if(webkit_tools.getCalculatedStyle(this.root,'position') != 'absolute')
		{
			rs.position = 'relative';
		}
		
		//set top, right, bottom, left
		rs.top = rs.top || '0px';
		rs.left = rs.left || '0px';
		rs.right = "";
		rs.bottom = "";		
		
		//set zindex;
		rs.zIndex = rs.zIndex || '0';
	}
	
	this.bindEvents = function()
	{
		var handle = this.p.handle;
		
		this.ts = webkit_tools.bindAsEventListener(this.touchStart, this);
		this.tm = webkit_tools.bindAsEventListener(this.touchMove, this);
		this.te = webkit_tools.bindAsEventListener(this.touchEnd, this);		
		
		handle.addEventListener("touchstart", this.ts, false);
		handle.addEventListener("touchmove", this.tm, false);
		handle.addEventListener("touchend", this.te, false);
	}	
	
	this.destroy = function()
	{
		var handle = this.p.handle;
		
		handle.removeEventListener("touchstart", this.ts);
		handle.removeEventListener("touchmove", this.tm);
		handle.removeEventListener("touchend", this.te);	
	}
	
	this.set = function(key, value)
	{
		this.p[key] = value;
	}
	
	this.touchStart = function(event)
	{
		//prepare needed variables
		var p = this.p;
		var r = this.root;
		var rs = r.style;
		var t = event.targetTouches[0];		
		
		//get position of touch
		touchX = t.pageX;
		touchY = t.pageY;
				
		//set base values for position of root
		rs.top = this.root.style.top || '0px';
		rs.left = this.root.style.left || '0px';
		rs.bottom = null;
		rs.right = null;
		
		var rootP = webkit_tools.cumulativeOffset(r);
		var cp = this.getPosition();
		
		//save event properties
		p.rx = cp.x;
		p.ry = cp.y;		
		p.tx = touchX;
		p.ty = touchY;
		p.z = parseInt(this.root.style.zIndex);
		
		//boost zIndex
		rs.zIndex = p.zIndex;
		webkit_drop.prepare();
		p.onStart();
	}
	
	this.touchMove = function(event)
	{
		event.preventDefault();
		event.stopPropagation();
		
		//prepare needed variables
		var p = this.p;
		var r = this.root;
		var rs = r.style;
		var t = event.targetTouches[0];
		if(t == null){return}
		
		var curX = t.pageX;
		var curY = t.pageY;
		
		var delX = curX - p.tx;
		var delY = curY - p.ty;
		
		rs.left = p.rx + delX + 'px';
		rs.top  = p.ry + delY + 'px';
		
		//scroll window
		if(p.scroll)
		{
			s = this.getScroll(curX, curY);
			if((s[0] != 0) || (s[1] != 0))
			{
				window.scrollTo(window.scrollX + s[0], window.scrollY + s[1]);
			}
		}
		
		//check droppables
		webkit_drop.check(curX, curY, r);
		
		//save position for touchEnd
		this.lastCurX = curX;
		this.lastCurY = curY;
	}
	
	this.touchEnd = function(event)
	{
		var r = this.root;
		var p = this.p;
		var dropped = webkit_drop.finalize(this.lastCurX, this.lastCurY, r, event);
		
		if(((p.revert) && (!dropped)) || (p.revert === 'always'))
		{
			//revert root
			var rs = r.style;
			rs.top = (p.ry + 'px');
			rs.left = (p.rx + 'px');
		}
		
		r.style.zIndex = this.p.z;
		this.p.onEnd();
	}
	
	this.getPosition = function()
	{
		var rs = this.root.style;
		return {x : parseInt(rs.left || 0), y : parseInt(rs.top  || 0)}
	}
	
	this.getScroll = function(pX, pY)
	{
		//read window variables
		var sX = window.scrollX;
		var sY = window.scrollY;
		
		var wX = window.innerWidth;
		var wY = window.innerHeight;
		
		//set contants		
		var scroll_amount = 10; //how many pixels to scroll
		var scroll_sensitivity = 100; //how many pixels from border to start scrolling from.
				
		var delX = 0;
		var delY = 0;		
		
		//process vertical y scroll
		if(pY - sY < scroll_sensitivity)
		{
			delY = -scroll_amount;
		}
		else
		if((sY + wY) - pY < scroll_sensitivity)
		{
			delY = scroll_amount;
		}
		
		//process horizontal x scroll
		if(pX - sX < scroll_sensitivity)
		{
			delX = -scroll_amount;
		}
		else
		if((sX + wX) - pX < scroll_sensitivity)
		{
			delX = scroll_amount;
		}
		
		return [delX, delY]
	}
	
	//contructor
	this.initialize(r, ip);
}

//Description
//webkit_click class. manages click events for draggables

var webkit_click = function(r, ip)
{
	this.initialize = function(root, instance_props)
	{
		var default_props = {onClick : webkit_tools.empty};
		
		this.root = webkit_tools.$(root);
		this.p = webkit_tools.extend(default_props, instance_props || {});
		this.bindEvents();
	}
	
	this.bindEvents = function()
	{
		var root = this.root;
		
		//bind events to local scope
		this.ts = webkit_tools.bindAsEventListener(this.touchStart,this);
		this.tm = webkit_tools.bindAsEventListener(this.touchMove,this);
		this.te = webkit_tools.bindAsEventListener(this.touchEnd,this);
		
		//add Listeners
		root.addEventListener("touchstart", this.ts, false);
		root.addEventListener("touchmove", this.tm, false);
		root.addEventListener("touchend", this.te, false);
		
		this.bound = true;	
	}	
	
	this.touchStart = function()
	{
		this.moved = false;
		if(this.bound == false)
		{
			this.root.addEventListener("touchmove", this.tm, false);
			this.bound = true;
		}
	}
	
	this.touchMove = function()
	{
		this.moved = true;
		this.root.removeEventListener("touchmove", this.tm);
		this.bound = false;
	}
	
	this.touchEnd = function()
	{
		if(this.moved == false)
		{
			this.p.onClick();
		}
	}
	
	this.setEvent = function(f)
	{
		if(typeof(f) == 'function')
		{
			this.p.onClick = f;
		}
	}
	
	this.unbind = function()
	{
		var root = this.root;
		root.removeEventListener("touchstart", this.ts);
		root.removeEventListener("touchmove", this.tm);
		root.removeEventListener("touchend", this.te);
	}
		
	//call constructor
	this.initialize(r, ip);
}