/*
 * generic static methods for creating map elements, converting between them and 
 * analyzing them, etc. 
 * 
 * currently this file is not in use.
 */
function MapFactory(){}
MapFactory.prototype={};
MapFactory.WIDTH=1;
MapFactory.COLOR="#000000";
MapFactory.FILLCOLOR="#FFFFFF";
MapFactory.OPACITY=0.7;
MapFactory.FILLOPACITY=0.5;
MapFactory.Images={};
MapFactory.Images['DEFAULT']=new Asset.image("http://maps.gstatic.com/intl/en_ca/mapfiles/ms/micons/yellow.png");
MapFactory.Images['default']=MapFactory.Images['DEFAULT'];
MapFactory.AbstractItem=function(item){
	
	if(!item){
		mm_debug(["Calling Abstract Item on undefined!",{'some debug stuff':(arguments&&arguments.callee&&arguments.callee.caller)?arguments.callee.caller:false}]);
		
	}
	return item.wrapper||item.sublayer||item;
};
MapFactory.ResolveItem=function(item){
	return item.item||item.marker||item.shape||item;
};

MapFactory.CreateMarker=function(data, callback){
	//default name description, note that description = null will trigger Content Module to query the server for data
	//this will allow kml files to be stripped down to nothing but coordinates names and styles and content
	//will be fetched when needed.
	var config=$merge({
		name:MapFactory.ItemDisplayType("marker"), 
		description:null,
		icon:"http://maps.gstatic.com/intl/en_ca/mapfiles/ms/micons/yellow.png" /*default to yellow icon*/
	},data);
	if(config.icon==null){
		config.icon="http://maps.gstatic.com/intl/en_ca/mapfiles/ms/micons/yellow.png";
	}
	/*draggable must be true to ever enable dragging*/
	var markerOptions = {
			draggable:true,
			title:config.name,
			position:config.coordinates,
			icon:config.icon
	}; 
	//create a generic marker the actual marker details will be adjusted dynamically.
	var kmlIcon = new google.maps.MarkerImage();
	kmlIcon.shadow="";
	//check static icon cache (defined above CreateMarker function) for icon, if null then load it
	if(MapFactory.Images[config.icon]==null){
		MapFactory.Images[config.icon]="loading";	//set to loading to block concurrent calls for the same marker
		var image=new Asset.image(config.icon,{onload:function(){
			MapFactory.Images[config.icon]=image;
		},onabort: function(){
			MapFactory.Images[config.icon]=MapFactory.Images['DEFAULT'];	
		},onerror: function(){
			MapFactory.Images[config.icon]=MapFactory.Images['DEFAULT'];	
		}});	
	}
	//function to run if/when icon is loaded
	var initMarker=function(){
		kmlIcon.image=config.icon;	
		//dynamically adjust size of to match image file
		var w=MapFactory.Images[config.icon].width;
		var h=MapFactory.Images[config.icon].height;
		//scale google.maps.MarkerImage so that it has a standard height and dynamic width
		//**stretched icons look horible
		var tWidth=32;
		var tHeight=32;
		if(config.iconOptions){
			if(config.iconOptions.width){
				tWidth=config.iconOptions.width;
				if(!config.iconOptions.height){tHeight=tWidth;}
			}
			if(config.iconOptions.height){
				tHeight=config.iconOptions.height;
				if(!config.iconOptions.width){tWidth=tHeight;}
			}
		}
		kmlIcon.iconSize=new google.maps.Size(((w/h)*tWidth),tHeight);
		//call to new google.maps.MarkerImage with updated (old) google.maps.MarkerImage->kmlIcon will use kmlIcons sizes
		//kmlIcon=new google.maps.MarkerImage(kmlIcon);
		//markerOptions.icon=kmlIcon; 
		markerOptions.icon=config.icon;
		marker = new google.maps.Marker(markerOptions); //actually create the marker
		marker.description=config.description;
		marker.style=config.icon;	/*attach to marker so that i can check for it when saving and editing*/
		marker.setDraggable(false); /*so markers aren't movable by anyone, call to enableDragging() restores functionality*/	
		callback(marker);
	};
	//initMarker is called imediatly if icon has been previously loaded, otherwise if checks every 100ms until it is loaded
	if(MapFactory.Images[config.icon]=="loading"){
		var wait=function(){
			if(MapFactory.Images[config.icon]!="loading"){
				initMarker();
				return true;
			}
			//mm_debug("Marker check - failed");
			return false;			
		};
		runLater(wait, 100); 
	}else{
		//must already be loaded.
		initMarker();

	}
};
MapFactory.SetInfoWindowClickEvents=function(infoWindow, viewer){
	
	google.maps.event.addListener(infoWindow, 'closeClick', function(){
		mm_debug(["InfoWindow Close Click Event", {infoWindow:infoWindow, infoWindowViewer:viewer}]);
		viewer.fireEvent('onClose');
		});
	
};
MapFactory.SetMarkerClickEvents=function(item){
	
	var mapItem=MapFactory.ResolveItem(item);
	var mediaMap=MapFactory.GetMM(mapItem);
	google.maps.event.addListener(mapItem, 'click', function(object){
		mm_debug(["Marker Click Event", mapItem]);
		mediaMap.eventManager.fireEvent('onMarkerClick', mapItem);}
	);
	//google.maps.event.addListener(mapItem, 'rightclick', function(object){mediaMap.eventManager.rightClickEventHandler(mapItem);});
};
MapFactory.SetMarkerDragEvents=function(item){
	var mapItem=MapFactory.ResolveItem(item);
	var mediaMap=MapFactory.GetMM(mapItem);
	google.maps.event.addListener(mapItem, 'dragend', function(l){
		MapFactory.SaveItem(item);
	});
	google.maps.event.addListener(mapItem, 'dragstart', function(l){
		mediaMap.menuOrderer.fadeOut();
		mediaMap.infoWindowViewer.close();
	});
};
MapFactory.CreateShape=function(coords,type,options){
	if(type=="polygon")
	{
		return MapFactory.CreatePolygon(coords, options);
	}
	else if(type=="route")
	{
		return MapFactory.CreateRoute(coords, options);
	}
	else if(type!="line")
	{
		mm_debug("MapFactory was unable to infer shape type! assuming line!");
	}
	return MapFactory.CreateLine(coords, options);

};
MapFactory.CreatePolygon=function(coords, options){
	var p={
		strokeColor:options.lineColor||MapFactory.COLOR,
		strokeOpacity:options.lineOpacity||MapFactory.OPACITY,
		strokeWidth:options.lineWidth||MapFactory.WIDTH,
		clickable:options.clickable||true,
		path:[coords||[]],
		fillColor:options.polyFill|MapFactory.FILLCOLOR,
		fillOpacity:options.polyOpacity||MapFactory.FILLOPACITY
		};


	//mm_debug(p);
	poly=new google.maps.Polygon(p);
	return poly;
};

MapFactory.CreateLine=function(coords, options){
	var p={
		strokeColor:options.lineColor||MapFactory.COLOR,
		strokeOpacity:options.lineOpacity||MapFactory.OPACITY,
		strokeWidth:options.lineWidth||MapFactory.WIDTH,
		clickable:options.clickable||true,
		path:coords||[]
	};
	mm_debug("Creating Line :: Factory");
	poly=new google.maps.Polyline(p);
	mm_debug(['Created New Line',{line:poly, options:p, coords:poly.getPath()}]);

	return poly;
};

MapFactory.CreateRoute=function(coords, options){

	//hmmmmmmm...???
	var g=new GDirections();

	var a=[];
	$each(coords,function(l){
		a.push(l.lat()+","+l.lng());
	});
	g.loadFromWaypoints(a,{getPloyline:true});

	mm_debug(g.getStatus());
	return g.getPolyline();
};



MapFactory.EnableMouseEditing=function(item){

	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem instanceof GeoliveMarker){
		mapItem.item.setDraggable(true);
	}
	else if(mapItem instanceof GeoliveShape){
		mapItem.item.enableEditing();
	}
	else{
		return false;
	}
	mapItem.mouseEditing=true;
	return true;
};
MapFactory.DisableMouseEditing=function(item){
	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem instanceof GeoliveMarker){
		mapItem.item.setDraggable(false);
	}
	else if(mapItem instanceof GeoliveShape){
		mapItem.item.disableEditing();
	}
	else{
		return false;
	}
	mapItem.mouseEditing=false;
	return true;
};
MapFactory.IsMouseEditing=function(item){
	//TODO: no longer compatable.
	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem.mouseEditing==true){
		return true;
	}
	return false;
};
MapFactory.ToggleMouseEditing=function(item){
	if(MapFactory.IsMouseEditing(item))
	{
		MapFactory.DisableMouseEditing(item);
		return false;
	}else{
		return MapFactory.EnableMouseEditing(item);
	}
};

/**
 * if the item has any link to the map this returns it.
 */


MapFactory.GetLayer=function(item)
{
	if(item==null){
		mm_debug("unable find Layer - item is null");
		return item;
	}
	if(item instanceof Layer){
		return item;
	}

	var layer=item.layer||(item.wrapper?item.wrapper.layer:null);
	if(layer==null){
		mm_debug("unable find Layer");
	}	
	return layer;
};


/**
 * Gets the root layer if layers have sublayers
 * @param item
 * @return
 */
MapFactory.GetRLayer=function(item)
{
	var layer=MapFactory.GetLayer(item);

	if(layer instanceof Layer&&layer.parentLayer instanceof LayerManager){
		return layer;
	}
	if(layer instanceof Layer){
		while(layer!=null&&!(layer.parent instanceof LayerManager)){
			layer=layer.parentLayer;
		}
	}
	mm_debug("unable find RootLayer");
	return layer;
};



MapFactory.GetLMan=function(item){
	if(item instanceof LayerManager){
		return item;
	}	
	if(item instanceof MediaMap){
		return item.layerManager;
	}
	var manager=MapFactory.GetRLayer(item);
	if(manager instanceof Layer&&manager.parentLayer instanceof LayerManager){
		return manager.parentLayer;
	}
	mm_debug("unable find LayerManager");
	return manager;
};
MapFactory.GetMM=function(item){	
	if(item instanceof MediaMap)
		return item;
	var mediaMap=MapFactory.GetLMan(item);
	if(mediaMap instanceof LayerManager&&mediaMap.mediaMap instanceof MediaMap){
		return mediaMap.mediaMap;
	}
	mm_trace();
	mm_debug(item);
	mm_debug("unable find MediaMap");
	return mediaMap;
};


MapFactory.GetGMap=function(item){

	if(item instanceof google.maps.Map){
		return item;
	}
	gMap=MapFactory.GetMM(item);
	if(gMap instanceof MediaMap){
		return gMap.mainMap;
	}
	mm_debug("unable find GMap");
	return gMap;
};

/**
 * calculates the number of zooms required (in or out) so that an overlay 
 * will fit inside the window ~centered and as bid as possible
 * 
 * @param xyA	
 * @param xyB
 * @return a number indicating the number of zooms required
 */
MapFactory.CalculateZoomFit=function(xyA,xyB)
{
	var max=8;
	var total=0;
	var w=xyA.lat();
	var x=xyA.lng();
	var y=xyB.lat();
	var z=xyB.lng();
	var mN=2;
	//mm_debug(xyA+" - "+xyB);								
	while(true)
	{
		if(max--){
			if((w*mN)<y&&(x*mN)<z)
			{
				w=w*mN;
				x=x*mN;
				total++;
			}		
			else if((w>y)||(x>z))
			{
				w=w/mN;
				x=x/mN;
				total--;
			}
			else break;
		}else break;	
	}
	return total;
};
MapFactory.PanTo=function(map, latlng, callback){

	var margin=0.05;//no need to pan if point is relatively within the center

	var c=map.getCenter();
	if(c==null)	//sometimes (rarely) c is null this is bad just quit if this occurs
	{
		mm_debug('can\'t pan becuase getCenter() returned null');
		return;
	}
	var b=map.getBounds();
	//map bounds in google.maps.LatLng
	var ne=b.getNorthEast();
	var sw=b.getSouthWest();

	var h=Math.abs(ne.lat()-sw.lat());
	var w=Math.abs(sw.lng()-ne.lng());


	n_sw=new google.maps.LatLng(c.lat()-h*margin, c.lng()-w*margin);
	n_ne=new google.maps.LatLng(c.lat()+h*margin, c.lng()+w*margin);
	n_b=new google.maps.LatLngBounds(n_sw,n_ne);

	mm_debug(n_b+" - contians -"+latlng+"?");

	if(!n_b.contains(latlng))	//no need if already there, or close enough
	{
		var handler=google.maps.event.addListener(map,"dragend",function(){
			google.maps.event.removeListener(handler);
			mm_debug("... and function");
			if(callback)
				callback();

		});
		map.panTo(latlng);
		google.maps.event.trigger(map, 'resize');
		mm_debug("pan ...");

	}else
	{
		mm_debug('no pan, just function');
		if(callback)
			callback();
	}
};
MapFactory.GetCenter=function(map){
	return map.getCenter();
};
MapFactory.PanToItem=function(map, item,callback){
	mapItem=MapFactory.ResolveItem(item);
	if(mapItem instanceof google.maps.Marker){
		MapFactory.PanTo(MapFactory.GetGMap(map), MapFactory.AbstractItem(mapItem).getLatLng(),callback);
	}else{

		mm_debug('panto item: failed (item is next line)');
		mm_debug(item);
	}

};
MapFactory.ZoomNumber=function(map, number, center){
	if(number>0)
	{
		for(var i=0;i<number;i++)
			map.zoomIn(center,true,true);
	}
	if(number<0)
	{
		for(var i=0;i>number;i--)
			map.zoomOut(center,true,true);
	}
};

//checks to see if user owns any item
MapFactory.ItemOwner=function(mediaMap, item, callback)
{
	var mapItem=MapFactory.AbstractItem(item);

	if(MapFactory.HasTag(mapItem, "noOwner")){
		callback(false);  //items can be set to have noOwner, not even admins can edit then.
	}else{

		switch(mediaMap.options.user){

		case "admin":callback(true);break;
		case "member"://mm_debug("Member Detected: May Require Authenication");
			if(mapItem.canEdit==true||(mapItem.change&&!(mapItem instanceof Layer)))
			{	
				mapItem.canEdit=true;	//for next time, and since saving removes item.change
				callback(true);	//any changed item indicates that user owns it (was able to make a change!)
			}
			else if(mapItem.canEdit==false)
				callback(false);
			else
			{
				var ownerQuery=MapFactory.GetItemOwnershipQuery(mapItem);
				if(!ownerQuery)
				{
					callback(false); break;	//layers should only be edited be admin for now.
				}
				ownerQuery.addEvent('onSuccess',function(result){
					if(result.permission=="granted"){
						callback(true);
						mapItem.canEdit=true;	//for later to avoid query
					}else {
						callback(false);
						mapItem.canEdit=false;
					}
				});
				ownerQuery.addEvent('onFailure',function(){callback(false);});
				ownerQuery.execute();
			}
			break;
		case "guest":
		default:callback(false);
		}
	}

};
MapFactory.GetItemOwnershipQuery=function(item)
{
	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem instanceof GeoliveMarker)
		return new MarkerCanEditRequest(mapItem);
	if(mapItem instanceof GeoliveLine){
		return new LineCanEditRequest(mapItem);
	}
	if(mapItem instanceof GeolivePolygon){			
		return new PolygonCanEditRequest(mapItem);
	}

	mm_debug("TODO: create permission queries for items ");
	return false;
};


MapFactory.ItemDisplayType=function(item)
{
	var mapItem=MapFactory.ResolveItem(item);

	if(mapItem instanceof google.maps.Marker||($type(item)=='string'&&item=="marker"))
	{
		if(mapItem.wrapper||$type(item)=='string')return "Placemark";
		else return "External Placemark";
	}
	if(mapItem instanceof google.maps.Polygon||($type(item)=='string'&&item=="polygon"))
	{
		if(mapItem.wrapper||$type(item)=='string')return "Polygon";

		else
			return "External Polygon";
	}
	if(mapItem instanceof google.maps.Polyline||($type(item)=='string'&&item=="line"))
	{
		if(mapItem.wrapper||$type(item)=='string') return "Line";
		else
			return "External Line";
	}	
	if(mapItem instanceof Layer)
	{
		if(mapItem.parentLayer instanceof Layer)
			return "Sublayer";
		else return "Layer";
	}
};
MapFactory.SetTag=function(item, tag){
	var mapItem=MapFactory.AbstractItem(item);
	if(!mapItem.tags)
		mapItem.tags={};
	$each(tag,function(v,k){
		mapItem.tags[k]=v;
	});
};
MapFactory.TagValue=function(item, tag){
	if(!item){
		mm_debug("Tried to grab tag from null object");
		mm_trace();
		
	}
	var tags=(item.tags||(item.wrapper?item.wrapper.tags:false));
	if(!tags){
		mm_debug("Tried to retrieve unavailable -"+tag+"- tag!");
		return null;
	}
	return tags[tag];
};
MapFactory.HasTag=function(item, tag)
{
	var dStr="Checking "+MapFactory.ItemDisplayType(item)+' Tags for "'+tag+'"';
	var mapItem=MapFactory.AbstractItem(item);
	var gItem=MapFactory.ResolveItem(item);
	var tags=gItem.tags||mapItem.tags||false;
	var found=false;
	if(!tags)
	{
		//mm_debug(dStr+" -> failed, no tags");
		return false;
	}
	$each(tags, function(v,k){

		if(k==tag)
		{
			found=true;
		}
	});
	//mm_debug(dStr+" -> "+(found?"success":"failed"));
	return found;
};

MapFactory.HasChanges=function(item)
{
	var mapItem=MapFactory.AbstractItem(item);	//container is where changes are stored
	if(mapItem.change)
		return true;
	return false;
};
MapFactory.IsNew=function(item)
{
	var mapItem=item.wrapper||item;	//container is where changes are stored
	if(mapItem.change&&mapItem.change=="new")
		return true;
	return false;
};

MapFactory.MergeChanges=function(item){
	var mapItem=MapFactory.AbstractItem(item);
	if(item instanceof GeoliveMarker||item instanceof GeoliveShape)
	{
		item.original=null;
		if(item.change){
			item.change=null;	
			item.layer.removeItemChange(item);
		}

	}else
		mm_debug("TODO: implement merge for items that are not markers");
	//TODO Implement merge methods for layers (admin only):
};
MapFactory.GetSaveRequest=function(item)
{
	var mapItem=MapFactory.AbstractItem(item);
	var saveItem;
	//mm_debug(mapItem);

	if(mapItem instanceof Layer){
//		var editedItems=[];
//		$each(mapItem.markers,function(m){
//			if(MapFactory.HasChanges(m))
//				editedItems.push(MapFactory.GetSaveRequest(m));
//		});
//		$each(mapItem.polygons,function(m){
//			if(MapFactory.HasChanges(m))
//				editedItems.push(MapFactory.GetSaveRequest(m));
//		});
		
			if(!mapItem.options.id||mapItem.options.id<1){
				return new LayerNewRequest(mapItem);
			}else{
			return new LayerSaveRequest(mapItem);
			}
		
		

	}else{
		if(MapFactory.HasTag(mapItem, 'ID'))
		{
			if(mapItem instanceof GeoliveMarker)
				saveItem=new MarkerSaveRequest(mapItem);
			else if(mapItem instanceof GeolivePolygon){
				saveItem=new PolygonSaveRequest(mapItem);
			}else if(mapItem instanceof GeoliveLine){
				saveItem=new LineSaveRequest(mapItem);
			}
			else mm_debug("Unable to save unknown map element!");
			//TODO Implement Get save-update request for layers :
		}
		else
		{
			if(mapItem instanceof GeoliveMarker)
				saveItem=new MarkerNewRequest(mapItem);
			else if(mapItem instanceof GeolivePolygon){
				saveItem=new PolygonNewRequest(mapItem);
			}else if(mapItem instanceof GeoliveLine){
				saveItem=new LineNewRequest(mapItem);
			}
			else mm_debug("Unable to create unknown map element!");
			//TODO implement save request for new layers:
		}
	}
	return saveItem;
};

MapFactory.GetDeleteRequest=function(item)
{
	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem instanceof GeoliveMarker)
		return new MarkerDeleteRequest(mapItem);
	else if(mapItem instanceof GeolivePolygon){
		return new PolygonDeleteRequest(mapItem);
	}
	else if(mapItem instanceof GeoliveLine){
		return new LineDeleteRequest(mapItem);
	}
	//TODO: Implement get delete request for layers
	mm_debug("TODO: create delete query for other items");
	return null;
};

MapFactory.SaveItem=function(item, config){
	var options=$merge({verifyMsg:true},config);
	var mapItem=MapFactory.AbstractItem(item);
	/*default ajax options*/
	var callbacks={
			onFailure:function(){},	
			onSuccess:function(){},
			onComplete:function(){}
	};
	$extend(callbacks,options); /*merges and overwrites callback with options*/
	/*
	 *	wrap callback method, onSuccess, with layer update calls. 
	 *	and clear evidence of changes since marker can no longer be reverted
	 */

	/*
	 * Not really neccessary to verify save unless overwritting
	 */
	if(options.verify)
		if(!confirm(options.verifyMsg||"are you sure you want to save this "+MapFactory.ItemDisplayType(mapItem)+"?"))
		{
			callbacks.onComplete();
			return;	//
		}
	/**
	 * wrap success and fail callbacks with our own.
	 * on successfuly saving an items 
	 */
	var suc=callbacks.onSuccess;
	callbacks.onSuccess=function(res){
		//mm_debug(res);
		//mapItem.layer.removeItemChange(item);
		if(res.success==true){
			MapFactory.MergeChanges(mapItem);	//also notifies layer
			if(options.notify)
				alert(options.notifySuccess||MapFactory.ItemDisplayType(mapItem)+" was successfully saved!");
			if(res.id){
				if(mapItem instanceof Layer){
					mapItem.options.id=res.id;					
				}else
				MapFactory.SetTag(mapItem,{ID:res.id});
			}
			MapFactory.DisableMouseEditing(mapItem);
		}
		else{
			if(options.notify)
				alert(options.notifyFail||"Unable to save "+MapFactory.ItemDisplayType(mapItem)+"!");
		}
		suc(res);
	};
	var fail=callbacks.onFailure;
	callbacks.onFailure=function(){
		if(options.notify)
			alert(options.notifyFail||"Unable to save "+MapFactory.ItemDisplayType(mapItem)+"!");
		fail();
	};

	//mm_debug(mapItem);
	var saveItem=MapFactory.GetSaveRequest(mapItem);

	saveItem.addEvent('onSuccess',callbacks.onSuccess);
	saveItem.addEvent('onFailure',callbacks.onFailure);
	saveItem.addEvent('onComplete',callbacks.onComplete);
	saveItem.execute();
};

MapFactory.DeleteItem=function(item, options){

	var mapItem=item.wrapper||item;
	var callbacks={
			onFailure:function(){},
			onSuccess:function(){},
			onComplete:function(){}
	};
	$extend(callbacks,options);

	if(options.verify)
		if(!confirm(options.verifyMsg||"are you sure you want to delete this "+MapFactory.ItemDisplayType(mapItem)+"?"))
		{
			callbacks.onComplete();
			return;	//
		}

	/*wrap onSuccess with layer updates and actual removal of marker from map*/
	var suc=callbacks.onSuccess;
	callbacks.onSuccess=function(res){

		if(res.success==true)
		{
			if(options.notify)
				alert(options.notifySuccess||MapFactory.ItemDisplayType(mapItem)+" was successfully deleted!");
			//alert("delete");

			MapFactory.MergeChanges(mapItem);	//also notifies layer
			MapFactory.RemoveFromMap(mapItem);

		}else
		{
			if(options.notify)
				alert(options.notifyFail||"Unable to delete "+MapFactory.ItemDisplayType(mapItem)+"!");
		}
		suc(res);

	};
	var fail=callbacks.onFailure;
	callbacks.onFailure=function(){
		if(options.notify)
			alert(options.notifyFail||"Unable to delete "+MapFactory.ItemDisplayType(mapItem)+"!");
		fail();
	};

	if(mapItem.change=="new")
	{
		//onSuccess automatically calls layer.MergeChanges
		callbacks.onSuccess({success:true}); //pretends that server responded since it isn't even at the server anyway
	}
	else
	{
		var deleteItem=MapFactory.GetDeleteRequest(mapItem);
		deleteItem.addEvent('onSuccess',callbacks.onSuccess);
		deleteItem.addEvent('onFailure',callbacks.onFailure);
		deleteItem.addEvent('onComplete',callbacks.onComplete);
		deleteItem.execute();
	}
};

MapFactory.EditItemDescription=function(item, description){
	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem instanceof GeoliveMapItem || mapItem instanceof Layer){
		var old=(mapItem.item||mapItem.options).description;
		(mapItem.item||mapItem.options).description=description;
		return old;
	}	
	throw "720 Invalid Item "+item;
};

MapFactory.EditItemName=function(item, name){
	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem instanceof GeoliveShape){
		var old=mapItem.getTitle();
		mapItem.name=name;
		return old;

	}else if( mapItem instanceof Layer){
		var old=mapItem.options.name;
		mapItem.options.name=name;
		return old;
	}else if (mapItem instanceof GeoliveMarker){
		var old=mapItem.getTitle();
		MapFactory.ResolveItem(mapItem).setTitle(name);
		return old;
	}

	throw "747 Invalid Item "+item;
};

/**
 * there is some documentation (not in google map api) about a setIcon() function
 * however since i could not find this in the api - cloning and replacing the element is
 * safer
 * @param item -google.maps.Marker or MarkerWrapper
 * @param icon -icon url.
 * @return
 */
MapFactory.EditItemIcon=function(item, icon, options){
	var mapItem=MapFactory.AbstractItem(item);
	if (mapItem instanceof GeoliveMarker){
		//mm_debug("about to clone");
		var old=MapFactory.GetItemIcon(mapItem);//carefull maybe should call item.getIcon();
		//MapFactory.CloneReplaceItem(mapItem, {'icon':icon, iconOptions:options||{}});
		MapFactory.ResolveItem(mapItem).setIcon(icon)
		return old;
	}
	throw "765 Invalid Item --expected Marker--"+item;
};
MapFactory.EditItemCoordinates=function(item, coordinates){
	var mapItem=MapFactory.AbstractItem(item);
	if (mapItem instanceof GeoliveMarker){
		var old=[mapItem.item.getLatLng()];
		mapItem.item.setLatLng((coordinates instanceof google.maps.LatLng)?coordinates:coordinates[0]);
		return old;
	}
	else if (item instanceof GeoliveShape){
		MapFactory.CloneReplaceItem(mapItem, {'coordinates':coordinates});
	}
	throw "779 Invalid Item --expected Marker or Shape--"+item;
};
MapFactory.EditItemStyle=function(item, style){
	var mapItem=MapFactory.AbstractItem(item);
	if (mapItem instanceof Layer){
		var old=[mapItem.options.style]||"";
		mapItem.options.style=style;
		return old;
	}

	throw "785 Invalid Item --expected layer--"+item;
};


/**
 * updates a field value for a map item, (may cause underlying item to be replaced)
 * 
 * @param item - marker line polygon layer
 * @param parameter name of field to update
 * @param value new value
 * @param options {showChanges:true}
 * @return true is change was applied
 */
MapFactory.EditItemParameter=function(item, parameter, value, options){
	var mapItem=MapFactory.AbstractItem(item);
	var config=$merge({showChanges:true},options);
	var changes=0;
	var old=null;
	switch(parameter){
	case 'name': old=MapFactory.EditItemName(mapItem, value); changes++; break;
	case 'description': old=MapFactory.EditItemDescription(mapItem, value); changes++; break;
	case 'icon': old=MapFactory.EditItemIcon(mapItem, value); changes++; break;
	case 'coordinates': old=MapFactory.EditItemCoordinates(mapItem, value); changes++; break;
	case 'style': old=MapFactory.EditItemStyle(mapItem, value); changes++; break;
	default: break;
	}
	if(config.showChanges&&(old!==value)){
		if(changes>0){
			if(mapItem instanceof GeoliveMapItem){
				if(!mapItem.change||mapItem.change==""){
					mapItem.change="edit";
					mapItem.layer.addItemChange(mapItem);
				}
			}
		}
	}
	return changes?true:false;
};

/**
 * creates a clone of an item replacing the original with the new clone
 * using any overriding parameters given by config (optional) - 
 * this may be an asynchronous call,
 * @param item - marker polygon line
 * @param config - overriding parameters
 * @return original marker 
 */
MapFactory.CloneReplaceItem=function(item, config){
	//mm_debug('clone replace');
	var mapItem=MapFactory.AbstractItem(item);
	if (mapItem instanceof GeoliveMapItem){
		var old=mapItem.item;
		MapFactory.CloneItem(mapItem, config,function(newItem)
				{
			newItem.wrapper=mapItem;

			if(mapItem instanceof GeoliveMarker){
				MapFactory.GetMM(mapItem).markerManager.removeMarker(old);
				mapItem.item=newItem;
				MapFactory.GetMM(mapItem).markerManager.addMarker(newItem,0);
			}else{
				MapFactory.GetGMap(mapItem).removeOverlay(old);
				mapItem.item=newItem;
				MapFactory.GetGMap(mapItem).addOverlay(newItem);
			}

			if(MapFactory.IsMouseEditing(mapItem)){
				MapFactory.EnableMouseEditing(mapItem);
			}
				});
		return old;
	}
	throw "848 Invalid Item "+item;
};

/**
 * Creates a duplicate item with any config parameters overriding the original
 * @param item	- marker polygon line (only marker for now)
 * @param config - overriding parameters
 * @param callback - function to call after successful clone operation - asynchronous
 * 	due to icon sizing algorithm for markers (otherwise synchronous)
 */
MapFactory.CloneItem=function(item, config, callback){
	var mapItem=MapFactory.AbstractItem(item);
	if (mapItem instanceof GeoliveMarker){
		MapFactory.CreateMarker($merge(MapFactory.GetItemParameters(mapItem),(config||{})),callback);
	}else if(mapItem instanceof GeoliveShape){
		var params=$merge(MapFactory.GetItemParameters(mapItem),(config||{}));
		if(mapItem instanceof GeolivePolygon)
			callback(MapFactory.CreatePolygon(params));
		else
			callback(MapFactory.CreateLine(params));
	}
	else
		throw "870 Invalid Item "+item;
};

MapFactory.GetItemParameters=function(item){
	var mapItem=MapFactory.AbstractItem(item);
	if(mapItem instanceof GeoliveMarker){
		return {coordinates:mapItem.getLatLng(),
			name:mapItem.getTitle(),
			icon:mapItem.getIcon(),
			description:mapItem.getDescription()
		};
	}
	//TODO implement GeoliveShape
	throw "883 Invalid Item (only markers implemented) "+item;

};

MapFactory.GetItemName=function(item){
	mapItem=MapFactory.AbstractItem(item);
	gItem=MapFactory.ResolveItem(item);
	return mapItem.name||mapItem.title||((mapItem.options&&(mapItem.options.name||mapItem.options.title))?(mapItem.options.name||mapItem.options.title):null)||(gItem.getTitle?gItem.getTitle():null)||gItem.name||gItem.title||null;
};
MapFactory.GetItemDescription=function(item){
	mapItem=MapFactory.AbstractItem(item);
	gItem=MapFactory.ResolveItem(item);
	return gItem.description||mapItem.description||((mapItem.options&&mapItem.options.description)? mapItem.options.description:null)||null;
};

MapFactory.GetItemCoordinates=function(item){
	gItem=MapFactory.ResolveItem(item);
	if(gItem instanceof google.maps.Marker)return [gItem.getPosition()];


};
MapFactory.GetItemIcon=function(item){
	mapItem=MapFactory.AbstractItem(item);
	gItem=MapFactory.ResolveItem(item);
	if(gItem instanceof google.maps.Marker){
		return gItem.getIcon();
	}
	return "";

};

MapFactory.GetItemParameter=function(item, parameter){
	switch(parameter){
	case "name":
	case "title": return MapFactory.GetItemName(item);
	case "description":return MapFactory.GetItemDescription(item);
	case "coordinates":return MapFactory.GetItemCoordinates(item);
	default: return null;
	}
};

MapFactory.GetItemWindowLocation=function(item){
	mapItem=MapFactory.AbstractItem(item);
	gItem=MapFactory.ResolveItem(item);
	if(gItem instanceof google.maps.LatLng)return gItem;
	if (mapItem instanceof GeoliveMarker)
	{
		return mapItem.getLatLng();
		me.options.infoWindowOptions.pixelOffset=new google.maps.Size(0, - gItem.getIcon().iconSize.height);
	}
	else if(gItem instanceof google.maps.Polygon){
		return gItem.getBounds().getCenter();  
	}
	return null;

};
MapFactory.GetItemWindowOffset=function(item){
	//TODO: better aproximation of offset.
	gItem=MapFactory.ResolveItem(item);
	if(gItem instanceof google.maps.Marker){
		return new google.maps.Size(0, - 32);
	}
	return new google.maps.Size(0,0);
};

MapFactory.RemoveFromMap=function(item){
	var mapItem=MapFactory.AbstractItem(item);

	if(mapItem instanceof GeoliveMarker)
	{
		mapItem.hide();
		mapItem.layer.removeMarker(mapItem);
	}
	else if(mapItem instanceof GeoliveShape){
		MapFactory.GetGMap(mapItem).removeOverlay(mapItem.item);
		mapItem.layer.removePolygon(mapItem);
	}else
	{
		mm_debug("TODO: create delete for other items");
	}


	$each([MapFactory.GetMM(mapItem).infoWindowViewer /*, MapFactory.GetMM(mapItem).squeezeViewer*/],function(viewer){
		if(viewer&&viewer.item&&viewer.state=="open"){

			i=((viewer.item?viewer.item.wrapper:false)||viewer.item);
			if(i==mapItem){
				viewer.close();
			}else{
				//mm_debug(i);
				//mm_debug(mapItem);
			}
		}
		if(!viewer)
			mm_debug("couldn't find viewer MGF 734");
	});

};
MapFactory.SortMapItemsByTag=function(items, tags){
	$each(tags,function(t){});

};

/**
 * assumes time is given as sql DateTime string, 
 * ie: 2010-08-17 01:56:48, or  2010/08/17 01:56:48
 * offset is difference between sever time and client time in milliseconds
 */
MapFactory.CalculateRelativeTime=function(time, offset){
	var now=new Date();



	var server=new Date(time.replace(/-/g, "/"));
	var serverMilli=server.getTime()+(offset||0);
	var relative=now.getTime()-serverMilli;

	var sMins=(relative/1000.0/60.0);
	var sHours=(sMins/60.0);
	var sDays=(sHours/24.0);
	var sMonthsAprox=(sDays/30.0);
	var sYears=(sDays/256.25);

	var weeks=Math.round(sDays/7);
	var remWeeks=(Math.round(sDays)%7);


	//mm_debug(time+"{ years:"+sYears+", months:"+sMonthsAprox+", weeks:"+weeks+"-remainder:"+remWeeks+" days:"+sDays+" hours:"+sHours+"), minutes:"+sMins+" offset:"+offset+"}");

	//knock off the really old posts first.
	if(sYears>=2)return Math.round(sYears)+" years ago";
	if(sMonthsAprox>11&&sMonthsAprox<13)return "a year ago";
	if(sMonthsAprox>=13){
		var remMonths=Math.round(sMonths)-12;
		return "a year "+(remMonths>1?"and "+roundedMonths+" months":"and a month")+" ago";
	}
	if(sMonthsAprox>1.6)return Math.round(sMonthsAprox)+" months ago";
	if(sMonthsAprox>1.3)return "1 and a half months ago";
	if(sMonthsAprox>1.1)return "just over a month ago";
	if(sMonthsAprox>0.9)return " a month ago";
	if(sMonthsAprox>0.8)return "almost a month ago";

	if(weeks>1){
		if(remWeeks==1){
			remWeeks=0;
		}
		if(remWeeks==6){
			remWeeks=0;
			weeks++;
		}

	}
	if(weeks>0&&remWeeks==0)return (weeks==1?"1 week ago":weeks+" weeks ago");
	if(weeks>1)return (weeks==1?"1 week":weeks+" weeks")+" and "+(remWeeks==1?"1 day ago":remWeeks+" days ago");

	if(sDays>2)return Math.round(sDays)+" days ago";
	if(sHours>40)return "almost 2 days ago";
	if(sHours>32)return "1 and a half days ago";
	if(sHours>28)return "just over 1 day ago";
	if(sHours>20)return "almost a day ago";
	if(sHours<14&&sHours>10)return "half a day ago";
	if(sHours>2)return Math.round(sHours)+" hours ago";

	if(sMins>140)return "2 and a half hours ago";
	if(sMins>130)return "jsut over 2 hours ago";
	if(sMins>110)return "2 hours ago";
	if(sMins>100)return "almost 2 hours ago";
	if(sMins>80)return "1 and a half hours ago";
	if(sMins>70)return "jsut over 1 hour ago";
	if(sMins>55)return "1 hour ago";
	if(sMins<35&&sMins>25)return "half an hour ago";



	if(sMins<=2)return "just now";
	if(sMins<=5)return "a few minutes ago";

	return  Math.round(sMins)+" minutes ago";





};

MapFactory.CalculateMapCoordinateFromElement=function(map, element, offset){
	
	var i_dim=element.getCoordinates();
	var c_dim=map.element.getCoordinates();

	var d_x=i_dim.left-c_dim.left;
	var d_y=i_dim.top-c_dim.top;
		var offset=offset||{x:0,y:0};
		var x=d_x+offset.x;
		var y=d_y+offset.y;
		
		//much harder than V2
		var scale = Math.pow(2, map.mainMap.getZoom());
		var left=map.mainMap.getProjection().fromLatLngToPoint(map.mainMap.getBounds().getSouthWest()).x*scale;
		var top=map.mainMap.getProjection().fromLatLngToPoint(map.mainMap.getBounds().getNorthEast()).y*scale;
		
		var point=map.mainMap.getProjection().fromPointToLatLng(
						new google.maps.Point(
								((x+left)/parseFloat(scale)),
								((y+top)/parseFloat(scale))
				)
		);

		mm_debug(["Calc",x, y, scale, left, top, point]);
		
		return point;

};

