/**
 * Automa a stati finiti.
 * Gestisce un workflow asincrono.
 * Mantiene uno stato del workflow mediante una serie di proprietà.
 * Il workflow è composto da un insieme finito di passi (fasi).
 * Il passaggio da una fase alla successiva è comandato da un evento (goOn).
 * Un thread di controllo resta in attesa dell'evento e comanda il passaggio alle fasi successive.
 * Ogni fase viene implementata da una funzione di nome prefissato (automa<NomeDellAutoma>Phase<N>).
 */

/**
 * Avvia l'automa. Viene richiamato il primo passo.
 */
function Automa_start() {
	if(this.valid) {
		this.canContinue=false;
		this.goOnState=0;
		this.nextPhase();
	} else
		alert('automa '+this.name+' not valid');
}

/**
 * Invia all'automa l'evento che scatena l'esecuzione di un nuovo passo.
 */
function Automa_goOn() {
	if(this.valid) {
		this.canContinue=true;
	} else
		alert('automa '+this.name+' not valid');
}

/**
 * Ripete un passo del workflow (consente di costruire workflow che contengano fasi cicliche).
 */
function Automa_repeatPhase() {
	if(this.valid) {
		this.goOnState--;
		this.canContinue=true;
	} else
		alert('automa '+this.name+' not valid');
}

/**
 * Esegue la successiva fase del workflow.
 */
function Automa_nextPhase() {
	if(this.valid) {
		this.canContinue=false;
		this.goOnState++;
		
		if(this.goOnState<=this.phases) {	
			
			eval('automa'+this.name+'Phase'+this.goOnState+'()');
			setTimeout(this.objName+'.wait()',this.interval);
		} else {
			this.valid=false;
		}
	} else
		alert('automa '+this.name+' not valid');
}

/**
 * Termina il workflow.
 */
function Automa_stop() {
	this.valid=false;
}

/**
 * Thread di attesa dell'evento di prosecuzione (goOn).
 */
function Automa_wait() {
	if(this.valid) {
		if(this.canContinue) {
			this.nextPhase();
		} else
			setTimeout(this.objName+'.wait()',this.interval);
	}
}

/**
 * Termina il workflow.
 */
function Automa_end() {
	this.valid=false;
	if(this.endAction)
		eval(this.endAction);
	
}

/**
 * Fissa una proprietà dello stato dell'automa.
 */
function Automa_setProperty(name,value) {
	this.automaInfo.setProperty(name,value);
}

/**
 * Fissa l'intervallo di controllo del thread associato all'automa.
 */
function Automa_setInterval(interval) {
	this.interval=interval;
}

/**
 * Restituisce il valore di una proprietà dello stato dell'automa.
 */
function Automa_getProperty(name) {
	return this.automaInfo.getProperty(name);
}

/**
 * Ripete un passo del workflow (consente di costruire workflow che contengano fasi cicliche).
 */
function Automa_rewind() {
	if(this.valid) {
		this.goOnState--;
	} else
		alert('automa '+this.name+' not valid');
}

/**
 * Costruisce un nuovo automa caratterizzato da un nome (name) e da un numero di passi (phases)
 * objName è il nome della variabile che conterrà l'oggetto Automa (permette di avere la reflection).
 * endAction è un'azione da invocare quando l'automa è terminato.
 */
function Automa(name,phases,objName,endAction) {
	this.valid=true;
	this.name=name;
	this.phases=phases;
	this.objName=objName;
	this.endAction=endAction;
	this.canContinue=false;
	this.goOnState=-1;
	this.automaInfo=new InfoContainer();
	this.interval=10;
}

Automa.prototype.start=Automa_start;
Automa.prototype.goOn=Automa_goOn;
Automa.prototype.repeatPhase=Automa_repeatPhase;
Automa.prototype.nextPhase=Automa_nextPhase;
Automa.prototype.rewind=Automa_rewind;
Automa.prototype.wait=Automa_wait;
Automa.prototype.stop=Automa_stop;
Automa.prototype.end=Automa_end;
Automa.prototype.setProperty=Automa_setProperty;
Automa.prototype.getProperty=Automa_getProperty;

/**
 * Contenitore di proprietà "named".
 */
function InfoContainer_setProperty(name,value) {
	this.properties[name]=value;
}

function InfoContainer_getProperty(name) {
	return this.properties[name];
}

function InfoContainer() {
	this.properties=new Array();
}

InfoContainer.prototype.setProperty=InfoContainer_setProperty;
InfoContainer.prototype.getProperty=InfoContainer_getProperty;

/**
 * Trasformazione affine (lineare).
 */
function AffineTransform_transform(x,y) {
	var newX=(x+this.offsetX)*this.scaleX;
	var newY=(y+this.offsetY)*this.scaleY;
	return new Array(newX,newY);
}

function AffineTransform_inverseTransform(x,y) {
	var newX=(x-this.offsetX)/this.scaleX;
	var newY=(y-this.offsetY)/this.scaleY;
	return new Array(newX,newY);
}

function AffineTransform(offsetX,offsetY,scaleX,scaleY) {
	this.offsetX=offsetX;
	this.offsetY=offsetY;
	this.scaleX=scaleX;
	this.scaleY=scaleY;
}

AffineTransform.prototype.transform=AffineTransform_transform;
AffineTransform.prototype.inverseTransform=AffineTransform_inverseTransform;


/**
 * Oggetto WebEngine. Permette la comunicazione con il motore Web.
 * Le funzioni del motore possono essere invocate con il metodo asincrono sendMsg.
 * Il metodo receiveResponse è addetto alla ricezione delle risposte dal motore. Esso
 * redirige la risposta ad un corretto gestore.
 * La comunicazione standard avviene mediante l'uso di eventi DOM, all'atto della chiamata e della
 * risposta, e attraverso una <IFRAME>, il cui contenuto costituisce l'insieme dei parametri di
 * chiamata e la risposta alla chiamata.
 */


/*
 * Invia una richiesta al motore Web. Il metodo è asincrono. Ciò significa che
 * non viene attesa la risposta. Tale risposta verrà inviata alla funzione <responseHandler>,
 * oppure al metodo <responseHandler> di <responseHandlerObject>.
 * Il tipo di richiesta viene indicata dal parametro <msgAction>. Ogni tipo di richiesta 
 * può prevedere una serie di parametri aggiuntivi.
 * La richiesta può essere gestita in maniera standard o demandata ad un gestore esterno.
 * I gestori esterni possono essere associati a particolari msgAction.
 * Il motore gestisce una richiesta alla volta. Eventuali richieste ricevute durante la gestione
 * di altre richieste vengono accodate e gestite in sequenza.
 */
function WebEngine_sendMsg(msgAction,responseHandlerObject,responseHandler) {
	
	if(this.ready) {
		this.ready=false;

		this.handler=responseHandler;
		this.handlerObject=responseHandlerObject;

		var otherArgs=new Array();
		for(count=3;count<arguments.length;count++)
			otherArgs.push(arguments[count]);
		this.lastHandler=this;
		if(this.handlers[msgAction])
			this.lastHandler=this.handlers[msgAction];
		this.communication=this.lastHandler.handleMsg(msgAction,otherArgs);
		if(this.communication)
			setTimeout("receiveResponse()",500);
		else {
			this.handler=null;
			this.handlerObject=null;
	

			if(this.queue.length==0)
				this.ready=true;
			else
				this.processQueue();
		}
		
	} else {
		
		this.queue.push(arguments);
	}
		

}

/**
 * Gestione standard delle chiamate al motore web.
 */
function WebEngine_handleMsg(msgAction,otherArgs) {
	var url=mapInfo.getProperty('engineUrl');		
	var msg='action='+msgAction;
	for(count=0;count<otherArgs.length;count++)
		msg += '&arg'+(count+1)+'='+otherArgs[count];
	if(mapInfo.getProperty('applicationName'))
		msg += '&application='+mapInfo.getProperty('applicationName');
	communication.location.href=url+'?'+msg;
	return communication;
}

/**
 * Verifica la coda delle richieste e le gestisce opportunamente.
 */
function WebEngine_processQueue() {
		
	var actionToDo=this.queue.pop();
	
	var msgAction=actionToDo[0];
	var responseHandlerObject=actionToDo[1];
	var responseHandler=actionToDo[2];

	this.handler=responseHandler;
	this.handlerObject=responseHandlerObject;
	
	var otherArgs=new Array();
	for(count=3;count<actionToDo.length;count++)
		otherArgs.push(actionToDo[count]);
	this.lastHandler=this;
	if(this.handlers[msgAction])
		this.lastHandler=this.handlers[msgAction];
	this.communication=this.lastHandler.handleMsg(msgAction,otherArgs);

	setTimeout("receiveResponse()",500);
		
}

/**
 * Verifica se si è ricevuta la risposta ad una richiesta.
 */
function WebEngine_hasReceivedResponse() {
	return this.communication.document.readyState=='complete';
}

/**
 * Inizializza lo spazio di comunicazione con il motore Web.
 */
function WebEngine_clearCommunication() {
}

/**
 * Riceve una risposta dal motore Web.
 * Se il client ha indicato una funzione di gestione (<responseHandler>) questa viene
 * richiamata.
 */
function WebEngine_receiveResponse(response) {
	try
	{			
		var response=this.communication.document.body.innerHTML;
		
		var args=response;
		if(args.indexOf('*ESCAPE*')==0)
			args=escape(args.substring(8));
		else {
			args=args.replace(/\'/g,'\\\'');
			args=args.replace(/\`/g,'\\\`');
		}
		
		this.lastHandler.clearCommunication();
		if(this.handlerObject!=null && this.handlerObject !=undefined && this.handler!=null && this.handler!=undefined) {
			with (this.handlerObject) eval(this.handler+'(\''+args+'\')');
		} else if(this.handler!=null && this.handler!=undefined) {
			
			eval(this.handler+'(\''+args+'\')');
		}
		
	} catch(e) {
		args='*ERROR*';
		if(this.handlerObject!=null && this.handlerObject !=undefined && this.handler!=null && this.handler!=undefined) {
			with (this.handlerObject) eval(this.handler+'(\''+args+'\')');
		} else if(this.handler!=null && this.handler!=undefined) {
			
			eval(this.handler+'(\''+args+'\')');
		}
	}
	this.handler=null;
	this.handlerObject=null;
	

	if(this.queue.length==0)
		this.ready=true;
	else
		this.processQueue();
}

/**
 * Associa un gestore esterno ad una particolare msgAction.
 */
function WebEngine_registerHandler(msgAction,handler) {
	this.handlers[msgAction]=handler;
}

/**
 * Costruttore.
 */
function WebEngine() {
	this.ready=true;
	this.communication=null;

	this.handler=null;
	this.handlerObject=null;
	this.queue=new Array();
	this.handlers=new Array();
	this.lastHandler=null;
}

WebEngine.prototype.sendMsg=WebEngine_sendMsg;
WebEngine.prototype.receiveResponse=WebEngine_receiveResponse;
WebEngine.prototype.hasReceivedResponse=WebEngine_hasReceivedResponse;
WebEngine.prototype.processQueue=WebEngine_processQueue;
WebEngine.prototype.registerHandler=WebEngine_registerHandler;
WebEngine.prototype.handleMsg=WebEngine_handleMsg;
WebEngine.prototype.clearCommunication=WebEngine_clearCommunication;


/**
 * Oggetto Engine. Permette la comunicazione con il motore Java.
 * Le funzioni del motore possono essere invocate con il metodo asincrono sendMsg.
 * Il metodo receiveResponse è addetto alla ricezione delle risposte dal motore. Esso
 * redirige la risposta ad un corretto gestore.
 * La comunicazione avviene mediante l'uso di eventi DOM, all'atto della chiamata e della
 * risposta, e attraverso una <DIV>, il cui contenuto costituisce l'insieme dei parametri di
 * chiamata e la risposta alla chiamata.
 */

/**
 * Oggetto Range. Rappresenta un rettangolo spaziale.
 * Implementa vari metodi per la sua gestione. Un oggetto Range è rappresentato dalle 
 * coordinate di due punti agli estremi di una diagonale (punto in alto a sinistra e
 * in basso a destra.
 */

/**
 * Esegue uno zoom ad una scala prefissata.
 * La scala viene espressa rispetto ad un range di riferimento.
 */
function Range_zoomAbsolute(scale,originalRange) {
	var newDx=originalRange.getDeltaX()/scale;
	var newDy=originalRange.getDeltaY()/scale;
	
	var cX=this.minX/2+this.maxX/2;
	var cY=this.minY/2+this.maxY/2;
	this.minX=cX-newDx/2;
	this.maxX=cX+newDx/2;
	this.minY=cY-newDy/2;
	this.maxY=cY+newDy/2;
}

/**
 * Restituisce true se il range interseca un range dato.
 */
 function Range_intersects(range) {
	var result=this.maxX>range.minX && this.minX<range.maxX && this.maxY>range.minY && this.minY<range.maxY;
	return result;
	
}

function Range_toString() {
	return this.minX+","+this.maxX+","+this.minY+","+this.maxY;
}

/**
 * Restituisce una copia del range.
 */
function Range_copy() {
	return new Range(this.minX,this.maxX,this.minY,this.maxY);
}


/**
 * Esegue uno spostamento del range che porta il nuovo centro in xCenter,yCenter.
 */
function Range_pan(xCenter,yCenter) {
	var deltaX=this.getDeltaX();
	var deltaY=this.getDeltaY();
	var currxCenter=this.minX+deltaX/2;
	var curryCenter=this.minY+deltaY/2;
	var newX=currxCenter-xCenter;
	var newY=curryCenter-yCenter;
	this.minX=newX;
	this.minY=newY;
	this.maxX=newX+deltaX;
	this.maxY=newY+deltaY;
}

function Range_getDeltaX() {
	return this.maxX-this.minX;
}

function Range_getDeltaY() {
	return this.maxY-this.minY;
}

/**
 * Restituisce il rapporto di scala con un altro range.
 * Il rapporto viene calcolato lungo l'asse X.
 */
function Range_getScale(range) {
	return range.getDeltaX()/this.getDeltaX();
}

/**
 * Restituisce il rapporto di scala con un altro range.
 * Il rapporto viene calcolato lungo l'asse X.
 */
function Range_getScaleX(range) {
	return range.getDeltaX()/this.getDeltaX();
}

/**
 * Restituisce il rapporto di scala con un altro range.
 * Il rapporto viene calcolato lungo l'asse Y.
 */
function Range_getScaleY(range) {
	return range.getDeltaY()/this.getDeltaY();
}

/**
 * Trasforma un range di modo che il rapporto larghezza/altezza sia pari a
 * width/height. Il range può essere più "piccolo" di quello iniziale.
 */
function Range_adjustAspectRatio(width,height) {
	
	var currentWidth=this.getDeltaX();
	var currentHeight=this.getDeltaY();
	
	var difference=currentWidth/currentHeight-width/height;
	
	if(difference>0) {

		var offset=((width/height*currentHeight)-currentWidth);
		
		this.minX=this.minX-offset;
		//this.maxX=this.maxX+offset;
	} else if(difference<0) {
		
		var offset=((height/width*currentWidth)-currentHeight);
		
		this.minY=this.minY-offset;
		//this.maxY=this.maxY+offset;
	}
	
}

/**
 * Trasforma un range di modo che il rapporto larghezza/altezza sia pari a
 * width/height. Il range può essere più "grande" di quello iniziale.
 */
function Range_inverseAdjustAspectRatio(width,height) {
	var currentWidth=this.getDeltaX();
	var currentHeight=this.getDeltaY();
	
	var difference=currentWidth/currentHeight-width/height;
	
	if(difference<0) {
		var offset=((width/height*currentHeight)-currentWidth)/2;
		
		//this.minX=this.minX-offset;
		this.maxX=this.maxX+offset;
	} else if(difference>0) {
		var offset=((height/width*currentWidth)-currentHeight)/2;
		
		//this.minY=this.minY-offset;
		this.maxY=this.maxY+offset;
	}
	
}

/**
 * Effettua uno zoom (in o out) sul range. zoomFactor indica il livello di zoom.
 * 1= no zoom, >1 zoom in, <1 zoom out.
 */
function Range_zoom(zoomFactor) {
	var deltax;
	var deltay;
	if(zoomFactor!=0.0) {
	var k = (1 - 1/zoomFactor) / 2.0;
	deltax = this.getDeltaX() * k;
	deltay = this.getDeltaY() * k;

	this.minX += deltax ;
	this.minY += deltay ;
	this.maxX -= deltax ;
	this.maxY -= deltay ;
	}
}

/**
 * Adatta il range di modo che sia contenuto in un range massimo dato.
 */
function Range_normalizeToMaxRange(maxRange) {
		if(maxRange!=null) {
			var currentWidth=this.getDeltaX();
			var currentHeight=this.getDeltaY();
			var maxWidth=maxRange.getDeltaX();
			var maxHeight=maxRange.getDeltaY();
			if(currentWidth>maxWidth || currentHeight>maxHeight) {
				this.minX = maxRange.minX;
				this.minY = maxRange.minY;
				this.maxX = maxRange.maxX;
				this.maxY = maxRange.maxY;
			} else {
				if(this.maxX>maxRange.maxX) {
					this.maxX=maxRange.maxX;
					this.minX=maxRange.maxX-currentWidth;
				} else if(this.minX<maxRange.minX) {
					this.minX=maxRange.minX;
					this.maxX=maxRange.minX+currentWidth;
				}
			
				if(this.maxY>maxRange.maxY) {
					this.maxY=maxRange.maxY;
					this.minY=maxRange.maxY-currentHeight;
				} else if(this.minY<maxRange.minY) {
					this.minY=maxRange.minY;
					this.maxY=maxRange.minY+currentHeight;
				}
			}
			this.adjustAspectRatio(currentWidth,currentHeight);
		} 
	}

/**
 * Unisce un range con uno dato.
 */
function Range_union(range) {
	var deltaX=range.getDeltaX();
	var deltaY=range.getDeltaY();
	var ratio=deltaX/deltaY;
	var newMinX=this.minX<range.minX ? this.minX:range.minX;
	var newMaxX=this.maxX>range.maxX ? this.maxX:range.maxX;
	var newMinY=this.minY<range.minY ? this.minY:range.minY;
	var newMaxY=this.maxY>range.maxY ? this.maxY:range.maxY;
	return new Range(newMinX,newMaxX,newMinY,newMaxY);
	
}

/**
 * Rende il range quadrato.
 */
function Range_normalizeToExternalSquare() {
	var delta = (this.getDeltaX() - this.getDeltaY()) / 2.0;

	if (delta > 0) {
	  // We have to expand vertically
	  this.maxY += delta;
	  this.minY -= delta;
	} else if (delta < 0) {
	  // We have to expand orizontally
	  delta = - delta;
	  this.maxX += delta;
	  this.minX -= delta;
	}
}

/**
 * Confronta due range, verificando se rappresentano lo stesso rettangolo spaziale.
 */
function Range_equals(range) {
	return this.minX==range.minX && this.maxX==range.maxX && this.minY==range.minY && this.maxY==range.maxY;
}

/**
 * Costruttore. Prende le coordinate dei due punti.
 */
function Range(minX,maxX,minY,maxY) {
	this.minX=minX;
	this.maxX=maxX;
	this.minY=minY;
	this.maxY=maxY;
}

//Range.prototype.zoomBox=Range_zoomBox;
Range.prototype.zoom=Range_zoom;
Range.prototype.zoomAbsolute=Range_zoomAbsolute;
Range.prototype.getDeltaX=Range_getDeltaX;
Range.prototype.getDeltaY=Range_getDeltaY;
Range.prototype.getScale=Range_getScale;
Range.prototype.getScaleX=Range_getScaleX;
Range.prototype.getScaleY=Range_getScaleY;
Range.prototype.intersects=Range_intersects;
Range.prototype.adjustAspectRatio=Range_adjustAspectRatio;
Range.prototype.inverseAdjustAspectRatio=Range_inverseAdjustAspectRatio;
Range.prototype.normalizeToExternalSquare=Range_normalizeToExternalSquare;
Range.prototype.normalizeToMaxRange=Range_normalizeToMaxRange;
Range.prototype.copy=Range_copy;
Range.prototype.pan=Range_pan;
Range.prototype.equals=Range_equals;
Range.prototype.toString=Range_toString;
Range.prototype.union=Range_union;

/**
 * Oggetto Map. Rappresenta tutta la mappa.
 */

function Map_getScale() {
	return this.currRange.getScale(this.originalRange);
}

/**
 * Effettua uno zoom (in o out) sulla mappa. zoomFactor indica il livello di zoom.
 * 1= no zoom, >1 zoom in, <1 zoom out.
 */
function Map_zoom(zoomFactor) {
	//this.oldRange=this.currRange.copy();
	if(zoomFactor==0) {
		this.currRange=this.originalRange.copy();
		this.realRange=this.originalRealRange.copy();
	} else {
		this.currRange.zoom(zoomFactor);
		this.realRange.zoom(zoomFactor);
	}
}

/**
 * Effettua uno zoom ad una scala prefissata sulla mappa. La scala viene espressa come rapporto
 * rispetto al range massimo della mappa.
 */
function Map_zoomAbsolute(scale) {
	//this.oldRange=this.currRange.copy();
	if(scale==0) {
		this.currRange=this.originalRange.copy();
		this.realRange=this.originalRealRange.copy();
	} else {
		this.currRange.zoomAbsolute(scale,this.originalRange);
		this.currRange.normalizeToMaxRange(this.maxRange);
		this.realRange.zoomAbsolute(scale,this.originalRealRange);
		this.realRange.normalizeToMaxRange(this.maxRealRange);
	}
}


/**
 * Effettua uno zoom su un range indicato. Il range è indicato in coordinate reali.
 */
function Map_zoomBox(range) {
	
	//this.oldRange=this.currRange.copy();
	
	var xFactor=this.maxRealRange.getDeltaX()/this.maxRange.getDeltaX();
	var yFactor=this.maxRealRange.getDeltaY()/this.maxRange.getDeltaY();
	
	var maxRange=this.maxRealRange;

	var cxmin=(range.minX-maxRange.minX)/xFactor;
	var cxmax=(range.maxX-maxRange.minX)/xFactor;
	var cymax=this.maxRange.maxY-((range.minY-maxRange.minY)/yFactor);
	var cymin=this.maxRange.maxY-((range.maxY-maxRange.minY)/yFactor);

	this.currRange=new Range(cxmin,cxmax,cymin,cymax);
	
	this.realRange=range.copy();
	
}

/**
 * Termina un aggiornamento della mappa, rivisualizzando tutti i layer aggiornati.
 */
function Map_end() {
	for(var pos in this.layersToBeUpdated) {
		
		var layerName=this.layersToBeUpdated[pos];

		var layer=this.layers[layerName];
		layer.show();
		
	}
}

function Map_layersWithFunction(func,onlyVisible) {
	var result=new Array();
	for(var layerName in this.layers) {
		var layer=this.layers[layerName];
		if(onlyVisible) {
			
			if(layer.hasFunction(func) && layer.visible && layer.visibleInScale(map.getScale()))
				result.push(layer);
		} else {
			if(layer.hasFunction(func))
				result.push(layer);
		}
	}
	return result;
}

/**
 * Aggiorna lo stato di un layer, o di tutta la mappa (se layerName non viene passato).
 */
function Map_update(layerName) {
	
	this.layersToBeUpdated=new Array();
	this.layersUpdated=new Array();
	if(layerName==null || layerName==undefined) {
		for(var layerName in this.layers) {
			var layer=this.layers[layerName];
			
			if(layer.hasToBeUpdated(this.oldRange,this.currRange,this.originalRange,this.realRange)) {
				
				this.layersToBeUpdated.push(layerName);		
			}
		}
	} else {
		var layer=this.layers[layerName];
		if(layer.hasToBeUpdated(this.oldRange,this.currRange,this.originalRange,this.realRange))
			this.layersToBeUpdated.push(layerName);		
	}
	if(this.layersToBeUpdated.length==0)
		eval(this.endAction);
	for(var pos in this.layersToBeUpdated) {
		
		var layerName=this.layersToBeUpdated[pos];
		var layer=this.layers[layerName];
		
		/**
		 * Nasconde il layer prima dell'aggiornamento
		 * Verrà rivisualizzato invocando Map.end().
		 * In questo modo si cerca di minimizzare l'effetto visivo di aggiornamento desincronizzato dei vari layer.
		 */
		
		layer.hide();
		layer.update(this.currRange,this.originalRange,this.realRange,this,'layerUpdated',this.maxRange);
	}

}

/**
 * Invocata quando un layer ha terminato il proprio aggiornamento. Lo stato della mappa
 * viene aggiornato.
 */
function Map_layerUpdated(layerName) {
	for(count=0;count<this.layersToBeUpdated.length;count++) {

		if(this.layersToBeUpdated[count]==layerName) {
			
			this.layersUpdated.push(layerName);
		}
	}

	if(this.layersToBeUpdated.length==this.layersUpdated.length) {
		this.oldRange=this.currRange.copy();
		eval(this.endAction);
	}
}

/**
 * Esegue uno spostamento della mappa che porta il nuovo centro in xCenter,yCenter (coordinate video).
 */
function Map_pan(xCenter,yCenter) {
	
	//this.oldRange=this.currRange.copy();
	var x=xCenter*this.currRange.getDeltaX();
	var y=yCenter*this.currRange.getDeltaY();
	this.currRange.pan(x,y);
	var xR=xCenter*this.realRange.getDeltaX();
	var falseYR=yCenter*this.realRange.getDeltaY();
	var yR=(1-yCenter)*this.realRange.getDeltaY();
	
	this.realRange.pan(xR,yR);
	this.realRange.normalizeToMaxRange(this.maxRealRange);
	this.currRange.normalizeToMaxRange(this.maxRange);
}


/**
 * Aggiunge un Layer alla mappa.
 */
function Map_addLayer(layer) {
	if(layer.dynamic)
		this.addDynamic(layer);
	else
		this.layers[layer.name]=layer;
}

/**
 * Rimuove un Layer dalla mappa.
 */
function Map_removeLayer(layerName) {
	var layer=this.layers[layerName];
	layer.unload();
	delete this.layers[layer.name];
}

/**
 * Aggiunge un layer dinamico alla mappa.
 * I layer di mappa vengono distinti in layer statici (sempre presenti) e dinamici (aggiunti e tolti alla bisogna).
 */
function Map_addDynamic(layer) {
	this.layers[layer.name]=layer;
	var emptyDynamicLayer=mapInfo.getProperty('emptyDynamicLayer'+layer.dynamicPosition);
	var divAna=document.getElementById(mapInfo.getProperty('mapDynamicLayers')+layer.dynamicPosition+emptyDynamicLayer);
	emptyDynamicLayer++;
	mapInfo.setProperty('emptyDynamicLayer'+layer.dynamicPosition,emptyDynamicLayer);
	divAna.innerHTML+=layer.init()+'<div  id="'+mapInfo.getProperty('mapDynamicLayers')+layer.dynamicPosition+emptyDynamicLayer+'" style="position:absolute; z-index:2001;visibility:visible"></div>';

	layer.setBasePath(this.basePath);
	layer.setMapName(this.mapName);
	layer.setMapObjName(this.objName);
}

/**
 * Restituisce il layer che .
 */
function Map_getLayerByLoadedName(name) {
	for (var lName in this.layers) {
		var layer=this.layers[lName];
		if(layer.isItYou(name))
			return layer;
	}
	return null;
}

/**
 * Restituisce il layer di nome name.
 */
function Map_getLayer(name) {
	return this.layers[name];
}

/**
 * Rende visibile il layer "name". La reale visibilità del layer dipenderà poi
 * da diversi fattori, tipo la scala corrente.
 */
function Map_showLayer(name) {
	
	var layer=this.layers[name];
	
	if(layer!=null && layer!=undefined && !layer.visible) {
		layer.visible=true;
		/*if (layer.layers)
		{
			for(h in layer.layers)
				layer.layers[h].visible=true;
		}
		if (layer.layers != undefined)
		{
			for(var l1 in layer.layers)
				layer.layers[l1].visible = true;
		}*/
		this.update();
	} else {
		eval(this.endAction);
	}
}

/**
 * Rende invisibili una serie di layer. I layer vengono specificati da una lista di nomi separati da virgola.
 */
function Map_hideLayers(namelist) {
	var names = namelist.split(',');
	
	var flag = false;
	for(var i in names)
	{
		var name = names[i];
		var layer=this.layers[name];
		
		if(layer!=null && layer!=undefined && layer.visible) {
			layer.visible=false;
			
			layer.hide();	
		
		} 
	}
}
/**
 * Rende visibili i layer "names". La reale visibilità del layer dipenderà poi
 * da diversi fattori, tipo la scala corrente. I layer vengono specificati da una lista di nomi separati da virgola.
 * Attenzione: richiama Map.update().
 */
function Map_showLayers(namelist) {
	var names = namelist.split(',');
	var flag = false;
	for(var i in names)
	{
		var name = names[i];
		
		var layer=this.layers[name];
		
		if(layer!=null && layer!=undefined && !layer.visible) {
			layer.visible=true;
			/*if (layer.layers != undefined)	{
				for(var l1 in layer.layers)
					layer.layers[l1].visible = true;
			}*/
		
		} else {
			flag = true;
		}
	}
	if(flag) 
		eval(this.endAction);
	else
		this.update();
}

/**
 * Nasconde il layer "name".
 */
function Map_hideLayer(name) {
	var layer=this.layers[name];
	if(layer!=null && layer!=undefined && layer.visible) {
		layer.visible=false;
			/*if (layer.layers != undefined)
			{
				for(var l1 in layer.layers)
					layer.layers[l1].visible = false;
				
			}*/
		
	} 
	
}

/**
 * Nasconde tutti i layer.
 * Attenzione: richiama Map.update()
 */
function Map_hideAll() {
	var ly;
	for(ly in this.layers)
		this.hideLayer(this.layers[ly].name);
	this.update();
}

/**
 * Ripulisce tutta la mappa, compresa l'area di pagina addetta al suo contenimento.
 * Attenzione: richiama Map.update()
 */
function Map_clear() {
	this.hideAll();
	if(document.getElementById(mapInfo.getProperty('mapStaticLayers')))
		document.getElementById(mapInfo.getProperty('mapStaticLayers')).innerHTML='';
}

/**
 * Inizializza la mappa, creando una DIV per ogni layer statico.
 */
function Map_init(extra) {
	var divs='';
	if(extra)
		divs=extra;
		
	for(var layerName in this.layers) {
		var layer=this.layers[layerName];
		layer.setBasePath(this.basePath);

		layer.setMapName(this.mapName);
		layer.setMapObjName(this.objName);
		divs+=layer.init();
	}
	if(document.getElementById(mapInfo.getProperty('mapStaticLayers')))
		document.getElementById(mapInfo.getProperty('mapStaticLayers')).innerHTML=divs;

}

/**
 * Invocata quando un layer ha terminato il proprio caricamento. Lo stato della mappa
 * viene aggiornato e viene mandata segnalazione al layer (invocando il metodo setLoaded).
 */
function Map_layerLoaded(layerName,idList) {
	var result=layerName;
	
	var layer=this.getLayerByLoadedName(layerName);
	var altLayer=mapInfo.getProperty('alternativeLayer');
	
	if(altLayer) {
		var layerData=altLayer.split(',');
		var layerUsualName=layerData[0];
		var layerNewName=layerData[1];
		if(layer.name.toLowerCase()==layerUsualName.toLowerCase()) {
			layer=map.getLayer(layerNewName);
			result=layerNewName;
		}
	}
	
	layer.setLoaded(layerName,idList);
		
	
	
	return result;
}

/**
 * Invocato quando l'utente passa su di una forma interattiva. Viene invocato passando
 * il nome del layer interattivo,l'id della forma,l'evento generato.
 */
function Map_layerShapeOnMouseOver(layerN,id,tooltip,evt) {
	
	var layer=map.getLayerByLoadedName(layerN);
	if(layer && layer.getDocument(layerN)) {
		var shape=layer.getShape(id);
		layer.onMouseOver(shape,id,tooltip,evt);
	}
}

/**
 * Invocato quando l'utente esce da una forma interattiva. Viene invocato passando
 * il nome del layer interattivo,l'id della forma,l'evento generato.
 */
function Map_layerShapeOnMouseOut(layerN,id,evt) {
	var layer=map.getLayerByLoadedName(layerN);
	if(layer && layer.getDocument(layerN)) {
		var shape=layer.getShape(id);
		layer.onMouseOut(shape,id,evt);
	}
}

/**
 * Invocato quando l'utente clicca su una forma interattiva. Viene invocato passando
 * il nome del layer interattivo,l'id della forma,l'evento generato.
 */
function Map_layerShapeOnClick(layerN,id,evt) {
	var layer=map.getLayerByLoadedName(layerN);
	if(layer && layer.getDocument(layerN)) {
		var shape=layer.getShape(id);
		layer.onClick(shape,id,evt);
	}
}

/**
 * Invocato quando l'utente si muove da una forma interattiva. Viene invocato passando
 * il nome del layer interattivo,l'id della forma,l'evento generato.
 */
function Map_layerShapeOnMouseMove(layerN,id,evt) {
	var layer=map.getLayerByLoadedName(layerN);
	if(layer && layer.getDocument(layerN)) {
		var shape=layer.getShape(id);
		layer.onMouseMove(shape,id,evt);
	}
	
	
}

/**
 * Verifica se una mappa possiede la configurazione data.
 */
function Map_hasConfiguration(id) {
	for(var count=0;count<this.configurations.length;count++) {
		if(this.configurations[count]==id)
			return true;
	}

	return false;
}

/**
 * Reinizializza una mappa.
 */
function Map_reset() {
	this.currRange=this.originalRange.copy();
	this.oldRange=this.originalRange.copy();
	this.realRange=this.originalRealRange.copy();
	this.layers=new Array();

	this.zoomDownMaps=new Array();
	this.zoomUpMap=null;
	//this.ecwLayer=null;	
	this.layersToBeUpdated=new Array();
	this.layersUpdated=new Array();
	this.ready=true;

}

function Map_endOpening() {
	this.oldRange=this.currRange.copy();

	for(var layerName in this.layers) {
		var layer=this.layers[layerName];
		layer.endOpening();
	}
}

/**
 * Costruttore. Accetta due range, uno in coordinate video, uno in coordinate reali proiettive.
 * Oltre a questo accetta un'azione (istruzione javascript) da invocare al termine di un aggiornamento,
 * il percorso base per la cache, il nome dell'oggetto (utile per chiamate riflessive).
 */
function Map(originalRange,realRange,maxRealRange,endAction,basePath,objName,mapName,maxScale,legendName,meterWidth,type,zooms,defaultTopo,confArr) {
	this.basePath=basePath;
	this.objName=objName;
	this.mapName=mapName;
	this.maxScale=maxScale;
	this.legendName=legendName;
	this.meterWidth=meterWidth;
	this.type=type;
	this.zooms=zooms;
	this.defaultTopo=defaultTopo;
	this.configurations=confArr;
	this.originalRange=originalRange.copy();
	this.currRange=originalRange.copy();
	this.oldRange=originalRange.copy();
	this.originalRealRange=realRange.copy();
	this.realRange=realRange.copy();
	this.maxRealRange=maxRealRange.copy();
	this.maxRange=this.currRange.copy();
	this.maxRange.inverseAdjustAspectRatio(this.maxRealRange.getDeltaX(),this.maxRealRange.getDeltaY());
	
	//this.ecwToLoad=false;
	
	this.layers=new Array();

	this.zoomDownMaps=new Array();
	this.zoomUpMap=null;
	this.endAction=endAction;
	this.layersToBeUpdated=new Array();
	this.layersUpdated=new Array();
	this.ready=true;
	
}

/**
 * Duplica una mappa.
 */
function Map_clone() {
	var map = new Map(this.originalRange,this.realRange,this.maxRealRange,this.endAction,this.basePath,this.objName,this.mapName,this.maxScale,this.legendName,this.meterWidth,this.type,this.zooms,this.defaultTopo,this.configurations);
	map.layers=this.layers;
	
	return map;
}

/**
 * Gestione dei passaggi di mappa tramite zoom.
 */

/**
 * Verifica se una coppia mappa/configurazione ha delle mappe in cui è possibile zoomare.
 */
function Map_hasZoomDownConfiguration(mapname,conf) {

	for(var pos in this.zoomDownMaps) {
		
		var zoomDown=this.zoomDownMaps[pos];

		if(zoomDown.mapname==mapname && zoomDown.configurations) {
			var confArr=zoomDown.configurations.split('*');
			for(var count in confArr) {
				var curConf=parseInt(confArr[count]);
				if(curConf==conf)
					return true;
			}
		}
	}

	return false;
}

function Map_addZoomDownMap(mapname,xmin,xmax,ymin,ymax,scale,coordsType,configurations)
{
	this.zoomDownMaps.push(new ZoomMap(mapname,xmin,xmax,ymin,ymax,scale,coordsType,configurations));
}

function Map_addZoomUpMap(mapname,xmin,xmax,ymin,ymax,scale,coordsType)
{
	this.zoomUpMap = new ZoomMap(mapname,xmin,xmax,ymin,ymax,scale,coordsType);
}

Map.prototype.end=Map_end;
Map.prototype.zoomAbsolute=Map_zoomAbsolute;
Map.prototype.addZoomUpMap=Map_addZoomUpMap;
Map.prototype.addZoomDownMap=Map_addZoomDownMap;
Map.prototype.clone=Map_clone;
Map.prototype.zoomBox=Map_zoomBox;
Map.prototype.addLayer=Map_addLayer;
Map.prototype.getLayer=Map_getLayer;
Map.prototype.showLayer=Map_showLayer;
Map.prototype.showLayers=Map_showLayers;
Map.prototype.hideLayers=Map_hideLayers;
Map.prototype.hideLayer=Map_hideLayer;
Map.prototype.hideAll=Map_hideAll;
Map.prototype.init=Map_init;
Map.prototype.clear=Map_clear;
Map.prototype.reset=Map_reset;
Map.prototype.zoom=Map_zoom;
Map.prototype.update=Map_update;
Map.prototype.pan=Map_pan;
Map.prototype.layerUpdated=Map_layerUpdated;
Map.prototype.layerLoaded=Map_layerLoaded;
Map.prototype.layerShapeOnClick=Map_layerShapeOnClick;
Map.prototype.layerShapeOnMouseOver=Map_layerShapeOnMouseOver;
Map.prototype.layerShapeOnMouseOut=Map_layerShapeOnMouseOut;
Map.prototype.layerShapeOnMouseMove=Map_layerShapeOnMouseMove;
Map.prototype.hasConfiguration=Map_hasConfiguration;
Map.prototype.hasZoomDownConfiguration=Map_hasZoomDownConfiguration;
Map.prototype.addDynamic=Map_addDynamic;
Map.prototype.removeLayer=Map_removeLayer;
Map.prototype.getLayerByLoadedName=Map_getLayerByLoadedName;
Map.prototype.endOpening=Map_endOpening;
Map.prototype.getScale=Map_getScale;
Map.prototype.layersWithFunction=Map_layersWithFunction;

/**
 * Identifica i parametri di passaggio tra mappe.
 */
function ZoomMap(mapname,xmin,xmax,ymin,ymax,scale,coordsType,configurations)
{
	this.mapname = mapname;
	this.xmin = xmin;
	this.xmax = xmax;
	this.ymin = ymin;
	this.ymax = ymax;
	this.scale = scale;
	this.coordsType = coordsType;
	this.configurations=configurations;
}


