/*

 	                          __     ___                               
	                  __     /\ \   /\_ \                              
	   __      _ __  /\_\    \_\ \  \//\ \      ___    __  __     __   
	 /'_ `\   /\`'__\\/\ \   /'_` \   \ \ \    / __`\ /\ \/\ \  /'__`\ 
	/\ \L\ \  \ \ \/  \ \ \ /\ \L\ \   \_\ \_ /\ \L\ \\ \ \_/ |/\  __/ 
	\ \____ \  \ \_\   \ \_\\ \___,_\  /\____\\ \____/ \ \___/ \ \____\
	 \/___L\ \  \/_/    \/_/ \/__,_ /  \/____/ \/___/   \/__/   \/____/
	   /\____/                                                          
	   \_/__/                                                           

	gridlove.js - core objects and methods
	Copyright 2006 Microsoft Corporation / Heh, just kidding... or am I?
	Copyright 2006 Gridlove / Anson Parker / ansonparker@gmail.com

*/




/* Grid Object
 *
 * Parameters -
 * gridid - string - the DOM id of the container block element for the grid 
 *
 *
 *  current drawing bug after clicking on an image for the map to center - things don't draw
 *  gets fixed if u click on another picture
 *  click to center doesn't work in safari
 *
 *
 *
*/
function Grid (gridid)
{

	var debug = true;

	var _grid = this;
	var _loaded = false;
	
	// special flag to cancel normal UI (must be in special tool) - can only be set locally
	var modal = false
	this.isModal = function() { return modal }
	
	/* core model private fields */
	var id           = null;
	var title        = null;
	var description  = null;
	var datecreated  = null;
	var creator      = null;
	var creatorid    = null;
	var updateable   = false;
	var active       = false;
	var timestamp    = 0;
	var style        = null;
	var picturestack = new Array(0);
	var lockstack    = new Array(0);
	var tagstack     = new Array(0);
	var debugstack   = new Array(0);
	var uploaders    = new Array(0);
	var currentuser  = null;
	var url          = null;
	this.mode        = 'full'
	var messagestack = new Array(0);
	
	var monthnames = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')	
	var isRedrawing = false
	
	/* draw queue */
	var drawQueue = new Array()
	var drawQueueTimeout = 0
	function addToDrawQueue(pic)
	{
		drawQueue.push(pic)
		if(!drawQueueTimeout)
			drawQueueTimeout = setTimeout( drawFromQueue , 25)
	}
	function drawFromQueue()
	{
		if(drawQueue.length)
		{
			drawPictureFromObj(drawQueue.shift())
			drawQueueTimeout = setTimeout( drawFromQueue , 25)
		} else {
			//alert('done')
		}
		
	}
	
	/* getters & setters for core fields */
	this.setId = function(id1) { id = id1; }
	this.getId = function () { return id; }	
	this.setTitle = function (title1) { title = title1; }
	this.getTitle = function () {	return title; }
	this.setDescription = function (desc) { description = desc; }
	this.getDescription = function () { return description; }
	this.setDateCreated = function (dc) { datecreated = dc; }
	this.getDateCreated = function () { return datecreated; }
	this.setCreator = function (crtr) { creator = crtr; }
	this.getCreator = function () { return creator };
	this.setCreatorId = function (crtr) { creatorid = crtr; }
	this.getCreatorId = function () { return creatorid };
	this.setUpdateable = function (upd) { updateable = upd; }
	this.isUpdateable = function () { return updateable; }
	this.setActive = function (actv) { active = actv; }
	this.isActive = function () { return active; }
	this.setTimestamp = function(ts) { timestamp = ts; }
	this.getTimestamp = function () { return timestamp; }
	this.setStyle = function (style1) { style = style1; }
	this.getStyle = function () {	return style; }
	this.setCurrentUser = function (crtr) { currentuser = crtr; }
	this.getCurrentUser = function () { return currentuser };
	this.setUrl = function (str) { url = str; }
	this.getUrl = function () {	return url; }
	this.addPicture = function(pic)
	{
		picturestack.push(pic);
		if(_loaded)
		{
			if(!_posgrid[''+pic.xpos])
				_posgrid[''+pic.xpos] = new Array(blockextentright-blockextentleft)
			_posgrid[''+pic.xpos][''+pic.ypos] = pic
			eventFired("update");
		}
	}
	this.removePicture = function(pic)
	{
		var pos = ''
		for(i=0;i<picturestack.length;i++)
		{
			if(picturestack[i].id == pic.id)
			{
				pos = i
				break
			}
		}
	
		if(pos != '')
			picturestack.splice(pos,1)
			
		_posgrid[''+pic.xpos][''+pic.ypos] = null
			
		var pd = document.getElementById(pic.id)
		if(pd)
			pd.parentNode.removeChild(pd)
		eventFired("update");
	}
	
	this.getPicture = function(x,y) { if(_posgrid[''+x] && _posgrid[''+x][''+y]) return _posgrid[''+x][''+y] }
	function getPictureById(picid)
	{
		for(i=0;i<picturestack.length;i++)
		{
			if (picturestack[i].id == picid)
				return picturestack[i];
		}
		return null;
	}
	
	this.addTag = function(tag) { tagstack.push(tag); }
	this.getTags = function () { return tagstack; }
	this.addUploader = function(url,method,params)
	{
		var upldr = new Uploader()
		upldr.url = url
		upldr.method = method
		upldr.params = params
		uploaders.push(upldr)
	}
	this.getUploaders = function()
	{
		return uploaders
	}
	this.addMessage = function(m)
	{
		if(!containsMessage(m.id))
		{
			messagestack.push(m)
		}
		//alert('adding mesage from ' + m.userName + ' with key ' + m.id)
	}
	function containsMessage(mid)
	{
	
		for(var i=0,j=messagestack.length;i<j;i++)
		{
			if(messagestack[i].id == mid)
				return true
		}
		return false
	
	}
	
	this.getMessages = function()
	{
		return messagestack
	}
	this.flagNewMessages = function()
	{
		// need a message sorter for syncing issues
		eventFired('update')
	}
	this.postMessage = function(msg)
	{
		_dao.postMessage(msg,_grid)
	}
	
	this.editDetails = function(title,desc)
	{
		_dao.editDetails(title,desc,_grid)
	}


	/* initialise on instantiation block */
	var _container = document.getElementById(gridid)
	var _canvas = document.createElement('div')
		_canvas.id = 'canvas'
		_container.appendChild(_canvas)
	this.getCanvas = function () { return _canvas }
	this.getContainer = function () { return _container }
	//alert('got canvas: ' + _container.clientWidth + ' x ' + _container.clientHeight)
	var _viewport = { height: _container.clientHeight, width: _container.clientWidth, minx: 0, miny: 0, maxx: 0, maxy: 0, offsetx: 0, offsety: 0 }
	var _lastdraw = { minx: null, miny: null, maxx: null, maxy: null, drawn: false }

	/* uploader - will be removing this */
	var _uploader = null
	this.registerUploader = function (upldr)
	{
		_uploader = upldr
		if(picturestack.length == 0 && _grid.isActive() == 'false')
		{		
			// if user is owner:
			_uploader.beginUploadAtTile(0,0,true)
		}
	}
	this.getUploader = function()
	{
		return _uploader
	}
	
	/* DAO - look for known DAO function GridDAO - otherwise it's null */
	var _dao = null
	if(typeof GridDAO == "function")
		_dao = new GridDAO()
	this.getDAO = function() { return _dao }
	
	/* refreshCycle */
	this.refresh = function()
	{
		if(_dao)
		{
			_dao.update(id,_grid, function() { setTimeout( _grid.refresh, 5000 ) })
		}
	}
		
	/* track rows and columns for 2d array reference */
	var _cols = 0
	var _rows = 0
	var _posgrid = null

	var gridsize = 10;
	var blocksize = gridsize * 10;
	this.getBlockSize = function () { return blocksize }
		
	var _hasLock = false
	
	this.setUploadLock = function() { _hasLock = true }
	this.releaseUploadLock = function () { _hasLock = false }
	this.hasUploadLock = function () { return _hasLock }
	
	var blockextentleft = 0
	var blockextentright = 0
	var blockextentup = 0
	var blockextentdown = 0
	
	var centerPicOnLoad = 0
	
	var lastdraw = null;

	/* get rid of all this crud. put the this.(function names) inline - much better for readability! */
	this.getViewportWidth  = function() { return _viewport.width }
	this.getViewportHeight = function() { return _viewport.height }
	this.getPictureById = getPictureById;
	this.setOffset = setOffset;
	this.getOffsetX = getOffsetX;
	this.getOffsetY = getOffsetY;
	this.addLock = addLock;
	this.getLock = getLock;
	this.releaseLock = releaseLock;
	
	this.load = function(boardid)
	{
		if(_dao != null)
		{
			_dao.load(boardid, _grid, _grid.initialise)
		} else {
			alert("Unable to load grid: no data access interface found");
		}
	}
	
	this.cbLoad = function(boardid,callback)
	{	
		if(_dao != null)
		{
			_dao.load(boardid, _grid, callback)
		} else {
			alert("Unable to load grid: no data access interface found");
		}	
	}
	
	function gridlog(msg)
	{
		debugstack.push(msg)
		eventFired("debug")
	}
	
	// function takes a pictures x & y coords and sees if it needs to update the
	// block extents for the board (i.e. is this picture more left/right up/down then other blocks)
	function testBlockLimit(pic)
	{
		var x = parseInt(pic.xpos,10)
		var y = parseInt(pic.ypos,10)
		
		if(!isNaN(x) && !isNaN(y))
		{
		
			if(x<blockextentleft)
			{
				blockextentleft = x
			} else {
				if(x>blockextentright)
					blockextentright = x			
			}
			
			if(y<blockextentup)
			{
				blockextentup = y
			} else {
				if(y>blockextentdown)
					blockextentdown = y			
			}
		
		} else {
			alert('unable to parse x,y position of pic ' + pic.id)
		}
	}
	
	this.getLeftmostPoint = function()
	{
		return blockextentleft * blocksize
	}
	
	this.getRightmostPoint = function()
	{
		return blockextentright * blocksize
	}
	
	this.getTopmostPoint = function ()
	{
		return blockextentup * blocksize
	}
	
	this.getBottommostPoint = function()
	{
		return blockextentdown * blocksize
	}

	this.setCenterPic = function(cpid)
	{
		centerPicOnLoad = cpid
	}
	
	function displayedCount()
	{
		var count = 0
		for(i=0, j=picturestack.length;i<j;i++)
		{
			if(picturestack[i].displayed)
				count++
		}
		return count
	}
	
	function addLock(lock)
	{
		lockstack.push(lock);
	}

	function getLock(x,y)
	{
		for(i=0;i<lockstack.length;i++)
		{
			var thislock = lockstack[i];
			if (thislock.getX() == x && thislock.getY() == y)
			{
				return thislock;
			}
		}
		return null;
	}
	
	function releaseLock(x,y)
	{
		var arrpos = 0;
		for(i=0;i<lockstack.length;i++)
		{
			var thislock = lockstack[i];
			if (thislock.getX() == x && thislock.getY() == y)
			{
				arrpos = i;
				break;
			}
		}
		lockstack.splice(arrpos,1)
	}


	// called on drag i think
	function setOffset(x,y)
	{	
		_viewport.offsetx = x;
		_viewport.offsety = y;
		
		_viewport.minx = Math.ceil(_viewport.offsetx / blocksize) * -1
		_viewport.miny = Math.ceil(_viewport.offsety / blocksize) * -1
		_viewport.maxx = Math.floor(  (_viewport.width - _viewport.offsetx) / blocksize )
		_viewport.maxy = Math.floor( (_viewport.height - _viewport.offsety) / blocksize )
				
		//updateOrigin();
	}

	function getOffsetX()
	{
		return _viewport.offsetx;
	}

	function getOffsetY()
	{
		return _viewport.offsety;
	}

	this.refreshViewport = function()
	{
		_viewport.width  = _container.clientWidth
		_viewport.height = _container.clientHeight
		
		// mins don't change for resize! only maxes
		_viewport.maxx = Math.floor(  (_viewport.width - _viewport.offsetx) / blocksize )
		_viewport.maxy = Math.floor( (_viewport.height - _viewport.offsety) / blocksize )
	}


	this.initialise = function()
	{
		// perform look-up here so canvas is cached for other calls (in draw, for example)

		//if(centerPicOnLoad != 0)
		//{
		//	_grid.center('p'+centerPicOnLoad)
		//}
		//centerGrid(centerPicOnLoad)

		/* special scenario for grid display with no pictures */		
		/** TODO extenalise this is 3rd party component that checks the below attributes */
		if(picturestack.length == 0 && _grid.isActive() == 'false')
		{
			//display: relies on external DOM elements
			var newgridmsg = document.getElementById('newgridmessage')
			if(newgridmsg)
				_canvas.appendChild(newgridmsg)
		}


		/* now we don't have create vs reposition i want to remove this drawing stuff and leave only bounds setting (with a call to draw() at end) */
		/* first recurse through pictures, this draws and establishes grid bounds */
		for(i=0;i<picturestack.length;i++)
		{
			testBlockLimit(picturestack[i])
		}
						
		/* create 2d array of pictures */
		// combine this with above so we only have one stack scan
		// add picRow[] and picCol[] for faster row/col getting (i.e. no blank spots to skip)
		_cols = blockextentright - blockextentleft
		_rows = blockextentdown - blockextentup
		_posgrid = new Array(_cols)
		
		for(var i=blockextentleft;i<=blockextentright;i++)
		{
			_posgrid[''+i] = new Array(_rows)
			//alert('setting up col ' + i + ' with ' + _rows + ' rows')
		}
				
		// populate hash with pics
		for(i=0,j=picturestack.length;i<j;i++)
		{
			var tp = picturestack[i]
			_posgrid[''+tp.xpos][''+tp.ypos] = tp
		}
		
		_lastdraw.minx = _viewport.minx
		_lastdraw.miny = _viewport.miny
		_lastdraw.maxx = _viewport.maxx
		_lastdraw.maxy = _viewport.maxy

		// bind events
		YAHOO.util.Event.addListener(document, 'mousemove', uiMouseMove);
		YAHOO.util.Event.addListener(document, 'mouseup',   uiMouseUp);
		YAHOO.util.Event.addListener(document, 'mousedown', uiMouseDown);
		YAHOO.util.Event.addListener(window, 'resize',    uiResize);
		
		/*
		if(is_ie)
		{
			document.onmousedown = uiMouseDown
			document.onmouseup   = uiMouseUp
			document.onmousemove = uiMouseMove
			window.onresize = uiResize
		} else {
			window.addEventListener('mousedown', uiMouseDown ,true)
			window.addEventListener('mouseup'  , uiMouseUp,   true)
			window.addEventListener('mousemove', uiMouseMove, true)
			window.addEventListener('resize',    uiResize,    true)
		}
		*/
		
		// fire off our load event
		eventFired('load')
		_loaded = true;
	}
	
	this.update = function()
	{
		var pic = picturestack[picturestack.length - 1]
		testBlockLimit(pic)
		drawPic(pic);
	}
	
	function drawPic(pic)
	{
		if( !pic.displayed &&
			pic.xpos >= _viewport.minx && pic.xpos <= _viewport.maxx &&
		    pic.ypos >= _viewport.miny && pic.ypos <= _viewport.maxy )
		{
			pic.displayed=true
			drawPictureFromObj(pic)
		}
	}
	
	function drawPictureFromObj(newpic)
	{	
		var ti = null;
		
		//alert('drawing picture ' + newpic.id)
	
		if(newpic.dom != null)
		{
			//if(debug) gridlog("restoring picture "+ newpic.id +" from DOM");
			ti = newpic.dom;
		} else {
			//if(debug) gridlog("instantiating picture "+ newpic.id);
			ti = newpic.getThumb()
			ti.className = 'pic'
			ti.style.position = 'absolute'
			ti.id = newpic.id;
			ti.style.left = (newpic.xpos * blocksize) + 'px';
			ti.style.top  = (newpic.ypos * blocksize) + 'px';
			
			//alert('drawing pic at ' + ti.style.left + ' , ' + ti.style.top)
			
			if(_grid.mode == 'full')
			{
				ti.onmouseover=function() { if(!modal) showAbout(newpic.title,parseInt(newpic.uploadDate,10),newpic.uploaderName) }
				ti.onmouseout=hideAbout
			}
			
			//ti.onmouseover = overPic
			//ti.onmouseout  = outPic
			
			//ti.onmousedown = overPic
			
			newpic.dom = ti;
			newpic.displayed = true
		}
		_canvas.appendChild(ti);
	}
		
	this.centerGrid = centerGrid
	function centerGrid(picid)
	{
		var acx = 0
		var acy = 0
		
		if(picid && picid != '')
		{
			var thispic = getPictureById('p'+picid)
			if(thispic)
			{
			
				// find center position of picture
				var xoff = -(blocksize*thispic.xpos + blocksize/2)
				var yoff = -(blocksize*thispic.ypos + blocksize/2)
										
				acx = Math.round(_viewport.width/2 + xoff)
				acy = Math.round(_viewport.height/2 + yoff)

			} else {
				// actual center
				acx = Math.round(_viewport.width/2) - 50
				acy = Math.round(_viewport.height/2) - 50			
			}

		} else {
	
			// actual center
			acx = Math.round(_viewport.width/2) - 50
			acy = Math.round(_viewport.height/2) - 50
		
		}
		
		// tweaked because of dashboard & header influence on what looks centered
		acy -= 40
		
		setOffset(acx,acy)
		_canvas.style.left = acx + 'px'
		_canvas.style.top  = acy + 'px'
		//alert('move canvas to ' + acx + ',' + acy)
		
		_container.style.backgroundPosition = acx%100 + "px " + acy%100 + "px";
	}
	
	this.plantSeed = function(picobj)
	{
		_canvas.style.left = _viewport.offsetx + 'px';
		_canvas.style.top  = _viewport.offsety + 'px';
		_container.style.backgroundPosition = _viewport.offsetx%100 + "px " + _viewport.offsety%100 + "px";	
		drawPictureFromObj(picobj)
	}
	
	this.waterSeed = function(picobj)
	{
		setTimeout(function() { growSeed(null,picobj) }, 50)
	}
	
	function growSeed(from,seed)
	{
		if(from)
			drawPictureFromObj(seed)
		
		//top
		var tmppic = _posgrid[''+seed.getX()][''+(parseInt(seed.getY(),10)-1)]
		//alert('top: ' + tmppic)
		if(tmppic && (!from || tmppic.id != from.getId()))
		{
			if(!tmppic.displayed)
				setTimeout(function() { growSeed(seed,tmppic) }, 50)
		}
			
		//bottom
		var tmppicb = _posgrid[''+seed.getX()][''+(parseInt(seed.getY())+1)]
		//alert('pos ' + seed.getX() + ',' + (seed.getY()+1) + ' = ' + tmppicb)
		if(tmppicb && (!from || tmppicb.getId() != from.getId()))
		{
			if(!tmppicb.displayed)
				setTimeout(function() { growSeed(seed,tmppicb) }, 50)
		}
			
		//left
		if(_posgrid[''+(parseInt(seed.getX(),10)-1)])
		{
			var tmppicl = _posgrid[''+(parseInt(seed.getX(),10)-1)][''+seed.getY()]
			//alert(tmppicl)
			if(tmppicl && (!from || tmppicl.getId() != from.getId()))
			{
				if(!tmppicl.displayed)
					setTimeout(function() { growSeed(seed,tmppicl) }, 50)
			}
		}
		
		//right
		if(_posgrid[''+(parseInt(seed.getX(),10)+1)])
		{
			var tmppicr = _posgrid[''+(parseInt(seed.getX(),10)+1)][''+seed.getY()]
			//alert(tmppicr)
			if(tmppicr && (!from || tmppicr.getId() != from.getId()))
			{
				if(!tmppicr.displayed)
					setTimeout(function() { growSeed(seed,tmppicr) }, 50)
			}
		}
	}
	
	// routine to spiral out from center
	this.spiralDraw = function(startpic)
	{
	
		var startx = parseInt(startpic.xpos,10);
		var starty = parseInt(startpic.ypos,10);
		
		var toprow=starty,leftcol=startx,bottomrow=starty,rightcol=startx;
		
		var radius = 0
		
		var leftextent=_viewport.minx,rightextent=_viewport.maxx,topextent=_viewport.miny,bottomextent=_viewport.maxy
		
		// viewport is sometimes bigger than grid define extents as the smaller of the two
		if(blockextentleft > _viewport.minx)
			leftextent = blockextentleft
			
		if(blockextentright < _viewport.maxx)
			rightextent = blockextentright
			
		if(blockextentup > _viewport.miny)
			topextent = blockextentup
			
		if(blockextentdown < _viewport.maxx)
			bottomextent = blockextentdown
		
		while(toprow >= topextent || bottomrow <= bottomextent || leftcol >= leftextent || rightcol <= rightextent)
		{
		
			//alert('radius = ' + radius)
		
			var sideradius = radius - 1
			if(sideradius < 0)
				sideradius = 0
		
			// right col (don't need to draw top pic because top draw will do that)
			rightcol    = startx + radius
			//alert(rightcol + ' <= ' + _viewport.maxx)
			if(rightcol <= rightextent)
			{
				var righttop    = starty - sideradius
				var rightbottom = starty + sideradius
				for(var y=righttop; y<=rightbottom; y++)
				{
					if(_posgrid[''+rightcol])
					{
						var thispic = _posgrid[''+rightcol][''+y]
						if(thispic && !thispic.displayed)
							addToDrawQueue(thispic)
					}				
				}			
			}
			
			// bottom row
			bottomrow   = starty + radius
			if(bottomrow <= bottomextent)
			{
				var bottomleft  = startx - radius
				var bottomright = startx + radius
				for(var x=bottomright; x>=bottomleft; x--)
				{
					//alert('bottom: drawing ' + x + ',' + bottomrow)
					if(_posgrid[''+x])
					{
						var thispic = _posgrid[''+x][''+bottomrow]
						if(thispic && !thispic.displayed)
							addToDrawQueue(thispic)
					}				
				}
			}
			
			// left col (don't need to draw top pic because top draw will do that)
			leftcol    = startx - radius
			if(leftcol >= leftextent)
			{
				var lefttop    = starty - sideradius
				var leftbottom = starty + sideradius
				for(var y=leftbottom; y>=lefttop; y--)
				{
					if(_posgrid[''+leftcol])
					{
						var thispic = _posgrid[''+leftcol][''+y]
						if(thispic && !thispic.displayed)
							addToDrawQueue(thispic)
					}				
				}			
			}
			
			// top row
			toprow   = starty - radius
			if(toprow >= topextent)
			{
				var topleft  = startx - radius
				var topright = startx + radius
				for(var x=topleft; x<=topright; x++)
				{
					if(_posgrid[''+x])
					{
						var thispic = _posgrid[''+x][''+toprow]
						if(thispic && !thispic.displayed)
							addToDrawQueue(thispic)
					}				
				}
			}
			
			radius++
			
			//alert('top/bottom = ' + toprow + '-' + bottomrow + ' left/right = ' + leftcol + '-' + rightcol)

		}

		_lastdraw.minx = _viewport.minx
		_lastdraw.maxx = _viewport.maxx
		_lastdraw.miny = _viewport.miny
		_lastdraw.maxy = _viewport.maxy	
		
	}
	
	this.safedraw = function()
	{
	
		//alert('drawing cols ' + _viewport.minx + ' to ' + _viewport.maxx + ' and rows ' +  _viewport.miny + ' to ' + _viewport.maxy)
	
		for(var col = _viewport.minx; col <= _viewport.maxx; col++)
		{
			drawColumn(col)	
		}

				_lastdraw.minx = _viewport.minx
				_lastdraw.maxx = _viewport.maxx
				_lastdraw.miny = _viewport.miny
				_lastdraw.maxy = _viewport.maxy

	}

	/* important function called on drag & resize */
	this.draw = function ()
	{
	
		if(isRedrawing)
			return
	
		//eventFired('move');
	
		_canvas.style.left = _viewport.offsetx + 'px';
		_canvas.style.top  = _viewport.offsety + 'px';
		_container.style.backgroundPosition = _viewport.offsetx%100 + "px " + _viewport.offsety%100 + "px";

		// new culling algorithm makes use of 2d hash and a bounds tracking op to see if we need to cull/add
		// and if we do it's simple 2d array crawl - not an entire dataset search! should be able to then call
		// this every cycle without much expense
		
		// need to make it work for new pictures added
		// need to only draw pics at first which are visible.
		
		// redraw if first time call or mins or maxes have changed
		if( redrawRequired() )
		{
			isRedrawing = true

			if(_lastdraw.minx != _viewport.minx)
			{
			
				// don't draw beyond bounds
				if(_viewport.minx <= blockextentleft && _lastdraw.minx <= blockextentleft)
				{} else {
		
					// cull _lastdraw.minx column if < _viewport.minx
					// else draw _viewport.minx column if < _lastdraw.minx
					if(_lastdraw.minx < _viewport.minx)
					{
						for(var col = _lastdraw.minx; col<_viewport.minx; col++)
						{
							//if (debug) gridlog("Culling columns " + _lastdraw.minx + " to " + _viewport.minx);
							cullColumn(col)
						}
					} else {
						for(var col = _viewport.minx; col<_lastdraw.minx; col++)
							drawColumn(col)
					}
				}
				_lastdraw.minx = _viewport.minx
			}
			
			if(_lastdraw.maxx != _viewport.maxx)
			{
					
				// don't draw beyond bounds
				if(_viewport.maxx >= blockextentright && _lastdraw.maxx >= blockextentright)
				{} else {
					
					// cull _lastdraw.maxx column if > _viewport.maxx
					// else draw _viewport.maxx column if > _lastdraw.maxx
					if(_lastdraw.maxx > _viewport.maxx)
					{
						//alert('culling ' + _lastdraw.maxx)
						for(var col = _lastdraw.maxx ; col>_viewport.maxx; col--)
							cullColumn(col)
					} else {
					    //alert('drawing ' + _viewport.maxx + ' down to ' + _lastdraw.maxx)
						for(var col = _viewport.maxx ; col>_lastdraw.maxx; col--)
							drawColumn(col)
					}
				}
				_lastdraw.maxx = _viewport.maxx
			}			
			
			if(_lastdraw.miny != _viewport.miny)
			{
			
				if(_viewport.miny <= blockextentup && _lastdraw.miny <= blockextentup)
				{} else {
					// cull _lastdraw.miny row if < _viewport.miny
					// else draw _viewport.miny row if < _lastdraw.miny
					if(_lastdraw.miny < _viewport.miny) {
					 //if (debug) gridlog("Culling rows " + _lastdraw.miny + " to " + _viewport.miny);
						for(var row = _lastdraw.miny; row<_viewport.miny; row++)
							cullRow(row)
					} else {
						//if (debug) gridlog("Drawing rows " + _viewport.miny + " to " + _lastdraw.miny + " between " + _viewport.minx + " and " + _viewport.maxx);
						for(var row = _viewport.miny; row<_lastdraw.miny; row++)
							drawRow(row)
					}
				}
				_lastdraw.miny = _viewport.miny
			}
			
			if(_lastdraw.maxy != _viewport.maxy)
			{
			
				if(_viewport.maxy >= blockextentdown && _lastdraw.maxy >= blockextentdown)
				{} else {
					// cull _lastdraw.maxy row if > _viewport.maxy
					// else draw _viewport.maxy row if > _lastdraw.maxy
					if(_lastdraw.maxy > _viewport.maxy) {
						for(var row = _lastdraw.maxy; row>_viewport.maxy; row--)
							cullRow(row)
					} else {
						//if (debug) gridlog("Drawing rows " + _viewport.maxy + " to " + _lastdraw.maxy);
						for(var row = _viewport.maxy; row>_lastdraw.maxy; row--)
							drawRow(row)
					}
				}
				_lastdraw.maxy = _viewport.maxy
			}
			
			isRedrawing = false
			
		} else {
			// this is what we want -- no unnecessary scans
			// should give us unlimited scalability of grid size
			return
		}
	}
	
	function cullRow(row)
	{
		for(var col = blockextentleft; col <= blockextentright; col++)
		{
			// won't always return pic because grid is not solid -- maybe we actually register each pic against
			// seperate row and column hashes so we can picsFromCol['col'] -- then we get relevant rowid from getY()
			// for now we do this -- null lookup hopefully not too expensive. maybe a more optimised way to convert
			// a number to a string than ('' + num) - other option is String(num) -- benchmark to see which is faster
			
			// return if empty column
			if(!_posgrid[''+col])
				continue
			var pic = _posgrid[''+col][''+row]
			if(pic)
				cullPic(pic)
		}	
	}
	
	function drawRow(row)
	{
		// draw visible - not invisible.
		//for(var col = blockextentleft; col <= blockextentright; col++)
		for(var col = _viewport.minx; col<=_viewport.maxx;col++)
		{
			// return if empty column: GOTCHA OF THE CENTURY - replace BAD BAD return with continue
			if(!_posgrid[''+col])
				continue
			var pic = _posgrid[''+col][''+row]
			if(pic)
				drawPic(pic)
		}	
	}
	
	function cullColumn(col)
	{
		for(var row = blockextentup; row <= blockextentdown; row++)
		{
			// return if empty column
			if(!_posgrid[''+col])
				return
				
			var pic = _posgrid[''+col][''+row]
			if(pic)
				cullPic(pic)
		}
	}
	
	function drawColumn(col)
	{
		//alert('drawing column ' + col)
		// draw visible - not invisible.
		//for(var row = blockextentup; row <= blockextentdown; row++)
		for(var row=_viewport.miny; row <= _viewport.maxy; row++)
		{
		
			// return if empty column
			if(!_posgrid[''+col])
				return
				
			var pic = _posgrid[''+col][''+row]
			if(pic)
				drawPic(pic)
		}
	}
	
	function cullPic(pic)
	{
	
		if(pic.displayed)
		{			
			pic.displayed = false
			var picdiv = document.getElementById(pic.id)
			if(picdiv)
				picdiv.parentNode.removeChild(picdiv)
		}
	}
	
	function redrawRequired()
	{

		if (_lastdraw.minx != _viewport.minx || _lastdraw.miny != _viewport.miny || _lastdraw.maxx != _viewport.maxx || _lastdraw.maxy != _viewport.maxy )
		{
			//alert('gotta redraw')
			return true
		}
		
		return false
	}
	
	this.center = function(picid)
	{
		var thispic = getPictureById(picid)

		if(thispic == null)
		{
			return
		}
			
		// find center position of picture
		var xoff = -(blocksize*thispic.xpos + blocksize/2)
		var yoff = -(blocksize*thispic.ypos + blocksize/2)
		
		// tweaked because of dashboard & header influence on what looks centered
		yoff -= 40
		
		// so final point is the above + center of viewport
		var vcx = _viewport.width/2
		var vcy = _viewport.height/2
		
		var p4 = new Point(Math.round(_viewport.width/2 + xoff),Math.round(_viewport.height/2 + yoff))
		var p3 = new Point(Math.round(_viewport.width/2 + xoff),Math.round(_viewport.height/2 + yoff))

		// randomise the 2nd control point for unpredictable touch +/- blocksize/4
		var randomx=0,randomy=0
		//var randomx = Math.round(Math.random(1) * (blocksize/2)) - blocksize/4
		//var randomy = Math.round(Math.random(1) * (blocksize/2)) - blocksize/4

		var p2 = new Point(_viewport.offsetx+randomx,_viewport.offsety+randomy)
		var p1 = new Point(_viewport.offsetx,_viewport.offsety)
		
		new BezierMover(document.getElementById('canvas'),p1,p2,p4,p4)
	
	}
	
	this.moveToCoords = function(x,y)
	{

		var p4 = new Point(x,y)
		var p3 = new Point(x,y)
		
		var p2 = new Point(_viewport.offsetx,_viewport.offsety)
		var p1 = new Point(_viewport.offsetx,_viewport.offsety)
		
		new BezierMover(document.getElementById('canvas'),p1,p2,p3,p4)


	}
	
	function isInBounds(x,y)
	{
	
		if( x+100 > -_viewport.offsetx              &&
		    x < _viewport.width-_viewport.offsetx     &&
		    y+100 > -_viewport.offsety              &&
		    y < _viewport.height-_viewport.offsety )
			return true
	
		return false
	}
	
	function tileIsInBounds(x,y)
	{
	
	}

	
	// returns tile coords of mouse
	this.getBlockCoords = function(x,y)
	{
		var mappedposx = x-_viewport.offsetx
		var mappedposy = y-_viewport.offsety

		var offsetx = Math.floor( mappedposx/blocksize )
		var offsety = Math.floor( mappedposy/blocksize )
		
		return new Point(offsetx,offsety)
	}

	// check whether block is available (i.e. 1 and only 1 adjacent neighbour)
	this.isAvailable = function(offx,offy)
	{

		var found = false;
		
		if(_grid.getPicture(offx,offy))
			return false;
			
		// now we depend on style:
		if(style == "4")
			return true
			
		if(style == "2")
		{
			if( _grid.getPicture(offx,offy-1) || _grid.getPicture(offx,offy+1) || _grid.getPicture(offx-1,offy) || _grid.getPicture(offx+1,offy))
			{
				return true;
			} else {
				return false
			}
		}

		//if(getLock(offx,offy) || getLock(offx,offy-1) || getLock(offx,offy+1) || getLock(offx-1,offy) || getLock(offx+1,offy) )
		//	return false;

		// check same x with y-1 and y+1
		if(_grid.getPicture(offx,offy-1))
			found = true;

		if(_grid.getPicture(offx,offy+1)) {
			if(found)
				return false;
			found = true
		}

		// check same y with x-1 and x+1
		if(_grid.getPicture(offx-1,offy)) {
			if(found)
				return false;
			found = true
		}

		if(_grid.getPicture(offx+1,offy)) {
			if(found)
				return false;
			found = true
		}

		return found;
	}
	
	// determine whether block can be deleted
	this.canDelete = function(offx,offy)
	{

		var found = false;
				
		// scatter - can delete any
		if(style == "4" || style=="2")
			return true
			
		// only return true the block is connected by 1 and only 1 side.

		// check same x with y-1 and y+1
		if(_grid.getPicture(offx,offy-1))
		{
			found = true;
		}
		
		if(_grid.getPicture(offx,offy+1)) {
			if(found)
				return false;
			found = true
		}

		// check same y with x-1 and x+1
		if(_grid.getPicture(offx-1,offy)) {
			if(found)
				return false;
			found = true
		}

		if(_grid.getPicture(offx+1,offy)) {
			if(found)
				return false;
			found = true
		}

		return found;
	}	
	
	// find the filled block adjacent to the specified one
	// function assumes there is only one and returns the first found
	// i.e. this is called by the upload script
	this.getAdjacentBlock = function(bx,by)
	{
		if(_grid.getPicture(bx,by-1))
			return new Point(bx,by-1);

		if(_grid.getPicture(bx,by+1))
			return new Point(bx,by+1);

		if(_grid.getPicture(bx-1,by))
			return new Point(bx-1,by);

		if(_grid.getPicture(bx+1,by))
			return new Point(bx+1,by);
	}
	
	
	/* EVENT HANDLING SYSTEM
	 * allows external components to listen to grid events - e.g. minimap and dashboard (maybe uploader)
	 * 
	 * Event       Description                           Returns
	 * load      Fired once grid & data have loaded    grid info object
	 * move      Fired on grid movement - WARNING      grid viewport (left,top,width,height,offset left,offset right)
	 * tilehover Fired when mouse over (new) tile      tile object
	 * reload    Fired on a grid refresh               grid info object
	 * resize    Fired on a window/grid resize         grid viewport
	 * update    Fired when a new image is added	   grif info object?
     *
	 */
	 
	var eventRegistry = {}
	var eventguid = 1000000
	
	this.registerEventListener = registerEventListener;
	function registerEventListener(event,fn)
	{
		
		//alert('listener registered for ' + event)
		if(!fn._eventGUID)
			fn._eventGUID = ++eventguid
	
		if( typeof eventRegistry[event] != "object" )
		{
			eventRegistry[event] = new Array(1);
			eventRegistry[event][0] = fn;
		} else {
			eventRegistry[event].push(fn);
		}
	}
	
	this.destroyEventListener = destroyEventListener
	function destroyEventListener(event,fn)
	{
		if(!eventRegistry[event] || eventRegistry[event].length == 0)
		{
			return
		}
	
		for (var i=0,j=eventRegistry[event].length;i<j;i++)
		{
			if(eventRegistry[event][i]._eventGUID == fn._eventGUID)
			{
				eventRegistry[event].splice(i,1)
			}
		}
	}
	
	function eventFired(event)
	{
		//alert(eventRegistry[event].length + ' calls for ' + event)
		if( typeof eventRegistry[event] == "object" )
		{
			var returnobj = getEventReturnObject(event);	
			for(var i=0;i<eventRegistry[event].length;i++)
			{
				//if(event == 'load')
				//	alert('firing ' + event + ' at ' + i)
				eventRegistry[event][i](returnobj);
			}
		}
	}
	
	function getEventReturnObject(event)
	{
		if(event == "load" || event == "update")
			return { id: id, title: title, url: url, description: description, datecreated: datecreated, creator: creator, size: picturestack.length, tags: tagstack, messages: _grid.getMessages(), isOwner: (currentuser==creatorid) };
	
		if(event == "move")
			return { width: _viewport.width, height: _viewport.height, offsetx: _viewport.offsetx, offsety: _viewport.offsety,
			ldminx: _lastdraw.minx, ldmaxx: _lastdraw.maxx, minx: _viewport.minx, maxx: _viewport.maxx, miny: _viewport.miny, maxy: _viewport.maxy, numdisplayed: displayedCount()   };
	
		if(event == "debug")
			return { message: debugstack[debugstack.length-1] }
	
		return {};
	}



	
	/* UI methods -- mouse / key event handling */
	/* TODO get rid of branched code - someone must have worked out x-platform event handling */
	var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
	var stx = 0; var sty = 0;
	var fx = 0; var fy = 0;
	var offx = 0; var offy = 0;
	var drg = false;
	var lastclicked = null;

	function uiMouseDown (e)
	{
	
		var button = e.which || e.button;
		if(button > 1)
			return
	
		stx = fx = YAHOO.util.Event.getPageX(e)
		sty = fy = YAHOO.util.Event.getPageY(e)
		var tgt = YAHOO.util.Event.getTarget(e)
		
		if(tgt.id == 'grid' || tgt.id == 'canvas' || tgt.className == 'pic' || tgt.id == 'marker')
		{
			drg = true;
		}
		
		lastclicked = tgt
	}
	
	if(typeof Resizer != 'undefined')
		var _resizeHandler = new Resizer()
	function uiMouseUp (e)
	{
		offx = YAHOO.util.Event.getPageX(e)
		offy = YAHOO.util.Event.getPageY(e)
		var tgt = YAHOO.util.Event.getTarget(e)

		drg = mdrg = false;
	
		if(tgt.className == 'pic' && !modal)
		{
			//
			if( !modal && Math.sqrt( Math.pow(Math.abs(fx-offx),2) + Math.pow(Math.abs(fy-offy),2)) < 10)
			{
			
			modal = true
			var pic = getPictureById(tgt.id)
			var cob = new CarbineOptionsBox()

			var links = new Array(0)
			
			links.push({text: '&gt; ZOOM', link: function() { cob.close(); modal = true; var zm = new Zoomer(); zm.create(tgt,offx,offy,_grid,function() { modal = false }) }})
			
			// construct re-crop link if we the owner
			if( _grid.getCurrentUser() == pic.uploaderId )
				links.push({text: '&gt; RE-CROP IMAGE', link: function() { cob.close(); modal = true; _resizeHandler.create(tgt,offx,offy,_grid,function() { modal = false }) }})
				
				
			// delete if allowed
			if( (_grid.getCurrentUser() == pic.uploaderId || _grid.getCurrentUser() == _grid.getCreatorId()) &&
					 _grid.canDelete(parseInt(pic.xpos,10),parseInt(pic.ypos,10)))
			{
					links.push({text: '&gt; DELETE THIS IMAGE', link: function() { cob.close(); _dao.remove(_grid.getId(),pic.id.substring(1),function() { _grid.removePicture(pic); eventFired("update") } ); return false }})
			}
			
			//picture link
			if(pic.url)
			{
				var linktext = ''
				if(pic.url.indexOf('amazon.com') > -1)
				{
					linktext ='&gt; View at Amazon'
				} else {
					if(pic.url.indexOf('flickr.com') > -1)
					{
						linktext ='&gt; View at Flickr'
					} else {
						linktext ='&gt; PICTURE LINK'
					}
				}
				links.push( {text: linktext, link: function() { document.location = pic.url; return false }} )				
			}
		
			
			cob.create('OPTIONS',links,function() { modal = false },offx,offy,_grid)
			//_resizeHandler.create(tgt,offx,offy,_grid,function() { modal = false })
			
			// if movement is less than 10 pixels load image info
			//if( Math.sqrt( Math.pow(Math.abs(fx-offx),2) + Math.pow(Math.abs(fy-offy),2)) < 10)
			//	zoomInPic(tgt.id)
			}
		}
		

	}

	function uiMouseMove (e)
	{
	
		offx = YAHOO.util.Event.getPageX(e)
		offy = YAHOO.util.Event.getPageY(e)

		if(drg && ( (offx - stx) != 0 || (offy - sty) != 0))
		{
			if(isRedrawing)
				return
				
			_grid.setOffset( _viewport.offsetx - (stx - offx), _viewport.offsety - (sty - offy))
			stx = offx;
			sty = offy;
			_grid.draw();
		} else {
			if(_uploader && !modal)
				_uploader.activateSquare(offx,offy);
		}
		
		var pcnf = document.getElementById('pcnf')
		if(pcnf)
		{
			pcnf.style.display=''
			pcnf.style.left = offx + 5 + 'px'
			pcnf.style.top  = offy + 5 + 'px'
		}	
		
		return false;
	}
	
	function uiResize (e)
	{
		//alert('resized!')
		_grid.refreshViewport()
		_grid.draw()
		eventFired("resize")
	}
	
	
	
	
	
	/***********/
	
	function hideDetails(picid)
	{
		var details = document.getElementById('details_'+picid)
		details.parentNode.removeChild(details)	
	}
	
	function showDetails(obj,picid)
	{
		var pic = getPictureById(picid)
		if(pic)
		{		
			var idiv = document.createElement('div')
			idiv.id = 'details_'+picid
			idiv.className = 'pcdt'
			idiv.style.visibility = 'hidden'
			idiv.style.position = 'absolute'
			idiv.style.zIndex = zfactor++
			idiv.style.left = obj.offsetLeft + obj.clientWidth - 500 + 'px'
						
			var seg = document.createElement('div')
			if(pic.title)
			{
				seg.innerHTML = '&#147;'+pic.title+'&#148;'
			} else {
				seg.innerHTML = '&#147;UNTITLED&#148;'
			}
			idiv.appendChild(seg)
			
			var fl = document.createElement('div')
			if(_grid.getCurrentUser() == pic.uploaderId)
			{
				fl.innerHTML='Uploaded by you'
			} else {
				fl.innerHTML='Uploaded by '+pic.uploaderName
			}
			idiv.appendChild(fl)
			

			if( !(pic.xpos == '0' &&  pic.ypos == '0') )
			{		
				if( (_grid.getCurrentUser() == pic.uploaderId || _grid.getCurrentUser() == _grid.getCreatorId()) &&
					 _grid.canDelete(parseInt(pic.xpos,10),parseInt(pic.ypos,10)))
				{
					var fl = document.createElement('div')
					var dellink = document.createElement('a')
					dellink.href="#"
					dellink.onclick = function() { _dao.remove(_grid.getId(),pic.id.substring(1),function() { hideDetails(picid); _grid.removePicture(pic); eventFired("update") } ); return false }
					dellink.appendChild(document.createTextNode('delete'))
					fl.appendChild(dellink)
					idiv.appendChild(fl)
				}
			}
			
			seg = document.createElement('div')
			if(pic.url != null && pic.url.length > 0)
			{
				var a = document.createElement('a')
				a.href=pic.url
				a.target="_blank"
				if(pic.url.indexOf('amazon.com') > -1)
				{
					a.appendChild(document.createTextNode('View at Amazon'))
				} else {
					if(pic.url.indexOf('flickr.com') > -1)
					{
						a.appendChild(document.createTextNode('View at Flickr'))
					} else {
						a.appendChild(document.createTextNode('PICTURE LINK'))
					}
				}
				seg.appendChild(a)
			} else {
				seg.appendChild(document.createTextNode('- No link -' ))
			}
			idiv.appendChild(seg)
			
			_canvas.appendChild(idiv)
			idiv.style.top = obj.offsetTop + obj.clientHeight - idiv.clientHeight + 'px'
			idiv.style.visibility = 'visible'
			
		}	
	}
	
	this.loadInspector = function(picid)
	{
		var pic = getPictureById(picid)
		if(pic)
		{
		
			var pidiv = document.getElementById('inspector')
			
			if(pidiv)
			{
				while(pidiv.firstChild)
					pidiv.removeChild(pidiv.firstChild)
			} else {
				pidiv = document.createElement('div')
				pidiv.id = 'inspector'
			}
			
			var close = document.createElement('img')
			close.className = 'close'
			close.src = '../images/info_close.gif'
			close.onclick = function () { pidiv.parentNode.removeChild(pidiv) }
			pidiv.appendChild(close)
			
			var seg = document.createElement('div')
			if(pic.title())
			{
				seg.innerHTML = '&#147;'+pic.title()+'&#148;'
			} else {
				seg.innerHTML = '&#147;UNTITLED&#148;'
			}
			pidiv.appendChild(seg)
			
			seg = document.createElement('div')
			seg.appendChild(document.createTextNode('Uploaded by ' + pic.uploaderName ))
			pidiv.appendChild(seg)		
			
			seg = document.createElement('div')
			var pdt = new Date(parseInt(pic.uploadDate,10))
			seg.appendChild(document.createTextNode( pdt.getDate() + ' ' + monthnames[pdt.getMonth()] + ' ' + pdt.getFullYear() ))
			pidiv.appendChild(seg)
			
			seg = document.createElement('div')
			if(pic.url != null && pic.url.length > 0)
			{
				var a = document.createElement('a')
				a.href=pic.url
				a.target="_blank"
				if(pic.url.indexOf('amazon.com') > -1)
				{
					a.appendChild(document.createTextNode('Buy this album'))
				} else {
					if(pic.url.indexOf('flickr.com') > -1)
					{
						a.appendChild(document.createTextNode('View at Flickr'))
					} else {
						a.appendChild(document.createTextNode('PICTURE LINK'))
					}
				}
				seg.appendChild(a)
			} else {
				seg.appendChild(document.createTextNode('- No link -' ))
			}
			pidiv.appendChild(seg)
			
			// tools
			seg = document.createElement('div')
			seg.className = 'tools'
			
			var tool = document.createElement('img')
			tool.src = '../images/info_tool_center.gif'
			tool.onclick = function() { _grid.center(picid) }
			seg.appendChild(tool)
			tool = document.createElement('img')
			tool.src = '../images/info_tool_report.gif'
			seg.appendChild(tool)
			tool = document.createElement('img')
			tool.src = '../images/info_tool_delete.gif'
			seg.appendChild(tool)
			
			pidiv.appendChild(seg)
			
			
			var picdom = pic.dom
			pidiv.style.top = picdom.offsetTop + 'px'
			pidiv.style.left = picdom.offsetLeft + picdom.clientWidth + 'px'
			
			_canvas.appendChild(pidiv)
		
		
		} else {
			alert('picture not found: ' + picid)
		}
	}
	
}


/*********** END GRID ***************/























function Picture ()
{

	this.loaded = false

	this.id = null;
	this.title = null;
	this.url = null;
	this.xpos = 0;
	this.ypos = 0;
	this.uploaderName = null;
	this.uploaderId = null;
	this.uploadDate = null;
	this.dom = null;
	this.thumbSrc = '';
	this.thumbData = document.createElement('img'); //broke in safari: new Image();
	this.thumbData.onmousedown = function () { return false }
	this.displayed = false;
	
}

Picture.prototype.getThumb = function() {
	if(!this.loaded)
	{
		this.thumbData.src = this.thumbSrc
		this.loaded = true
	}
	return this.thumbData	
}

function Message ()
{
	this.id       = null;
	this.gridId   = null;
	this.userName = null;
	this.messageDate = null;
	this.text     = null;
}

Picture.prototype.fromJson = function(hsh) {
	var nmsg = new Message()
}


function randomvalue(low, high) {
	return Math.floor(Math.random() * (1 + high - low) + low);
}

function Lock ()
{

	var xpos = 0;
	var ypos = 0;
	var user = '';

	var id = 'p' + randomvalue(10000,99999);

	this.getId = getId;
	this.setId = setId;
	this.setPos = setPos;
	this.getX = getX;
	this.getY = getY;
	this.setUser = setUser;
	this.getUser = getUser;

	function setId(id1)
	{
		id = id1;
	}

	function getId()
	{
		return id;
	}

	function setPos(x,y)
	{
		xpos = x;
		ypos = y;
	}

	function getX()
	{
		return xpos;
	}

	function getY()
	{
		return ypos;
	}
	
	function setUser(usr)
	{
		user = usr;
	}
	
	function getUser()
	{
		return user;
	}
}

function Tag()
{
	this.id = null;
	this.name = null;
	this.userid = null;
}

function Uploader()
{
	this.url = null
	this.method = null
	this.params = null
}





function BezierMover(obj,p1,p2,p3,p4)
{

	//alert("let's fly to " + p4)

	var me = this
	var x = p1.x;
	var y = p1.y;
	var inc = 0.075; // 20 steps to 1
	var t = 0.0;
	var flying = false;
	var domobj = obj;
	
	function fly()
	{
		
		var cx = 3 * (p2.x - p1.x)
		var bx = 3 * (p3.x - p2.x) - cx
		var ax = p4.x - p1.x - cx - bx

		var cy = 3 * (p2.y - p1.y)
		var by = 3 * (p3.y - p2.y) - cy
		var ay = p4.y - p1.y - cy - by

		x = (ax * Math.pow(t,3)) + (bx * Math.pow(t,2)) + (cx * t) + p1.x;
		y = (ay * Math.pow(t,3)) + (by * Math.pow(t,2)) + (cy * t) + p1.y;
		t = t + inc

		GRID.setOffset(Math.round(x),Math.round(y))
		GRID.draw()

		if(t <= 1)
		{
			setTimeout(function(){ fly() }, 20);
		} else {
			t = 0
		}
	}
	
	fly()

}

function Point(x,y)
{
	this.x=x
	this.y=y
	
	this.toString = function() {
		return x+','+y
	}
}
