/* Copyright 2009 Palm, Inc.  All rights reserved. */

var GridList = Class.create
({
	initialize: function(args)
	{
		this.target = args.target;
		this.target.mojo = this;
		this.columns = args.columns || 2;
		this.firstItem = args.templates.first || args.template.next;
		this.nextItem = args.templates.next || args.template.first;
		this.onItemMoved = args.onItemMoved;
		this.formatters = args.formatters;
		this.dragEnabled = args.dragEnabled == false ? false : true;
		this.scroller = Mojo.View.getScrollerForElement(this.target);
		
		this.normalZIndex = -2;
		this.dragZIndex = -1;
		
		this.onDragStart = this.onDragStart.bindAsEventListener(this);
		this.onDragMove = this.onDragMove.bindAsEventListener(this);
	},
	
	render: function(items)
	{
		this.items = items;
		if (this.table)
		{
			this.table.parentNode.removeChild(this.table);
		}
		this.table = this.createGrid(items);
		this.target.appendChild(this.table);
	},

	createGrid: function(items)
	{
		var t = document.createElement('table');
		t.style.position = 'relative';
				
		Mojo.Event.listen(t, Mojo.Event.hold, this.onDragStart);
		
		var zindex = this.normalZIndex;
		var length = items.length;
		var idx = 0;
		var rinfo = { object: null, template: null, formatters: this.formatters };

		for (var ir = 0; idx < length; ir++)
		{
			var tr = t.insertRow(ir);
			for (var ic = 0; ic < this.columns && idx < length; ic++, idx++)
			{
				// Create a table cell with a div inside to hold the content.
				// We use a 'div' because you cannot set the css position to be relative.
				var tc = tr.insertCell(ic);
				tc.setAttribute("x-mojo-tap-highlight", "momentary");
				var div = document.createElement('div');
				tc.appendChild(div);
				
				div.style.position = 'relative';
				div.style.zIndex = zindex;
				
				rinfo.object = items[idx];
				rinfo.template = idx == 0 ? this.firstItem : this.nextItem;
				div.innerHTML = Mojo.View.render(rinfo);
				div.iteminfo = items[idx];
			}
		}

		return t;
	},
	
	findItem: function(target)
	{
		if (target.firstChild) 
		{
			return target.firstChild.iteminfo;
		}
		else 
		{
			return null;
		}
	},
	
	getItemAt: function(row, column)
	{
		var rows = this.table.rows;
		if (row < 0 || row >= rows.length)
		{
			return null;
		}
		var columns = rows[row].cells;
		if (column < 0 || column >= columns.length)
		{
			return null;
		}
		return this.findItem(columns[column]);
	},
	
/*****	
	OLDswap: function(fdiv, tdiv)
	{
		if (this._swapping)
		{
			return false;
		}
		this._swapping = true;
		
		var fcell = fdiv.parentNode;
		var tcell = tdiv.parentNode;
					
		// Calculate x and y difference between two elements
		var xdiff = fcell.offsetLeft - tcell.offsetLeft;
		var ydiff = fcell.offsetTop - tcell.offsetTop;
		
		// HACK
		ydiff -= 53;
				
		// Animate swap
		var animcount = 0;
		var complete = function()
		{
			if (--animcount == 0) {
				tdiv.style.left = null;
				tdiv.style.top = null;
				
				fcell.removeChild(fdiv);
				tcell.removeChild(tdiv);
				fcell.appendChild(tdiv);
				tcell.appendChild(fdiv);
				
				this._swapping = false;
				
				this._doItemMoved(fcell, tdiv);
				this._doItemMoved(tcell, fdiv);
			}
		}.bind(this);

		var time = 0.2;
		
		if (ydiff) {
			animcount++;
			Mojo.Animation.animateStyle(tdiv, 'top', 'linear', {
				from: 0,
				to: ydiff,
				duration: time,
				onComplete: complete
			});
		}
		if (xdiff) {
			animcount++;
			Mojo.Animation.animateStyle(tdiv, 'left', 'linear', {
				from: 0,
				to: xdiff,
				duration: time,
				onComplete: complete
			});
		}
		
		return true;
	},
****/

	swap: function(draggingdiv, placediv)
	{
		if (this.dragging)
		{
			return;
		}
		this.dragging = true;
		
		var draggingcell = draggingdiv.parentNode;
		var xpos = draggingcell.cellIndex;
		var ypos = draggingcell.parentNode.rowIndex;
		var table = draggingcell.parentNode.parentNode.parentNode;
		
		var placecell = placediv.parentNode;
		
		var coldiff = xpos - placecell.cellIndex;
		var rowdiff = ypos - placecell.parentNode.rowIndex;
		
		Mojo.Log.info("SWAP (%d,%d)->(%d,%d)", xpos, ypos, placecell.cellIndex, placecell.parentNode.rowIndex);
		Mojo.Log.info("count", this.columns - coldiff, "rowdiff", rowdiff, "coldiff", coldiff);
		
		var self = this;
		var count;
		function done()
		{
			if (--count == 0)
			{
				draggingcell.removeChild(draggingdiv);
				placecell.appendChild(draggingdiv);
				
				self.dragging = false;
			}
		}
		
		if (rowdiff == 0)
		{
			// An easy swap - just need to move one tile
			count = Math.abs(coldiff);
			var dir = coldiff / count;
			for (var c = 0; c < count; c++)
			{
				xpos -= dir;
				this._moveTile(table.rows[ypos].cells[xpos].firstChild, dir, 0, done);
			}
		}
		else if (rowdiff == 1)
		{
			count = this.columns + coldiff;
			for (var c = 0; c < count; c++)
			{
				xpos--;
				if (xpos == -1)
				{
					ypos--;
					xpos = this.columns - 1;
					this._moveTile(table.rows[ypos].cells[xpos].firstChild, -(this.columns - 1), 1, done)
				}
				else
				{
					this._moveTile(table.rows[ypos].cells[xpos].firstChild, 1, 0, done);
				}
			}
		}
		else if (rowdiff == -1)
		{
			// Moving tile down one row
			count = this.columns - coldiff;
			for (var c = 0; c < count; c++)
			{
				xpos++;
				if (xpos == this.columns)
				{
					ypos++;
					xpos = 0;
					this._moveTile(table.rows[ypos].cells[xpos].firstChild, this.columns - 1, -1, done)
				}
				else
				{
					this._moveTile(table.rows[ypos].cells[xpos].firstChild, -1, 0, done);
				}
			}
		}
		else
		{
			// Not something we allow
			this.dragging = false;
		}
	},
	
	_moveTile: function(div, hdir, vdir, done)
	{
		var cell = div.parentNode;
		var row = cell.parentNode;
		var table = row.parentNode.parentNode;
		var ncell = table.rows[row.rowIndex + vdir].cells[cell.cellIndex + hdir];
		
		// Dont move things that aren't draggable
		if (window.getComputedStyle(cell.firstChild.firstChild).WebkitUserDrag != 'element')
		{
			done();
			return;
		}
		
		var count = 0;
		function finish()
		{
			if (--count == 0)
			{
				div.style.left = null;
				div.style.top = null;
			
				cell.removeChild(div);
				ncell.appendChild(div);

				done();
			}
		}
		if (hdir != 0)
		{
			count++;
			Mojo.Animation.animateStyle(div, 'left', 'linear', 
			{
				from: 0,
				to: ncell.offsetLeft - cell.offsetLeft,
				duration: 0.2,
				onComplete: finish
			});
		}
		if (vdir != 0)
		{
			count++;
			Mojo.Animation.animateStyle(div, 'top', 'linear', 
			{
				from: 0,
				to: ncell.offsetTop - cell.offsetTop,
				duration: 0.2,
				onComplete: finish
			});
		}
	},

	onDragStart: function(event)
	{
		if (!this.dragEnabled)
		{
			return;
		}
		var target = this._findGridCell(event.target);
		if (target) {
			target.style.zIndex = this.dragZIndex;
			var apos = Element.cumulativeOffset(target);
			var spos = Element.cumulativeScrollOffset(target);
			var dragging = {
				element: target,
				height: target.clientHeight,
				startevent: { left: event.down.pageX, top: event.down.pageY },
				startpos: { left: apos.left, top: apos.top - spos.top }
			};
			target.style.position = 'fixed';
			target.style.top = dragging.startpos.top + 'px';
			target.style.left = dragging.startpos.left + 'px';
			this._dragging = dragging;
			
			target.addClassName('grid-dragging');
			
			Mojo.Event.listen(this.scroller, 'mousemove', this.onDragMove);
			this.scroller.addEventListener(Mojo.Event.dragStart, this._stopScrollHandler, true);
			
			var self = this;
			function dragEnd()
			{
				dragging.element.style.position = 'relative';
				dragging.element.style.top = '';
				dragging.element.style.left = '';
				dragging.element.style.zIndex = self.normalZIndex;				
				dragging.element.removeClassName('grid-dragging');

				Mojo.Event.stopListening(self.scroller.ownerDocument, 'mouseup', arguments.callee);
				Mojo.Event.stopListening(self.scroller, 'mousemove', self.onDragMove);
				self.scroller.removeEventListener(Mojo.Event.dragStart, self._stopScrollHandler, true);
				
				self._dragging = null;
				
				self._doItemMoved(dragging.element.parentNode, dragging.element);
			};
			Mojo.Event.listen(this.scroller.ownerDocument, 'mouseup', dragEnd, false);
		}
	},
	
	_doItemMoved: function(cell, item)
	{
		if (this.onItemMoved) 
		{
			var row = cell.parentNode;
			var grid = row.parentNode.parentNode;
			var glen = grid.rows[0].cells.length;
			
			var iteminfo = item.iteminfo;
			
			var idx = row.rowIndex * glen + cell.cellIndex;
			var pidx = idx - 1;
			var nidx = idx + 1;
			
			var before = (pidx < 0 ? null : grid.rows[(pidx / glen) | 0].cells[pidx % glen].firstChild.iteminfo);
			var after = (nidx >= grid.rows.length * glen ? null : grid.rows[(nidx / glen) | 0].cells[nidx % glen]);
			if (after)
			{
				after = after.firstChild.iteminfo;
			}
			
			this.onItemMoved(iteminfo, before, after);
		}
	},
	
	_adjustDragged: function(event)
	{
		var dragging = this._dragging;

		var x = dragging.startpos.left + event.pageX - dragging.startevent.left;
		var y = dragging.startpos.top + event.pageY - dragging.startevent.top;
		dragging.element.style.top = y + 'px';
		dragging.element.style.left = x + 'px';
		
		var scroller = this.scroller.mojo;
		var size = scroller.scrollerSize();
		var pos = scroller.getScrollPosition();
		if (event.pageY + dragging.height > size.height)
		{
			scroller.scrollTo(undefined, pos.top - dragging.height / 2, true, false);
		}
		if (event.pageY - dragging.height < 0)
		{
			scroller.scrollTo(undefined, pos.top + dragging.height / 2, true, false);
		}
	},
	
	onDragMove: function(event)
	{
		this._adjustDragged(event);
		
		var dragging = this._dragging;
		
		var target = this._findGridCell(event.target) || this._beyondGrid(event);
		if (target && target != dragging.element) 
		{				
			this.swap(dragging.element, target);
		}
	},
	
	_stopScrollHandler: function(event)
	{
		Event.stop(event);
	},
	
	_findGridCell: function(cell)
	{
		return cell.firstChild && cell.firstChild.firstChild && window.getComputedStyle(cell.firstChild.firstChild).WebkitUserDrag == 'element' ? cell.firstChild : null;
	},
	
	_beyondGrid: function(event)
	{
		var elem = event.target;
		if ((elem.tagName == 'TABLE' || elem.tagName == 'DIV') && (event.pageY > Element.getDimensions(elem).height / 2) || (event.pageY > this.scroller.mojo.scrollerSize().height / 2))
		{
			// If we drag beyound half the height of the container, then we assume we've dragged off then end.
			var idx = this.items.length - 1;
			//Mojo.Log.info("idx %d %d %d", idx, idx / this.columns, idx % this.columns);
			return this.table.rows[0 | (idx / this.columns)].cells[idx % this.columns].firstChild;
		}
		return null;
	},
});
