// an info window class, akin to what I did in dataview.2.js, but split out
// the data rendering bit can be overridden

// old specification
//function infoWindow(mapCanvas,rawdataContainer,edcuid,dateString,dataObject)

// an infoElement is some positionable element.  pass in the element
// as the first param (or the id, the argument will be run through
// $(e) )
//
// note that the element is not appended to any containing div.  The
// calling program is responsiblefor that
function MappedElement(element,mapCoord,mapObject,pixelOffset){ 
    if(_debug){_debug.log("creating mapped element at "+mapCoord);}
    
    var e=$(element);
    e.style.position="absolute";

    if(!pixelOffset){pixelOffset=new coord();}

    var self=this;
    
    // mapObject is a reference to the containing map object.  needed
    // to get reference points, scales, etc
    // keep as a closure, no real need to expose, eh

    // allow hooks to override access to mapObject API
    this.getZoomLevel=function(){
	return mapObject.getZoomLevel();
    };
    // zoom is not scale.  Use zoom level to get scale from current
    // map spec.  returns a coord type of object (scale.x, scale.y)
    this.getScale=function(){
	var z=self.getZoomLevel();
	var spec=mapObject.getCurrentMapType();
	var ppd=spec.getPixelsPerDegree(z);
	return new decimalCoord(ppd.x, ppd.y);
    };
    // convert a map coord into a bitmap coord
    // lat,lon version
    this.getBitmapCoordinate=function(lat,lon,zoom,c){
	if(!c){
	    c=new decimalCoord(0,0);
	}
	if(!zoom){
	    zoom=self.getZoomLevel();
	}
	var spec=mapObject.getCurrentMapType();
	//if(_debug){_debug.log("calling spec.getBitMapCoordinate("+lat+","+lon+","+zoom+","+c+");");}
	var bmc= spec.getBitmapCoordinate(lat,lon,zoom);
	var dc=mapObject.getDivCoordinate(bmc.x,bmc.y);
	c.x=dc.x;
	c.y=dc.y;
	//(_debug){_debug.log("returned with "+bmc+" converteed to "+dc+" converteed to "+c);}
	return c;
    };
    // convert a map coord into a bitmap coord
    // utm version
    this.getBitmapCoordinateUTM=function(UTMcoord,zoom,coord){
	// convert utm to lat lon, then call lat lon version using utm.js
	var llcoord=val_utm(UTMcoord.y, UTMcoord.x, UTMcoord.zone || 11);
	if(_debug){_debug.log("converting from UTM "+UTMcoord+" to lat lon "+llcoord);}

	return self.getBitmapCoordinate(llcoord.y,llcoord.x,zoom,coord);
    };
    // reposition this object's display div to a pixel coordinate
    this.reposition=function(c){
	var offset=pixelOffset;
	if(!c){return null;}
	// presume using decimalCoord, or coord
	// could check I guess
	c.moveBy(pixelOffset );
	if(_debug){_debug.log("in reposition with "+c);}
	e.style.left=c.toLeftStyle();
	e.style.top=c.toTopStyle();
	if(_debug){_debug.log("element repositioned to "+Element.getCoord(e)+" "+e.style.left+" "+e.style.top+" "+Element.getLeft(e)+" "+Element.getTop(e)+" "+e.style.top);}
    }

    // so using the above, a window can reposition itself on zoom
    this.refresh=function(){
	//(_debug){_debug.log("calling MappedElement.refresh");}
	var c;
	// check whether utm or lat lon
	if(mapCoord.UTM){
	    c=self.getBitmapCoordinateUTM(mapCoord);
	} else {
	    c=self.getBitmapCoordinate(mapCoord.y,mapCoord.x);
	}
	//(_debug){_debug.log("mapCoord is "+mapCoord+", pixelCoord is "+c);}
	self.reposition(c);
    };

    this.getMapTL=function(tl){
	if(!tl){
	    tl=new coord(0,0);
	}
	var spec=mapObject.getCurrentMapType();
	tl.x=mapObject.currentPanOffset.width*spec.tileSize;
	tl.y=mapObject.currentPanOffset.height*spec.tileSize;
	return tl;
    };
    this.getMapBR=function(tl,br){
	if(!tl){
	    tl=self.getMapTL();
	}
	var width=new boxSize(mapObject.viewSize.width,mapObject.viewSize.height);
	// left and top padding
	tl.moveBy(new coord(mapObject.tilePaddingOffset.width*-1,mapObject.tilePaddingOffset.height*-1));
	width.grow(mapObject.tilePaddingOffset);
	// bottom and right padding
	width.grow(mapObject.tilePaddingOffset);
	br=tl.bbox(width);
	return br;
    };
    this.DESTROY=function(){
	element=null;
	e=null;
	mapCoord=null;
	mapObject=null;
	return 1;
    };
    this.refresh();
    return this;
}

	    
// so by this logic, the info window is a MappedElement with a
// <div> tree as the element, and the marker or point is a
// MappedElement with an <a> tag tree as the element

// but a marker wants to pop open an info window, and the coordinate
// is the same, just shifted up the height of the info window plus any
// xy offset.  is that enough to force them both into a single class?
// I think not.  Well perhaps.  do each separately then see

// info window.  as before, extract the map coordinate from the passed
// data object.  can override the data parsing rules if the data
// object should change
function infoWindow(data,mapCanvas,mapObject,dataParser){ 
    var self=this;
    var appended=false;
    var wanted=false;
    this.isWanted=function(){return wanted;};
    // parse the data, generate a div, and get map space coordinates
    if(dataParser){
	self.makeObsBox=dataParser;
    }
    if(_debug){_debug.log("calling data parser with "+ data);}
    var params=this.makeObsBox(data);
    var div= document.createElement("div");
    this.getElement=function(){
	return div;
    }
    div.style.zIndex=2;
    div.style.position="absolute";
    Element.makePositioned(div);
    div.appendChild(params.ul);

    // create a close button
    var tD=noFunTakeDown(mapObject);
    tD.href="javascript:void(0)";
    Event.observe(tD, 
		  "click", 
 		  (function(event){
 		      if(_debug){_debug.log("in click for object tD="+tD.className);}
 		      fade();
 		      wanted=false;
 		  })
		  );
    var tdPos=new coord(0,0);
    tdPos.moveBy(new coord(params.box.width-12,2));
    tD.style.top=tdPos.toTopStyle();
    tD.style.left=tdPos.toLeftStyle();
    div.appendChild(tD);
    this.getCloseButton=function(){
	if(_debug){_debug.log("returning object tD="+tD.className);}
	return tD;
    };
    var contentBox=params.box;
    // collect position from data
    var mapCoord=new decimalCoord;
    if(params.UTM){
	// have to insist.  if this flag is set, have UTM data, else
	// have lat lon data
	mapCoord.UTM=true;
	mapCoord.x=params.lon;
	mapCoord.y=params.lat;
    }else{
	mapCoord.LatLon=true;
	mapCoord.x=params.lon;
	mapCoord.y=params.lat;
    }
    params.ul=null;// kill leaking reference to ul, since I will not use
                   // it again
    this.getParsedValue=function(idx){
	return params[idx];
    };

    var mapElement=new MappedElement(div,
				     mapCoord,
				     mapObject,
				     new boxSize(0,-1*(contentBox.height+20)));
    this.getMapCoord=function(){ return mapCoord; }
    this.getMapElement=function(){return mapElement;}
    
    function append(){
	if(_debug){_debug.log("in append");}
	Element.hide(div);
	mapCanvas.appendChild(div);	
        new Effect.Appear(div);
	appended=true;
    }
    function fade(){
	Effect.MyFade(div);
	appended=false;
    }
    function remove(){
	try{
	    Element.remove(div);
	}catch(Ex){}
	appended=false;
    }

    var displayable=false;
    // decide whether to display or not
    this.checkDisplayable=function(tl,br){
	if(_debug){_debug.log("in checkDisplayable");}
	// top left
	if(!tl){
	    tl=mapElement.getMapTL();
	}
	// bottom right
	if(!br){
	    br=mapElement.getMapBR(tl);
	}
	// just check that current position is inside tl, br
	// 
	var currpos=Element.getCoord(div);
	if(_debug){_debug.log("currpos"+currpos+",tl:"+tl+",br:"+br);}
	displayable = (currpos.y >= tl.y &&
		       currpos.y <  br.y &&
		       currpos.x >= tl.x && 
		       currpos.x <  br.x);
	if(displayable && wanted && !appended){
	    append();
	}
	if(!displayable && appended){
	    remove();
	}
	return displayable;
    };
    this.refresh=function(tl,br){
	if(appended){remove();}
	if(wanted){
	    if(_debug){_debug.log("in infoWindow.refresh, calling mapElement.refresh");}
	    // top left
	    if(tl==null){
		tl=mapElement.getMapTL();
	    }
	    // bottom right
	    if(br=null){
		br=mapElement.getMapBR(tl);
	    }
	    mapElement.refresh();
	    if(_debug){_debug.log("in infoWindow.refresh, calling checkDisplayable.  tl="+tl+" br="+br);}
	    self.checkDisplayable(tl,br);
	}
    };
    this.raise=function(){
	// just remove and append to get this element on top
	if(appended){remove();}
	if(wanted){
	    append();
	}
    };
    Event.observe(div,'click',self.raise);
    this.close=function(){
	if(_debug){_debug.log("in infoWindow.close");}
	if(appended){fade();remove();}
	wanted=false;
    };
    this.show=function(tl,br){
	if(_debug){_debug.log("in infoWindow.open");}
	if(appended){remove();}
	wanted=true;
	self.refresh(tl,br)
    };
    this.open=function(event){
	if(_debug){_debug.log("in infoWindow.open");}
	if(appended){remove();}
	wanted=true;
	self.refresh()
    };

    this.DESTROY=function(){
	self.close();
	data=null;
	mapCanvas=null;
	mapObject=null;
	dataParser=null;
	params=null;
	div=null;
	contentBox=null;
	mapCoord=null;
	mapElement.DESTROY();
	mapElement=null;
	tD=null;
	return 1;
    };
}


// makeObsBox is a function that creates a ul element containing which
// describes a single GPS observation.  It is tailored specifically to
// what is expected of a GPS point.  Override if you've got some other
// sort of data

infoWindow.prototype.nautToMPH= 1.1516;
infoWindow.prototype.makeObsBox=function(obs){
    // placeholder function.  
	if(_debug){_debug.log("entering infoWindow.makeObsBox with obs:"+obs);}
    var ul=document.createTextNode("Dummy function.  You need to define a function that will create a UL block with data, a box (class boxSize) representing the size of the response, a variable UTM which is true if the data is UTM, and false if the data is the usual WGS84 lat lon, a variable zone if the UTM zone is other than 11 (null or undefined otherwise or if UTM=false), a variable lat holding the parsed lat, and a variable lon holding the parsed lon. and return all that as a js object: return {ul:ul, box:box, UTM:true, zone:11, lat:lat, lon:lon}");
    var box=box=new boxSize(215,36);
    return {ul:ul, box:box,UTM:false, zone:11, lat:null, lon:null};//{, pos:pos};
};


// use inheritcance to provide makeObsBox for different kinds of data

var gpsDataParseAndHTML=function(obs){
    if(_debug){_debug.log("entering gpsDataParseAndHTML with obs:"+obs);}
    var ul = document.createElement("ul");
    var li;
    var str;
    var box=new boxSize(215,0); // default size, make longer if
    // extra elements, shorter if
    // not
    var moreText=new boxSize(0,10); // add 10 px per line
    // move through known data and put in order
    // forming relevant strings at each step
    
    // 'utc'
    str="time: "+obs.utc;
    li=document.createElement("li");
    li.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul.appendChild(li);
    // dwell time
    if(obs.dwell && obs.dwell.getTime()>1000){
	str="dwell time:" + printElapsedTime(obs.dwell);
	li=document.createElement("li");
	li.appendChild(document.createTextNode(str));
	box.grow(moreText);
	ul.appendChild(li);
    }
    // 'position'
    str="pos:"
    li=document.createElement("li");
    li.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul2 = document.createElement("ul");	
    //p:lat
    // note that asSVG puts a negative
    str="lat="+(-1*(Math.floor(obs.lat))) +" m";
    var lat=-1*(obs.lat);
    if(_debug){_debug.log("parsed lat as "+lat);}
    
    li2=document.createElement("li");
    li2.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul2.appendChild(li2);
    str="lon:"+Math.floor(obs.lon) +" m";
    var lon=obs.lon;
    if(_debug){_debug.log("parsed lon as "+lon);}
    li2=document.createElement("li");
    li2.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul2.appendChild(li2);
    str="datum: WGS 84/UTM zone 11N";
    li2=document.createElement("li");
    li2.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul2.appendChild(li2);
    li.appendChild(ul2);
    ul.appendChild(li);
    // altitude
    if(obs.alt){
	str="alt: "+obs.alt+"meters";
	li=document.createElement("li");
	li.appendChild(document.createTextNode(str));
	box.grow(moreText);
	ul.appendChild(li);
    }
    // 'speed'
    str="spd: "+ Math.floor(obs.spd * this.nautToMPH) +" mph ("+ Math.floor(obs.spd) +" knots)";
    var spd=Math.floor(obs.spd * this.nautToMPH);
    li=document.createElement("li");
    li.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul.appendChild(li);
    // 'precision'
    str="precision:";
    li=document.createElement("li");
    li.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul2 = document.createElement("ul");	
    //p:fix
    str="fix="+obs.fix;
    var fix=obs.fix;
    li2=document.createElement("li");
    li2.appendChild(document.createTextNode(str));
    box.grow(moreText);
    ul2.appendChild(li2);
    if(obs.pdop){
	str="pdop=";
	str+=obs.pdop;
	li2=document.createElement("li");
	li2.appendChild(document.createTextNode(str));
	box.grow(moreText);
	ul2.appendChild(li2);
    }
    if(obs.hdop){
	str="hdop=";
	str+=obs.hdop;
	li2=document.createElement("li");
	li2.appendChild(document.createTextNode(str));
	box.grow(moreText);
	ul2.appendChild(li2);
    }
    if(obs.vdop){
	str="vdop=";
	str+=obs.vdop;
	li2=document.createElement("li");
	li2.appendChild(document.createTextNode(str));
	box.grow(moreText);
	ul2.appendChild(li2);
    }
    li.appendChild(ul2);
    ul.appendChild(li);
    ul.className="popUp";
    box.setEm(12);
    ul.style.width=box.toWidthStyle();
    ul.style.height=box.toHeightStyle();
    //var pos=new coord(0,-1*box.height-20);
    ul.style.left=0;//pos.toLeftStyle();
    ul.style.top=0;//pos.toTopStyle();
    return {ul:ul, box:box, UTM:true, zone:11, 
	    lat:lat, 
	    lon:lon,
	    spd:spd,
	    fix:fix
	    };//{, pos:pos};
};
// just the parsing
var gpsDataParse=function(obs){
    if(_debug){_debug.log("entering gpsDataParse with obs:"+obs);}
    var lat=-1*(obs.lat);
    var lon=obs.lon;
    var point=new decimalCoord(lon,lat);
    point.UTM=true;
    point.zone=11;
    var spd=Math.floor(obs.spd * this.nautToMPH);
    var fix=obs.fix;
    return {UTM:true,
	    zone:11, 
	    lat:lat, 
	    lon:lon,
	    spd:spd,
	    fix:fix,
	    point:point
	    };//{, pos:pos};
};

function tracerGPSWindow(data,mapCanvas,mapObject) {
    var self = new infoWindow(data,mapCanvas,mapObject,gpsDataParseAndHTML);
    return self;
}

function openshut(data,mapElement,mapObject,infoWindowClass){
    if(_debug){_debug.log("creating new openshut object");}
    var ifw;
    var self=this;
    var _open=false;
    this.open=function(){
	if(infoWindowClass==null){
	    ifw=new tracerGPSWindow(data,mapElement,mapObject);
	}else{
	    if(_debug){_debug.log("using infoWindowClass to define ifw");}
	    ifw=new infoWindowClass(data,mapElement,mapObject);
	    if(_debug){_debug.log("done using infoWindowClass to define ifw");}
	}
	if(_debug){_debug.log("calling ifw.open");}
	ifw.open();
	if(_debug){_debug.log("done calling ifw.open");}
	_open=true;
    };
    if(_debug){_debug.log("done creating open function");}
    this.getElement=function(){
	if(ifw && ifw.getElement){
	    return ifw.getElement();
	}
	return null;
    };
    this.getCloseButton=function(){
	if(ifw && ifw.getCloseButton){
	    return ifw.getCloseButton();
	}
	return null;
    };
    this.refresh=function(tl,br){
	if(_open){
	    ifw.refresh(tl,br);
	}
    };
    this.shut=function(){
	if(_open){
	    ifw.close();
	    ifw.DESTROY();
	    ifw=null;
	    _open=false;
	}
    };
    this.status=function(){
	return _open;
    };
    if(_debug){_debug.log("done creating status function");}
    this.DESTROY=function(){
	data=null;
	self.shut();
	return 1;
    };

    if(_debug){_debug.log("done creating new openshut object");}
}


var baseIcon=null;
// infoMarker.  create after creating a tracerGPSwindow thingee, pass mapElement
function infoMarker(mapCoord,mapCanvas,mapObject,imgSrc,markerBox){ 
    if(_debug){_debug.log("creating info marker at "+mapCoord); }
    var self=this;
    if(baseIcon==null){
	baseIcon=new myIcon();
	baseIcon.initialize({mapObject:mapObject});
    }
    if(!markerBox){
	markerBox=new boxSize(7,8);
    }
    var imgData=baseIcon.newImage_Complete({image:imgSrc});
    var ank=document.createElement("div");
    markerBox.apply(ank);
    function applyImgData(){
	// imgData is a node that needs its children copied
	if(_debug){_debug.log("copying child nodes from imgData="+imgData+", first child="+imgData.firstChild);}
	var nodes=imgData.childNodes;
	for(var i=0;i<nodes.length;i++){
	    ank.appendChild(nodes[i].cloneNode(true));
	}
	if(_debug){_debug.log("done copying, first child is"+Sarissa.serialize(ank.firstChild));}
    }
    function removeImgData(){
	if(ank){
	    Sarissa.clearChildNodes(ank);
	}
    }
    this.changeImage=function(src){
	removeImgData();
	imgData=baseIcon.newImage_Complete({image:src});
	applyImgData();
	imgData=null;
    };
    this.getElement=function(){return ank;};

    ank.style.zIndex=2;
    // ank.href="javascript:void(0)";
    applyImgData();
    imgData=null;

    ank.className="infoMarker";
    // make the MappedElement object
    // map element allows proper positioning via its refresh method
    
    if(_debug){_debug.log("creating new mapped element in infoMarker");}
    var mapElement= new MappedElement(ank,
				      mapCoord,
				      mapObject,
				      new boxSize(-3,-3)// adjust for ball size
				      );


    if(_debug){_debug.log("done creating new mapped element in infoMarker");}

    this.getMapCoord=function(){ return mapCoord.copy(); }
    this.getMapElement=function(){return mapElement;}

    var appended=false;
    var wanted=true;
    this.isWanted=function(){return wanted;};
    function append(){
	if(_debug){_debug.log("in append");}
	Element.hide(ank);
	mapCanvas.appendChild(ank);	
        new Effect.Appear(ank);
	appended=true;
    }
    function remove(){
	try{
	    Element.remove(ank);
	}catch(Ex){}
	appended=false;
    }

    var displayable=false;
    // decide whether to display or not
    this.checkDisplayable=function(tl,br){
	if(_debug){_debug.log("in checkDisplayable");}
	// top left
	if(!tl){
	    tl=mapElement.getMapTL();
	}
	// bottom right
	if(!br){
	    br=mapElement.getMapBR(tl);
	}
	// just check that current position is inside tl, br
	// 
	var currpos=Element.getCoord(ank);
	if(_debug){_debug.log("currpos"+currpos+",tl:"+tl+",br:"+br);}
	displayable = (currpos.y >= tl.y &&
		       currpos.y <  br.y &&
		       currpos.x >= tl.x && 
		       currpos.x <  br.x);
	if(displayable && wanted && !appended){
	    append();
	}
	if(!displayable && appended){
	    remove();
	}
	return displayable;
    };
    this.refresh=function(tl,br){
	if(_debug){_debug.log("in infoMarker.refresh, calling mapElement.refresh");}
	if(appended){remove();}
	
	mapElement.refresh();
	if(_debug){_debug.log("in infoMarker.refresh, calling checkDisplayable");}
	self.checkDisplayable(tl,br);
    };
    this.close=function(){
	if(_debug){_debug.log("in infoMarker.close");}
	if(appended){remove();}
	//wanted=false;
    };
    this.show=function(tl,br){
	if(_debug){_debug.log("in infoMarker.show");}
	if(appended){remove();}
	//wanted=true;
	self.refresh(tl,br)
    };
    this.open=function(event){
	if(_debug){_debug.log("in infoMarker.open");}
	if(appended){remove();}
	//wanted=true;
	self.refresh()
    };

}

