/****************************************
*    General Variables                  *
*****************************************/

var drag_drops		= [];
var containers		= [];
var drop_targets	= [];
var i_mouse_down	= false;
var l_mouse_state	= false;
var mouse_offset	= null;
var drag_object		= null;
var current_target	= null;
var last_target		= null;
var drag_helper		= null;
var temp_div		= null;
var root_parent		= null;
var root_sibling	= null;


/****************************************
*    Read Drag Container Function       *
*****************************************/

// This function is used along with the create drag container function.
// its function is to look at all of the children nodes in the each container
// (if more than one) and comma delimit there ids. each of the containers are
// then sperated by a vertical bar. eg: 1,2,3|a,b,c
function read_drag_container(){

	// Create the variable to hold the order of all of the containers
	var order = '';

	// Loop through the containers array (made in the create drag container function)
	for(var i=0; i<containers.length; i++){

		// Set a variable for the container object
		var container = containers[i];

		// Add the vertical bar to the order string
		order += '|';

		// Create a variable to temporarily hold the order of the trays.
		var trays = '';

		// Loop through the children nodes of the container
		for(var j=0; j<container.childNodes.length; j++){

			// Firefox puts in lots of #text nodes...skip these
			if(container.childNodes[j].nodeName=='#text') {
				continue;
			}

			// Add the comma and then the id to the trays variable
			trays += ',' + container.childNodes[j].getAttribute('id');
		}

		// Remove the first comma and add it to the order string
		// This will chage the trays string from this ,a,b,c to a,b,c
		order += trays.substring(1);

	}

	// Remove the first vertical bar and return the value
	// This will chage the order string from this |1,2,3|a,b,c to 1,2,3|a,b,c
	return order.substring(1);
}


/****************************************
*    Create Drag Container Function     *
*****************************************/

// This funtion is used to create the dragable list containers
// you simply pass in the drag containers ids as seperate function
// parameters
function create_drag_container(){

	// Create a new "Container Instance" so that items from one "Set" can not
	// be dragged into items from another "Set"
	var container_drag			= drag_drops.length;
	drag_drops[container_drag]	= [];

	// Each item passed to this function should be a "container".  Store each
	// of these items in our current container. Loop through the optional
	// arguments to set up each container
	for(var i=0; i<arguments.length; i++){

		// Set a variable for the container object
		var container = arguments[i];

		// Add the container into the containers array
		containers.push(container);

		// Add the container to the drag_drops array
		drag_drops[container_drag].push(container);

		// Setup an attribute for the container. so the script knows
		// that we can add items to this container
		container.setAttribute('DropObj', container_drag);

		// Every top level item in these containers should be draggable.  Do this
		// by setting the DragObj attribute on each item and then later checking
		// this attribute in the mouse_move function. Loop through all of the
		// children nodes and add the DragObj attribute to them
		for(var j=0; j<container.childNodes.length; j++){

			// Firefox puts in lots of #text nodes...skip these
			if(container.childNodes[j].nodeName=='#text') {
				continue;
			}

			// Setup an attribute for the container. so the script knows
			// that we can drag and add this item to a container
			container.childNodes[j].setAttribute('DragObj', container_drag);

		}
	}
}


/****************************************
*    Mouse Move Function                *
*****************************************/

// This function
function mouse_move(ev){

	// Get the event object and hold it in a variable
	ev = ev || window.event;

	// We are setting target to whatever item the mouse is currently on
	// Firefox uses event.target here, MSIE uses event.srcElement
	var target = ev.target || ev.srcElement;

	// Get the mouse position object
	var mouse_position = mouse_coords(ev);

	// mouseOut event - fires if the item the mouse is on has changed
	if(last_target && (target!==last_target)){

		// Reset the classname for the target element if we need to
		var orig_class = last_target.getAttribute('origClass');
		if(orig_class) {
			last_target.className = orig_class;
		}
	}


	// drag_object is the grouping our item is in (set from the create drag container function).
	// if the item is not in a grouping we ignore it since it can't be dragged with this
	// script.
	var drag_object_group = target.getAttribute('DragObj');

	// If the mouse was moved over an element that is draggable
	if(drag_object_group!=null){

		// mouseOver event - Change the item's class if necessary
		if(target!=last_target){
			var over_class = target.getAttribute('overClass');
			if(over_class){
				target.setAttribute('origClass', target.className);
				target.className = over_class;
			}
		}

		// if the user is just starting to drag the element
		if(i_mouse_down && !l_mouse_state){

			// mouseDown target
			current_target = target;

			// Get and record the mouse x and y offset for the element
			root_parent		= current_target.parentNode;
			root_sibling	= current_target.nextSibling;
			mouse_offset   	= get_mouse_offset(target, ev);

			// We remove anything that is in our drag_helper DIV so we can put a new item in it.
			for(var i=0; i<drag_helper.childNodes.length; i++) {
				drag_helper.removeChild(drag_helper.childNodes[i]);
			}

			// Make a copy of the current item and put it in our drag helper.
			// and set display to block
			drag_helper.appendChild(current_target.cloneNode(true));
			drag_helper.style.display = 'block';

			// set the class on our helper DIV if necessary
			var drag_class = current_target.getAttribute('dragClass');
			if(drag_class){
				drag_helper.firstChild.className = drag_class;
			}

			// Disable dragging from our helper DIV (it's already being dragged)
			drag_helper.firstChild.removeAttribute('DragObj');

			// Record the current position of all drag/drop targets related
			// to the element.  We do this here so that we do not have to do
			// it on the general mouse move event which fires when the mouse
			// moves even 1 pixel.  If we don't do this here the script
			// would run much slower.
			var drag_containers = drag_drops[drag_object_group];

			// First record the width/height of our drag item.  Then hide it since
			// it is going to (potentially) be moved out of its parent.
			current_target.setAttribute('startWidth',  parseInt(current_target.offsetWidth));
			current_target.setAttribute('startHeight', parseInt(current_target.offsetHeight));
			current_target.style.display  = 'none';

			// Loop through each possible drop container
			for(var i=0; i<drag_containers.length; i++){
				with(drag_containers[i]){

					// Get the position object for the container
					var position = get_position(drag_containers[i]);

					// save the width, height and position of each container.
					// Even though we are saving the width and height of each
					// container back to the container this is much faster because
					// we are saving the number and do not have to run through
					// any calculations again.  Also, offsetHeight and offsetWidth
					// are both fairly slow.  You would never normally notice any
					// performance hit from these two functions but our code is
					// going to be running hundreds of times each second so every
					// little bit helps!
					// Note that the biggest performance gain here, by far, comes
					// from not having to run through the get_position function
					// hundreds of times.
					setAttribute('startWidth',  parseInt(offsetWidth));
					setAttribute('startHeight', parseInt(offsetHeight));
					setAttribute('startLeft',   position.x);
					setAttribute('startTop',    position.y);
				}

				// Loop through each child element of each container
				for(var j=0; j<drag_containers[i].childNodes.length; j++){
					with(drag_containers[i].childNodes[j]){

						// Firefox puts in lots of #text nodes...skip these
						if((nodeName=='#text') || (drag_containers[i].childNodes[j]==current_target)) {
							continue;
						}

						// Get the position object for the containers child nodes
						var position = get_position(drag_containers[i].childNodes[j]);

						// save the width, height and position of each element
						setAttribute('startWidth',  parseInt(offsetWidth));
						setAttribute('startHeight', parseInt(offsetHeight));
						setAttribute('startLeft',   position.x);
						setAttribute('startTop',    position.y);
					}
				}
			}
		}
	}

	// If we get in here we are dragging something
	if(current_target){

		// Move our helper div to wherever the mouse is (adjusted by mouse_offset)
		drag_helper.style.top  = mouse_position.y - mouse_offset.y;
		drag_helper.style.left = mouse_position.x - mouse_offset.x;

		// Setup the current target this is a check to see if the current target
		// is the correct item we are dragging
		var drag_containers  = drag_drops[current_target.getAttribute('DragObj')];

		// Set the active container variable to null this also resets it is it
		// has been set once before
		var active_container = null;

		// Get the x and y positions of the element and calculate the new position base on the
		// mouse position and the mouse offset
		var x_position = mouse_position.x - mouse_offset.x + (parseInt(current_target.getAttribute('startWidth')) /2);
		var y_position = mouse_position.y - mouse_offset.y + (parseInt(current_target.getAttribute('startHeight'))/2);

		// Check each drop container to see if our target object is "inside" the container
		for(var i=0; i<drag_containers.length; i++){
			with(drag_containers[i]){
				if((parseInt(getAttribute('startLeft')) < x_position) &&
					(parseInt(getAttribute('startTop')) < y_position) &&
					((parseInt(getAttribute('startLeft')) + parseInt(getAttribute('startWidth'))) > x_position) &&
					((parseInt(getAttribute('startTop'))  + parseInt(getAttribute('startHeight'))) > y_position)){

						// Our target is inside of our container so save the container into
						// the activeCont variable and then exit the loop since we no longer
						// need to check the rest of the containers
						active_container = drag_containers[i];

						// exit the for loop
						break;
				}
			}
		}

		// Our target object is in one of our containers.  Check to see where our div belongs
		if(active_container){

			// before_node will hold the first node AFTER where our div belongs
			var before_node = null;

			// Loop through each child node (skipping text nodes).
			for(var i=active_container.childNodes.length-1; i>=0; i--){
				with(active_container.childNodes[i]){
					if(nodeName=='#text') {
						continue;
					}

					// If the current item is "After" the item being dragged
					if(current_target != active_container.childNodes[i] &&
						((parseInt(getAttribute('startLeft')) + parseInt(getAttribute('startWidth')))  > x_position) &&
						((parseInt(getAttribute('startTop'))  + parseInt(getAttribute('startHeight'))) > y_position)) {
							before_node = active_container.childNodes[i];
					}
				}
			}

			// The item being dragged belongs before another item
			if(before_node){
				if(before_node!=current_target.nextSibling){
					active_container.insertBefore(current_target, before_node);
				}

			// The item being dragged belongs at the end of the current container
			} else {
				if((current_target.nextSibling) || (current_target.parentNode!=active_container)){
					active_container.appendChild(current_target);
				}
			}

			// The timeout is here because the container doesn't "immediately" resize
			setTimeout(function(){
				var container_position = get_position(active_container);
				active_container.setAttribute('startWidth',  parseInt(active_container.offsetWidth));
				active_container.setAttribute('startHeight', parseInt(active_container.offsetHeight));
				active_container.setAttribute('startLeft',   container_position.x);
				active_container.setAttribute('startTop',    container_position.y);
			}, 5);

			// Make our drag item visible
			if(current_target.style.display!=''){
				current_target.style.display    = '';
				current_target.style.visibility = 'hidden';
			}
		} else {

			// Our drag item is not in a container, so hide it.
			if(current_target.style.display!='none'){
				current_target.style.display  = 'none';
			}
		}
	}

	// Track the current mouse state so we can compare against it next time
	l_mouse_state = i_mouse_down;

	// MouseMove target
	last_target  = target;

	if(drag_object){
		drag_object.style.position = 'absolute';
		drag_object.style.top      = mouse_position.y - mouse_offset.y;
		drag_object.style.left     = mouse_position.x - mouse_offset.x;
	}

	// Track the current mouse state so we can compare against it next time
	l_mouse_state = i_mouse_down;

	// This prevents items on the page from being highlighted while dragging
	if(current_target || drag_object){
		return false;
	}
}


/****************************************
*    Mouse Up Function                  *
*****************************************/

function mouse_up(ev){
	if(current_target){

		// Hide our helper object - it is no longer needed
		drag_helper.style.display = 'none';

		// If the drag item is invisible put it back where it was before moving it
		if(current_target.style.display == 'none'){
			if(root_sibling){
				root_parent.insertBefore(current_target, root_sibling);
			} else {
				root_parent.appendChild(current_target);
			}
		}

		// make sure the drag item is visible
		current_target.style.display    = '';
		current_target.style.visibility = 'visible';
	}

	// Clear out the mouse, drag, and target variables
	current_target  = null;
	drag_object = null;
	i_mouse_down = false;
}


/****************************************
*    Mouse Down Function                *
*****************************************/

function mouse_down(){
	i_mouse_down = true;
	if(last_target){
		return false;
	}
}


/****************************************
*    Make Clickable Function            *
*****************************************/

function make_clickable(object){
	object.onmousedown = function(){
		drag_object = this;
	}
}


/****************************************
*    Make Draggable Function            *
*****************************************/

function make_draggable(item){
	if(!item) return;
	item.onmousedown = function(ev){
		drag_object  = this;
		mouse_offset = get_mouse_offset(this, ev);
		return false;
	}
}


/****************************************
*    Add Drop Target Function           *
*****************************************/

function add_drop_target(drop_target){
	drop_targets.push(drop_target);
}

document.onmousemove = mouse_move;
document.onmousedown = mouse_down;
document.onmouseup   = mouse_up;