/*
 * Grid class
 *
 * It controls and manages the displayed grid and object snapping.
 *
 * Basically has 2 operating modes:
 * 	- global settings which generates a global grid, and snap settings for it.
 * 	- personalized (or dynamic) which is generated dinamically from the current selected element
 *
 * the grid is just an background image for $(documentContainer)
 * the snap is used while dragging elements around, to select a "snapping" point according to the selected grid (or snappping)
 *
 *
 * internal attribtues:
 * 		snapX/Y the points where the snapping occurs. the actual snap point is determined as (object.x % snapX),
 * 			so we only define the "period" where snapping takes place
 *
 * 		dynamicContent - a function which returns the snapping points, so a dynamic grid/snap could be used
 *
 * 		* gridType_ - the value of the grid type represented as " type | x | y" - usually taken from $(page_grid_) selector
 * 		* gridType - the type of the grid taken from gridType_
 * 		* gridX/Y the grid size(period).
 * 			Due to a weird bug, i have to generate a grid of at least 128x128px because the objects seemed to get misaligned for small grids
 * */
function Grid( attributes ){// constructor for the main class
	if(this.name==null || !this.name )	{
		this.name='Grid';
	}

	// internal variables, used pretty often (internally)
	this._heap=new Array();
	this.attributes={};
	this.initialize(attributes);

	// theese are pretty much static
	this.page=$('documentContainer');
	this.pageDimensions=Element.getDimensions(this.page);

	this.pageTemplate=new _cloneFactory('pageTemplate_.:id:.',{'id':	{	type:'int',	start:'1'	}, 'pageDescription':{type:'customControlled',start:'0'}},{bulkEval:true});

	this.version='1.0.1.12';

	this.debug=false;

	//each Grid has its own event scheduler, used for scheduling grid updates
	this.eventScheduler=new _eventScheduler();

	// create the Grid object. It will attach global events and handlers
	this.create();

	if(attributes){
		this._heap['pageW']=attributes.pageW;
		this._heap['pageH']=attributes.pageH;
	}
	this._heap['pages']=0;
	this._heap['generatedPages']=new Array();

	return true;
}

// general initialiation function. Called directly by the contructor.
Grid.prototype.initialize=function (attributes){
	// things may go wrong if this is 0 !
	this.attributes.snapX=1;
	this.attributes.snapY=1;

	this.attributes.gridX=1;
	this.attributes.gridY=1;

	return true;
}

/* here we create and attach the Control to the DOM. Also bind any needed events.
 *  calls crealGrid by default
 */
Grid.prototype.create=function (){
	this._heap['grid']=new Array();

	this.clearGrid();
	return true;
}

// called when destroying the grid (page-cleanup)
Grid.prototype.destroy=function (){
	this.clearGrid();
	this.eventScheduler.destroy();
	this._heap=null;
	this.attributes=null;
}

/*
 * internal:  returns snapX/Y according to gridSettings attribtue.
 * 	used by setupGrid
 * */
Grid.prototype.transformGridSettings2px=function (gridSettings){
	var ret={};
	ret.global=true;

	if(gridSettings==null)gridSettings={};

	if(!gridSettings.gridX && gridSettings.gridXmm)gridSettings.gridX=mm2px(gridSettings.gridX);
	if(!gridSettings.gridY && gridSettings.gridYmm)gridSettings.gridY=mm2px(gridSettings.gridY);

	switch(gridSettings.grid){
		case '': 				// get grid from object dimensions (width and height)
			ret.gridX=gridSettings.gridX;
			ret.gridY=gridSettings.gridY;

			ret.global=false;
		break;
		case 'x/2': 			// get grid from object width
			ret.gridX=Math.round(gridSettings.gridX/2);
			ret.gridY=ret.gridX;

			ret.global=false;
		break;
		case 'y/2': 			// get grid from object height
			ret.gridX=Math.round(gridSettings.gridY/2);
			ret.gridY=ret.gridX;

			ret.global=false;
		break;
		case 'x': 				// get grid from object width
			ret.gridX=gridSettings.gridX;
			ret.gridY=ret.gridX;

			ret.global=false;
		break;
		case 'y': 				// get grid from object height
			ret.gridY=gridSettings.gridY;
			ret.gridX=ret.gridY;

			ret.global=false;
		break;

		default:				// get grid from object height
			ret.global=true;
		break;
	}
	return ret;
}

/*
 * internal:  returns snapX/Y according to gridSettings attribtue.
 * 	used by setupGrid
 * */
Grid.prototype.transformSnapSettings2px=function (gridSettings){
	var ret={};
	ret.globalSnap=true;

	if(gridSettings==null)gridSettings={};

	if(!gridSettings.snapX && gridSettings.snapXmm)gridSettings.snapX=mm2px(gridSettings.snapX);
	if(!gridSettings.snapY && gridSettings.snapYmm)gridSettings.snapY=mm2px(gridSettings.snapY);

	switch(gridSettings.snap){
		case '': 				// get grid from object dimensions (width and height)
			ret.snapX=gridSettings.snapX;
			ret.snapY=gridSettings.snapY;

			ret.globalSnap=true;
		break;
		case 'x/2': 			// get grid from object width
			ret.snapX=Math.round(gridSettings.snapX/2);
			ret.snapY=ret.snapX;

			ret.globalSnap=false;
		break;
		case 'y/2': 			// get grid from object height
			ret.snapX=Math.round(gridSettings.snapY/2);
			ret.snapY=ret.snapX;

			ret.globalSnap=false;
		break;
		case 'x': 				// get grid from object width
			ret.snapX=gridSettings.snapX;
			ret.snapY=ret.snapX;

			ret.globalSnap=false;
		break;
		case 'y': 				// get grid from object height
			ret.snapY=gridSettings.snapY;
			ret.snapX=ret.snapY;

			ret.globalSnap=false;
		break;

		default:				// get grid from object height
			ret.globalSnap=true;
		break;
	}
	return ret;
}

/* clears the grid.
 */
Grid.prototype.clearGrid=function (){
	if(!this.attributes.gridVisible) return true; // already cleared

	this._heap['_gridX']=this.attributes.gridX;
	this._heap['_gridY']=this.attributes.gridY;

	this.attributes.gridVisible=false;

	this.page.setStyle({backgroundImage:''});
	this._heap['grid']['currentCode']='';

	return true;
}

/*
 * Schedule grid clearing.
 * 	called when an element unfocuses
 * */
Grid.prototype.unfocus=function (){
	this.eventScheduler.schedule('clearGrid',this.clearGrid.bind(this,true),0.25);
	return true;
}

/*
 * Setup the grid
 * 	called when an element unfocuses
 * */
Grid.prototype.focus=function (gridSettings){
	gridSettings=(gridSettings?gridSettings:this.attributes);
	this.setupGrid(gridSettings);

	if(this.attributes.snap==''){
		this.setupSnap(this.attributes.gridSettings);
	}

	return this.drawGrid();
}

/* setup the grid.
 */
Grid.prototype.setupGrid=function (gridSettings){
	if(!gridSettings)gridSettings={};

	//cancel the grid clearing event
	this.eventScheduler.cancel('clearGrid');

	//initialize temp vars
	var gridX=0,gridY=0;

	if(gridSettings.offsetX)this.attributes.offsetX=gridSettings.offsetX;
	if(gridSettings.offsetY)this.attributes.offsetY=gridSettings.offsetY;

	// "splits" will contain the gridType elements( Array(type, x, y) )
	var splits=new Array();

	// if we specified a grid, then overwrite the internal(old) one. Else keep using the old one.
	if(gridSettings.gridType){
		splits=String(gridSettings.gridType).split('|');

		this.attributes.gridType=gridSettings.gridType;
		this.attributes.grid=splits[0];
		this.attributes.gridXmm=splits[1];
		this.attributes.gridYmm=splits[2];

	}else{
		// the grid type was not specified... how come?
	}

	var a=this.transformGridSettings2px(this.attributes);
	this.attributes.global=a.global;

	// if we requested a "global" grid ( a static grid , as opposed to the dynamic grid that changes pattern according to the element sizes )
	if(this.attributes.global){
		// get snapX/Y from the splits array. this is the lowest level
		gridX=mm2px(this.attributes.gridXmm);
		gridY=mm2px(this.attributes.gridYmm);

		this.attributes.offsetX=0;
		this.attributes.offsetY=0;
		this.attributes.dynamicContent=false;
	}else{
		// get snapX/Y from the dynamicContent if specified
		// dynamicContent is expected to be a delta function returning data as an object with x.width and x.height elements specified in px
		this.attributes.dynamicContent=gridSettings.dynamicContent;

		if(this.attributes.dynamicContent){
			var dimensions=this.attributes.dynamicContent();
			gridX=dimensions.width;
			gridY=dimensions.height;
			var a=this.transformGridSettings2px({gridX:gridX,gridY:gridY,grid:this.attributes.grid});

			gridX=a.gridX;
			gridY=a.gridY;
		}else{
			gridX=0;
			gridY=0;
		}
	}

	this._heap['gridX']=gridX;
	this._heap['gridY']=gridY;

	this.attributes.gridX=gridX;
	this.attributes.gridY=gridY;

	if(!this.attributes.offsetX)this.attributes.offsetX=0;
	if(!this.attributes.offsetY)this.attributes.offsetY=0;

	return true;
}

/* setup the snapping points.
 */
Grid.prototype.setupSnap=function (gridSettings){
	if(!gridSettings)gridSettings={};

	//initialize temp vars
	var snapX=0,snapY=0;

	// "splits" will contain the gridType elements( Array(type, x, y) )
	var splits=new Array();

	// if we specified a grid, then overwrite the internal(old) one. Else keep using the old one.
	if(gridSettings.snapType){
		splits=String(gridSettings.snapType).split('|');

		this.attributes.snapType=gridSettings.snapType;
		this.attributes.snap=splits[0];
		this.attributes.snapXmm=splits[1];
		this.attributes.snapYmm=splits[2];
	}else{
		// the grid type was not specified... how come?
		this.attributes.snapX=this.attributes.gridX;
		this.attributes.snapY=this.attributes.gridY;

		this.attributes.snapXmm=px2mm(this.attributes.snapX);
		this.attributes.snapYmm=px2mm(this.attributes.snapY);
		return false;
	}

	var thisSnap=this.transformSnapSettings2px(this.attributes);
	this.attributes.globalSnap=thisSnap.globalSnap;

	// if we requested a "global" grid ( a static grid , as opposed to the dynamic grid that changes pattern according to the element sizes )
	if(this.attributes.globalSnap){
		// get snapX/Y from the splits array. this is the lowest level
		snapX=mm2px(this.attributes.snapXmm);
		snapY=mm2px(this.attributes.snapYmm);

	}else{
		// get snapX/Y from the already-processed grid-settings
		snapX=this.attributes.gridX;
		snapY=this.attributes.gridX;
	}

	this.attributes.snapX=snapX;
	this.attributes.snapY=snapY;

	return true;
}

/*
 * Redisplay the grid, called when page settings has changed.
 * */
Grid.prototype.onPageChanged=function (params){

	this._heap['pageW']=params.width;
	this._heap['pageH']=params.height;

	$(this.pageTemplate.sourceElement).setStyle({width:this._heap['pageW']+'px',height:this._heap['pageH']+'px'});

	// remove all generated pages.
	for(var i=0;i<this._heap['generatedPages'].length;i++){
		var page=this._heap['generatedPages'][i];

		if(page){
			try{
				page.remove();
			}catch(err){};
		}
	}
	this._heap['pages']=0;
	this._heap['generatedPages']=new Array();

	if(this.attributes.global) {
		return this.setupGrid({gridType:this.attributes.gridType_});
	}else{
		if(this.attributes.dynamicContent)
			return this.setupGrid({dynamicContent:this.attributes.dynamicContent});
	}
}

/*
 * This takes care of the actual drawing of the grid
 * */
Grid.prototype.drawGrid=function (force){
	if((this.attributes.gridX<=1 && this.attributes.gridY<=1) && this._heap['rescheduledDraws']<3){
		this._heap['rescheduledDraws']++;
		this.eventScheduler.schedule('drawGrid',this.drawGrid.bind(this,force),0.5);
		return false;
	}

	// in the _heap we store the actual grid data. the data in attributes may change without notice, but the ones in _heap are only updated on redraw
	// define a unique grid code, to be stored so another call to redraw the same gridd will do a return
	var backgroundPosition='-2px -2px';

	if(this.attributes.offsetX && this.attributes.offsetY)
		backgroundPosition=this.attributes.offsetX+'px '+this.attributes.offsetY+'px';

	var gridCode= this._heap['gridX'] + 'x' + this._heap['gridY'] +'@'+ this.attributes.gridType+'+'+backgroundPosition;

	// if we have different codes(different grids, or a forced redraw was requested,m then make the draw
	if((this._heap['grid']['currentCode']!=gridCode || force )){
		this.eventScheduler.cancel('drawGrid');
		this._heap['rescheduledDraws']=0;
		this.attributes.gridVisible=true;
		this.page.setStyle({
							backgroundImage:('url(makeGrid.php?w=' + this.attributes.gridX + '&h=' + this.attributes.gridY + '&gridType=' + this.attributes.grid + '&rnd=' + Math.random() + ')'),
							backgroundPosition:backgroundPosition
							});

		// notify all elements that a grid-change occured
		Controls.dispatchEvent('onSnapChanged',{snapX:this.attributes.snapX,snapY:this.attributes.snapY});

		this._heap['grid']['gridX']=this._heap['gridX'];
		this._heap['grid']['gridY']=this._heap['gridY'];
		this._heap['grid']['currentCode']=gridCode;
	}
}

/*
 * Callback used by the main Control class when dragging objects around
 * */
Grid.prototype.onDrag=function (x,y,constrainX,constrainY, element){
	x=Math.max(0,x);
	y=Math.max(0,y);

	var h = 0;
	if(element!=null){
		h = element.child.objectDimensions.height;
	}

	if(this.attributes.snap=='free')return {x:x,y:y};

	x=Math.round(x/this.attributes.snapX)*this.attributes.snapX;
	y=Math.round(y/this.attributes.snapY)*this.attributes.snapY;

	if(this.attributes.snapXmm>0){
		var xmm=px2mm(x);
		if(xmm%this.attributes.snapXmm!=0){
			xmm=Math.round(xmm/this.attributes.snapXmm)*this.attributes.snapXmm;
			x=mm2px(xmm);
		}
	}

	if(this.attributes.snapYmm>0){
		var ymm=px2mm(y);
		if(ymm%this.attributes.snapYmm!=0){
			ymm=Math.round(ymm/this.attributes.snapYmm)*this.attributes.snapYmm;
			y=mm2px(ymm);
		}
	}

	x=(x>0?x:0);
	y=(y>0?y:0);

    x=Math.round(constrain(x,0,constrainX));
    y=Math.round(constrain(y,0,h+constrainY*(grid._heap['pages']+1)));

    xmm=Math.round(px2mm(x));
    ymm=Math.round(px2mm(y));

    // add new pages to this document if needed
    if((y+h)>=(grid._heap['pageH']*grid._heap['pages'])){
		this.expandPageRange(x,y+(3*h/4));
    }

    return {Xmm:xmm, Ymm:ymm, x:x, y:y};
}

Grid.prototype.expandPageRange =function(x,y){
	// add new pages to this document.
    while(y>=(this._heap['pageH']*this._heap['pages'])){
		this._heap['pages']=Number(this._heap['pages'])+1;

    	var clone=this.pageTemplate.createClone($('documentContainer'),{'pageDescription':grid._heap['pages']});

    	this._heap['generatedPages'].push(clone);

    	$('documentContainer').setStyle({height:(this._heap['pages']*this._heap['pageH'])+'px'});
    	$('preDocumentContainer1').setStyle({height:(this._heap['pages']*this._heap['pageH'])+'px'});
    	$('preDocumentContainer2').setStyle({height:(this._heap['pages']*this._heap['pageH'])+'px'});

    	// this will cause a refresh of the ruler
    	toggleRuler($('page_ruler_').checked);
    }
}
