//                              -*- Mode: Java -*- 
// utilities.js --- 
// Author          : James E. Marca
// Created On      : Fri May 27 15:13:33 2005
// Last Modified By: James E. Marca
// Last Modified On: Wed Sep  7 14:51:31 2005
// Update Count    : 391
// Status          : Unknown, Use with caution!
// 
// this contains utility classes (is that the right word?) for drawing
// maps in js.  based originally on google maps code, but modified
// (and broken) over time

// extra prototype functions for known JS classes:


// add a prototype for xmlEscaping a String object.  This was
// originally in google maps, but I've actually replaced that version
// with the Sarissa version here.  Sarissa doesn't seem to use
// prototypes that much, I wonder why.  Perhaps for error checking?  a
// sarissa object knows what it can do??
//

// pulling more out of get_a_map
// browser sniffing.  Incompletely cherry picked from Google's code,
// as I recall
function sniffer(type,version,OS){
    this.type=type;
    this.version=version;
    this.os=OS;
};
var m=new sniffer(0,0,null);
var userAgent=navigator.userAgent.toLowerCase();
if(userAgent.indexOf("msie")!=-1&&document.all){ //msie
    m.type=1; // okay
    if(userAgent.indexOf("msie 5")) m.version=5; 
} else if(userAgent.indexOf("safari")!=-1){ //safari
    m.type=3; // safari bad version
    if(userAgent.indexOf("safari/125")!=-1) m.version=1; //safari okay version
} else if(userAgent.indexOf("mozilla")!=-1){
    m.type=2; // yay mozilla
} 

if(userAgent.indexOf("x11;")!=-1){ 
    // running on X11, need timeout fix for dragging, pan, zoom.
    // don't know why, but google does it, and so does map of
    // switzerland
    m.os=1;
}


// // let lots of timers happen per object.  From google maps
// var _timeoutCounter=0;
// Object.prototype.setTimeout=function(jsString,millisecs){
//     var counterString="tempVar"+_timeoutCounter;
//     _timeoutCounter++;
//     eval(counterString+" = this;");
//     var escapedJSString=jsString.replace(/\\/g,"\\\\").replace(/\"/g,'\\"');
//     return window.setTimeout(counterString+
// 			     '._setTimeoutDispatcher("'+
// 			     escapedJSString+
// 			     '");'+
// 			     counterString+
// 			     " = null;",
// 			     millisecs);
// };
// Object.prototype._setTimeoutDispatcher=function(escapedJSString){
//     eval(escapedJSString);
// };

Object.prototype.eventHandler=function(eventName){
    var h=this;
    h=h;
    //alert("h is " +h+" and event name is "+eventName);
    return function(b){
	if(!b) b=window.event;
	if(b&&!b.target) b.target=b.srcElement;
	//alert("h is " +h+" and event name is "+eventName);
	h[eventName](b);
    }
};

// sineWaveMovement
//
// silly thing from Google to make movement look smooth.  It
// accelerates up the sine hump, then slows down, instead of just
// jumping from a to b, and in stead of using uniform division of the
// distance
//
function sineWaveMovement(Yf){
    this.ticks=Yf;
    this.tick=0;
}
sineWaveMovement.prototype.reset=function(){
    this.tick=0;
};
sineWaveMovement.prototype.next=function(){
    this.tick++;
    var Ta=Math.PI*(this.tick/this.ticks-0.5);
    return(Math.sin(Ta)+1)/2;
};
sineWaveMovement.prototype.more=function(){
    return this.tick<this.ticks;
};


// captureEvent
//
// no idea where this comes from, or where it is used.  I know it came
// from Google, and i know I read about similar techniques in
// O'Reilly's Javascript book
//
function captureEvent(b){
    if(m.type==1){
	window.event.cancelBubble=true;
    }else{
	b.cancelBubble=true;
	b.preventDefault();
	b.stopPropagation();
    }
}

// perhaps not a good idea to overload the basic types too much

String.prototype.xmlEscape=function(){
    return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;"); // and an extra ") // for emacs fontlock and java-mode
};
// unescape from Sarissa
String.prototype.unescape = function(){
    return this.replace(/&apos;/g,"'").replace(/&quot;/g,"\"").replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&amp;/g,"&");
};
String.prototype.jsEscape=function(){
    return this.replace(/\\/g,"\\\\").replace(/\'/g,'\\"').xmlEscape();
};



// The classes (functions) included here are:
//
// debug: for dumping debugging to a div
//
// coord: a coordinate class
//
// compareSixDecimals: a real function
//
// mapCoord: an extension of coordinate class, with pixel and utm coords
//
// boxSize: a slight flavor modification to coordinate, that gives width, height
//

// debug
//
// debug is my own invention.  if the variable _debug is defined (and
// if it is a type _debug=new debug() ) then you can do the idiom
// if(_debug)_debug.log("text") and that will get written in the debug
// div with id="debug"
function debug(e){
    if(e){
	this.target=$(e);
    }else{
	this.target=$("debug");
    }
    if(!this.target){
	this.target = document.createElement("div");
	this.target.style.id="debug";
	this.target.style.fontSize="9px";
	document.body.appendChild(this.target);
    }
}
debug.prototype.log=function(txt){
    var txtNode = document.createTextNode(txt);      
    var myP = document.createElement("p");
    myP.appendChild(txtNode);
    this.target.appendChild(myP);
};
var _debug;

function toStyle(n){
    // so sick of +"px"
    return n+"px";
}

function FormValue(key,value){
    return "&value("+key+")="+value;
}

function FormKeyValue(key,value){
    return "&"+key+"="+value;
}

// yeah, there is probably a sprintf function.  so sue me
function twoDigits(n){
    if(n<10){return "0"+n};
    return n;
}

//
// coord and mapcoord.  
//
// Google had a smaller version of coord, as I recall.  I've added a
// few more features, and I actually use it as much as I can, instead
// of passing x,y.  Makes writing to styles easier, as well as scaling
// and translating.
//
// Mapcoord, which follows, extends coord by inclusion, joining a UTM
// coordinate, a pixel coordinate, and a scale, and the ability to
// register the mapcoordinate to a reference point at pixel coord 0,0
// to properly position the mapcoord on the pixel canvas.

// coord
function coord(x,y){
    this.x=Math.round(x);
    this.y=Math.round(y);
}
coord.prototype.toString=function(){
    return"("+this.x+", "+this.y+")";
};
coord.prototype.apply=function(a){
    a.style.position="absolute";
    a.style.left=this.toLeftStyle();
    a.style.top=this.toTopStyle();
};
coord.prototype.toLeftStyle=function(){
    return toStyle(this.x);
};
coord.prototype.toTopStyle=function(){
    return toStyle(this.y);
};
coord.prototype.equals=function(otherC){
    if(!otherC)return false;
    return this.x==otherC.x && this.y==otherC.y;
};
coord.prototype.distanceFrom=function(otherC){
    if(!otherC){otherC=new coord(0,0);}
    var dx=this.x-otherC.x;
    var dy=this.y-otherC.y;
    return Math.sqrt(dx*dx+dy*dy);
};
coord.prototype.approxEquals=function(otherC){
    if(!otherC){return false;}
    return compareSixDecimals(this.x,otherC.x) && compareSixDecimals(this.y,otherC.y);
};
coord.prototype.approxEqualsXY=function(x,y){
    if(!x || !y){return false;}
    return compareSixDecimals(this.x,x) && compareSixDecimals(this.y,y);
};
coord.prototype.leftOf=function(otherC){
    try{
	if( typeof otherC == "object") {
	    return (this.x<otherC.x);
	} else {
	    return (this.x<otherC);
	}
    } catch (e){}
    return false;
};
coord.prototype.rightOf=function(otherC){
    try{
	if( typeof otherC == "object") {
	    return (this.x>otherC.x);
	} else {
	    return (this.x>otherC);
	}
    } catch (e){}
    return false;
};
coord.prototype.below=function(otherC){
    try{
	if( typeof otherC == "object") {
	    return (this.y<otherC.y);
	} else {
	    return (this.y<otherC);
	}
    } catch (e){}
    return false;
};
coord.prototype.above=function(otherC){
    try{
	if( typeof otherC == "object") {
	    return (this.y>otherC.y);
	} else {
	    return (this.y>otherC);
	}
    } catch (e){}
    return false;
};
coord.prototype.difference=function(otherC){
    var dx=Math.floor(this.x-otherC.x);
    var dy=Math.floor(this.y-otherC.y);
    //alert("coord difference: " + dx + " " + dy);
    return new coord(dx,dy);
};
coord.prototype.differenceXY=function(x,y){
    var dx=this.x-x;
    var dy=this.y-y;
    return new coord(dx,dy);
};
coord.prototype.scale=function(sa,sb){
    if(!sb){sb=sa;}
    var sx=Math.floor(this.x * sa);
    var sy=Math.floor(this.y * sb);
    //alert("in scale "+this.x + " " + this.y +" "+sa);
    return new coord(sx,sy);
};
coord.prototype.bbox=function(box){
    var dx=this.x+box.width;
    var dy=this.y+box.height;
    return new coord(dx,dy);
};
coord.prototype.moveBy=function(c){
    if(c.width!=null){
	this.x=Math.floor(this.x+c.width);
	this.y=Math.floor(this.y+c.height);
    }else{
	this.x=Math.floor(this.x+c.x);
	this.y=Math.floor(this.y+c.y);
    }
};
coord.prototype.radial=function(){
    // borrowing from PDL::Transform.pm
    //(mod operator on atan2 puts everything in the interval [0,2*PI).)
    var rads = Math.atan2(-this.y,this.x) % (2*Math.PI) ;
    var mag  = Math.sqrt(this.x*this.x + this.y*this.y);
    var radial = new coord(rads,mag);
    radial.radial = 1;
    return radial;
};
coord.prototype.copy=function(){
    return new coord(this.x,this.y);
};
coord.prototype.toGPoint=function(){
    return new GPoint(this.x,this.y);
};
	
function decimalCoord(x,y){
    //inheritance of coord, dropping rounds and floors
    // var self=new coord(x,y);
    // defeat rounding;
    this.x=x;
    this.y=y;
    return this;
}
decimalCoord.inherits(coord);
decimalCoord.method('difference',
		    function(otherC){
			var dx=this.x-otherC.x;
			var dy=this.y-otherC.y;
			return new decimalCoord(dx,dy);
		    });
decimalCoord.method('differenceXY',
		    function(x,y){
			var dx=this.x-x;
			var dy=this.y-y;
			return new decimalCoord(dx,dy);
		    });
decimalCoord.method('scale',
		    function(sa,sb){
			if(!sb){sb=sa;}
			var sx=this.x * sa;
			var sy=this.y * sb;
			//if(_debug)_debug.log(sx + " " + sy + " " +this.x + " " + this.y);
			return new decimalCoord(sx,sy);
		    });
decimalCoord.method('bbox',
		    function(box){
			var dx=this.x+box.width;
			var dy=this.y+box.height;
			return new decimalCoord(dx,dy);
		    });
decimalCoord.method('moveBy',
		    function(c){
			if(c.width!=null){
			    this.x=this.x+c.width;
			    this.y=this.y+c.height;
			}else{
			    this.x=this.x+c.x;
			    this.y=this.y+c.y;
			}
		    });
decimalCoord.method('radial',
		    function(){
			// borrowing from PDL::Transform.pm
			//(mod operator on atan2 puts everything in the interval [0,2*PI).)
			var rads = Math.atan2(-this.y,this.x) % (2*Math.PI) ;
			var mag  = Math.sqrt(this.x*this.x + this.y*this.y);
			var radial = new decimalCoord(rads,mag);
			radial.radial = 1;
			return radial;
		    });
decimalCoord.method('cartesian',
		    function(){
    // again borrowing from PDL::Transform.pm
    var cartes=new decimalCoord(this.y*Math.cos(this.x),
				 -this.y*Math.sin(this.x));
    return cartes;
});

decimalCoord.method('copy',
		    function(){
			return new decimalCoord(this.x,this.y);
		    });

//rounding comparison, stolen as is from Google maps, although I have
//the same technique in my code all over, but I use floor and trunc
//rather than round *100000, but those are in perl, not js
function compareSixDecimals(l,r){
    return Math.round(l*1000000) == Math.round(r*1000000);
}

// mapCoord
//
// as mentioned above, mapCoord extends coord by inclusion.  I have a
// pixel coordinate, and a mapspace coordinate, plus a scale which
// allows translation between the two (provided you call the register
// function with a mapCoord object which has a known mapping from
// mapspace to pixel space (typically ppixel (0,0).
//
// note the stupid use of scale as a variable here, while scale is a
// function of coord.  Be careful not to ask for this.utmCoord.scale,
// when you mean this.scale, or vice versa
//
function mapCoord(utmx,utmy,scale){
    if(!utmx&&!utmy){
	utmx=0;
	utmy=0;
    }
    this.utmCoord=new decimalCoord(utmx,-1*utmy);
	//this.utmCoord=new decimalCoord(utmx,utmy);
    this.scale=scale;
    this.transform = 1/scale;
    return this.setSelf();
}

mapCoord.prototype.setSelf=function(){
    this.offset=this.getOffset(this.utmCoord);
    this.pixelCoord=this.translateAndScale();
    return this;
};

mapCoord.prototype.copy=function(){
    var newCopy =new mapCoord();
    newCopy.utmCoord=this.utmCoord.copy();
    newCopy.transform=this.transform;
    newCopy.scale=this.scale;
    newCopy.pixelCoord=this.pixelCoord.copy();
    newCopy.offset=this.offset.copy();
    return newCopy;
};    
mapCoord.prototype.toString=function(){
    return"mapspace:("+this.utmCoord.scale(1,-1)+"), pixelSpace:("+this.pixelCoord+")";
};
mapCoord.prototype.translateAndScale=function(){
    var doubletranslate = (this.utmCoord.difference(this.offset));
    var doubleCoord=doubletranslate.scale(this.transform);
    return new coord(doubleCoord.x,doubleCoord.y);
};
mapCoord.prototype.moveByPixels=function(pixelDiff){
    this.pixelCoord.moveBy(pixelDiff);
    var translate = pixelDiff.scale(this.scale);
    this.utmCoord.moveBy(translate);
};
mapCoord.prototype.getOffset=function(c){
    return new coord(c.x,c.y);
};
mapCoord.prototype.bbox=function(C){
    var offsetBox = new boxSize(C.utmCoord.x,C.utmCoord.y);
    var newCoord=this.utmCoord.bbox(offsetBox);
    return new mapCoord(newCoord.x,-1*newCoord.y,this.scale);
};
mapCoord.prototype.setScale=function(s){
    if(this.scale==s){return 0;}
    this.scale=s; //meters per pixel
    this.transform=1/s;
    this.pixelCoord=this.translateAndScale();
    return 1;
};

mapCoord.prototype.register=function(knownMapCoord){
    if(this.scale != knownMapCoord.scale){
	//throw ("scales are different");
	this.scale=knownMapCoord.scale;
    }
    //this.scale=knownMapCoord.scale;
    //this.transform=knownMapCoord.transform;
    //figure my x,y in pixel space given points corresp. to 0,0
    var knownPixelOffset=knownMapCoord.pixelCoord.difference(knownMapCoord.utmCoord.scale(this.transform));
    this.pixelCoord=this.utmCoord.scale(this.transform);
    var doublePixelCoord=this.utmCoord.scale(this.transform);

    doublePixelCoord.moveBy(knownPixelOffset);
    this.pixelCoord=new coord(doublePixelCoord.x,doublePixelCoord.y);
    if(_debug)_debug.log("in register, knownPixelOffset "+knownPixelOffset+" doublePixelCoord: "+doublePixelCoord+" this: "+this+" from "+knownMapCoord);
};
mapCoord.prototype.getMapCoordAtPixelOrigin=function(origin){
    if(!origin){
	origin=new coord(0,0);
    }
    var pixelDiff=origin.difference(this.pixelCoord);
    var myUTMOffset=pixelDiff.scale(-1*this.scale);
    var newUTM=this.utmCoord.difference(myUTMOffset);
    return newUTM;
};
// mapCoord.prototype.scaleOffset=function(){
//     this.pixelCoord = this.offset.scale(this.transform);
// };

//boxSize (width,height)
//
// there was a class in Google maps, and I don't really see the need
// for it, as it is really no different from a pixel coord which has
// been registered to 0,0, or a pixel coord in radial.  However, it is
// sometimes useful, and it makes for easier readability if you write
// width when you mean width.
//
// also I've used it here and there in the pixel and map coord classes
// to allow for offset type calculations
//
function boxSize(width,height){
    this.width=width;
    this.height=height;
    this.fs=12;
}
boxSize.prototype.toString=function(){
    return"boxSize("+this.width+", "+this.height+")";
};
boxSize.prototype.toGSize=function(){
    return new GSize(this.width,this.height);
};

boxSize.prototype.apply=function(a){
    a.style.width=this.toWidthStyle();
    a.style.height=this.toHeightStyle();
};
boxSize.prototype.toWidthStyle=function(){
    return toStyle(this.width);
};
boxSize.prototype.toHeightStyle=function(){
    return toStyle(this.height);
};
boxSize.prototype.setEm=function(fontsize){
    this.fs=fontsize;
};
boxSize.prototype.toWidthStyleEm=function(){
    return this.width/this.fs+"em";
};
boxSize.prototype.toHeightStyleEm=function(){
    return this.height/this.fs+"em";
};
boxSize.prototype.scale=function(sa,sb){
    if(!sb){sb=sa;}
    var sx=this.width* sa;
    var sy=this.height*sb;
    return new boxSize(sx,sy);
};
boxSize.prototype.grow=function(growbox){
    if(growbox){
	this.width+=growbox.width;
	this.height+=growbox.height;
    }
    return this;
};
boxSize.prototype.equals=function(otherBox){
    if(!otherBox){return false;}
    return this.width==otherBox.width && this.height==otherBox.height
};
boxSize.prototype.approxEquals=function(otherBox){
    if(!otherBox){return false;}
    return compareSixDecimals(this.width,otherBox.width) && compareSixDecimals(this.height,otherBox.height);
};


// other classes and functions


// spiral
//
// written May 5, 2005, so it is higher up the learning curve that
// most of this code
//
// spiral is more or less an iterator over a two dimensional array
// that starts kind of in the middle and spirals outwards.  I wrote
// this myself, but it *looks* like Google maps uses a similar
// technique to fill a map from the middle out because I think their
// zero point is in the middle, rather than onthe top left.  Anyway, I
// mainly wrote this because I was watching my screen refresh from
// left to right, top to bottom, but what I was interested in was the
// middle.  When the images are cached in the browser, it looks pretty
// cool when zooming in, like an explosion.
//
// oh, this is documentation not self-adulation.  Anyway, pass in the
// 2D array and a callback function.  The aray will NOT be checked to
// make sure that it is 2D---you'll probably just get runtime crash.
// The callback function should accept (row, col) as arguments.  I
// used to also pass back a reference to the "thing" at row, col in
// the array, as in (array[row][col],row,col), but it seemed more
// flexible to ignore that and just pass back the indices, and let the
// callback look at the original array if desired.  Besides, I wanted
// to guard against possible errors related to copy by reference, copy
// by value, the masking of 'this', etc.
//
function spiral( callback,array ){
    // iterate from the middle outwards to get around the shift
    // and wait problem when zooming

    var w=array.length;
    var h=array[0].length;
    var l=Math.floor(w/2)-1;
    var r=l+1;
    var t=Math.floor(h/2)-1;
    var b=t+1;
    
    //initialize loop
    var i=l;
    var j=t;
    t--;
    l--;
    i--;
    // spiral out from the middle
    while (! (l<0 && r>=w && t<0 && b>=h)){
	while(moveRight()){
	    callback(i,j);
	}
	while(moveDown()){
	    callback(i,j);
	}
	while(moveLeft()){
	    callback(i,j);
	}
	while(moveUp()){
	    callback(i,j);
	}
	// a bit of leftover debugging code
	//alert("done\ni is "+i+"\n j is"+j+"\nl is "+l+"\nr is "+ r+"\nt is "+ t+"\nb is "+b);
    }
    // set up the functions for spiraling out
    function moveRight(){
	if(j>=0){
	    if(i<r ){
		i++;
		if(i<w)return 1;
	    }
	}else{
	    i=r;
	}
	if(r<w)r++;
	return 0;
    };
    function moveDown(){
	if(i<w){
	    if(j<b){
		j++;
		if(j<h)return 1;
	    }
	}else{
	    j=b;
	}
	if(b<h)b++;
	return 0;
    };
    function moveLeft(){
	if(j<h){
	    if(i>l){
		i--;
		if(i>=0)return 1;
	    }
	}else{
	    i=l;
	}
	if(l>-1)l--;
	return 0;
    };
    function moveUp(){
	if(i>=0){
	    if(j>t){
		j--;
		if(j>=0)return 1;
	    }
	}else{
	    j=t;
	}
	if(t>-1)t--;
	return 0;
    };
}

// isDivisible
//
// If the points get offset from a known alignment, then I run the
// risk of caching slightly overlapping maps.  In order to fix this, I
// make sure that whenever I set a UTM point as the top left corner of
// the map, or a cell or whatever, I make sure that it is divisible by
// whatever factor I am using.  This makes the passed in UTM a clean
// multiple of 128*16, if no arguments are used, which corresponds to
// a scale of 16 meters per pixel, and a tile size of 128.  Typically,
// you want to pass in as factor scale*tileSize
//

function isDivisible(utm,factor){
    if(!factor){factor=(16*128);}
    var dcoord=utm.utmCoord.scale( 1 /factor);
    utm.utmCoord=(new decimalCoord(Math.floor(dcoord.x),Math.floor(dcoord.y))).scale(factor);
    utm.setSelf();
    return utm;
}

// from googlemaps, lightly reworked


// from googlemaps, lightly reworked
function classNamer(c,name){if(c.className){c.className+=" "+name}else{c.className=name}};

// cut a lot of cutnpaste from gmaps

// okay here is something really ugly and hackish that I need to clean
// up and or use Sarissa or Prototype to fix properly

// my very own classes for stupid button tricks

// I redid this for gracenotes.  compare, contrast, and refactor

function Anchor(text,coderef,ownerDocument){
    if(!ownerDocument){ownerDocument=document;}
    this.anchor=ownerDocument.createElement("a");
    this.anchor.href="javascript:void(0)";
    if(coderef){this.anchor.onclick=coderef;}
    if(text){
	this.anchor.appendChild(ownerDocument.createTextNode(text));
    }
    return this;
}
// modify the above as I get used to using DOM elements...
function getAnchorElement(){
    var anchor=document.createElement("a");
    anchor.className='bA'; // somehow I am getting borders!
    return anchor;
}

Effect.MyPuff = Class.create();
Effect.MyPuff = Effect.Puff;
Effect.MyPuff.prototype.hide=function() {
    this.element.style.display = 'none';
    this.element.parentNode.display='none';
    var parent=this.element.parentNode;
    var parentOfParent=parent.parentNode;
    parent.removeChild(this.element);
    parentOfParent.removeChild(parent);
};


Effect.MyFade = function(element) {
    options = Object.extend({
	    from: 1.0,
		to:   0.3,
		afterFinish: function(effect){
		try{Element.remove(effect.element);}catch(E){}
		effect.setOpacity(1); } 
	}, arguments[1] || {});
  return new Effect.Opacity(element,options);
}

function disapparate(a,e){
    Event.observe(a, "click", (function(event){
				   var self=this;
				   new Effect.MyFade(e);
				   Event.stop(event);
			       }), true);
}

function takeDown(puffElement,clickFun){
    if(!clickFun){clickFun=disapparate;}
    var anchor=document.createElement("a");
    anchor.href="javascript:void(0)";
    clickFun(anchor,puffElement);
    var xbox=new boxSize(14,14);
    var fsrc="/tracer/images/close.png";
    if(_SARISSA_IS_IE){
	fsrc="/tracer/images/close.gif";
    }
    var f=zb.create(fsrc,
		    xbox, //width,height
		    null, //position via containing anchor
		    null, // z index
		    null, // crop
		    null); // name of class
    anchor.appendChild(f);
    anchor.className='takeDown';
    return anchor;
}

function noFunTakeDown(mapObject){
    var anchor=document.createElement("a");
    var xbox=new boxSize(14,14);
    var fsrc="/tracer/images/close.png";
    // try using gicon class, rather than messing with copy paste zb.create nonsense
    var f=new GIcon();
    f.shadow = null;
    f.image = "/tracer/images/close.png";
    f.iconSize = xbox;
    f.shadowSize = new GSize(0,0);

    // temporary marker to get at image
    var m=new GMarker(new GPoint(0,0),f);
    m.initialize(mapObject);
    var img=m.iconImage;
    m.remove();
    m=null;
    anchor.appendChild(img);
    anchor.className='takeDown';
    return anchor;
}

function UniqueId(){
    var _active_links=0;
    this.next=function(){
	_active_links +=1;
	return _active_links;
    }
}

var globalId = new UniqueId();

function ALi(text,coderef){
    //if(_debug)_debug.log("in ALi coderef is "+coderef);
    this.li=document.createElement("li");
    this.A=new Anchor(text,coderef);
    this.li.appendChild(this.A.anchor);
    //this.li.style.className="activelink";
    this.li.style.id=globalId.next();
}

function showHideAction(obj) {
    this.obj=obj;
    var localObj=this.obj;
    this.clicky=function(){
	if(localObj.childrenHidden){
	    localObj.show();
	}else{
	    localObj.hide();
	}
    };
}

var _unique_buttons=0;
function toggleButton(text,parent){
    this.parent=parent;
    this.id=_unique_buttons++;
    this.childrenHidden=1;
    this.showMyself=null;
    this.hideMyself=null;
    var showHide=new showHideAction(this);
    this.tButton=new ALi(text,showHide.clicky);
    this.htmlObj=this.tButton.li;
}
toggleButton.prototype.toString=function(){
    return "id="+this.id+",hidden="+this.childrenHidden+",parent="+this.parent.toString();
};

function recursiveElementHide(element,index){
    if(index){
	var childList=element.childNodes[index];
	recursiveElementHide(childList);
    }else{
	try{
	    var childList=element.childNodes;
	    if(childList){
		for(var i=0;i<childList.length;i++){
		    recursiveElementHide(childList[i]);
		}
	    }
	    //element.style.visibility="hidden";
	    if(element&&element.style)element.style.display="none";
	}catch(exceptionE){}
	
    }
}
function recursiveElementShow(element,index){
    if(index){
	var childList=element.childNodes[index];
	recursiveElementShow(childList);
    }else{
	try{
	    var childList=element.childNodes;
	    if(childList){
		for(var i=0;i<childList.length;i++){
		    recursiveElementShow(childList[i]);
		}
	    }
	    if(element&&element.style)element.style.display="block";
	}catch(exceptionE){}
    }
}
toggleButton.prototype.hide=function(){
    // hide children, but don't hide self!
    recursiveElementHide(this.htmlObj,1);
    this.childrenHidden=1;
    //if(_debug)_debug.log("in toggleButton.hide()");
    if(this.parent.hideMyself)this.parent.hideMyself();
};
toggleButton.prototype.show=function(){
    recursiveElementShow(this.htmlObj);
    this.childrenHidden=0;
    //if(_debug)_debug.log("in toggleButton.show()");
    if(this.parent.showMyself)this.parent.showMyself();
};
// toggleButton.prototype.toggleHidden=function(){
//     alert("entering toggleHidden for object "+this);
//     if(this.childrenHidden){
// 	this.show();
//     }else{
// 	this.hide();
//     }
// };



// additions to prototype.js v1.2
// additions to Element
Element.getCoord=function(element) {
    var elem = $(element);
    
    var c= new coord(parseFloat(elem.style.left || '0'),
		     parseFloat(elem.style.top || '0'));
    return c;
};
Element.getBox=function(element){
    var elem = $(element);
    var c= new boxSize(elem.offsetWidth,elem.offsetHeight);
    return c;
};
// Retrieve the x coordinate
Element.getLeft=function(element) {
    var elem = $(element);
    return parseFloat(elem.style.left || '0');
};
// Retrieve the y coordinate
Element.getTop=function(element) {
    var elem = $(element);
    return parseFloat(elem.style.top || '0');
};
// Retrieve the rendered width of an element
Element.getWidth=function(element)  {
    if(element){
	this.elem = $(element);
    }
    var result=0;
    if (this.elem.offsetWidth) {
	result = this.elem.offsetWidth;
    } else if (this.elem.clip && this.elem.clip.width) {
	result = this.elem.clip.width;
    } else if (this.elem.style && this.elem.style.pixelWidth) {
	result = this.elem.style.pixelWidth;
    }
        if(element){
	this.elem=null;
    }
    return parseInt(result);
};
// Retrieve the rendered height of an element
// override (I hope) the default Element.getHeight
Element.getHeight=function(element){
    if(element){
	this.elem = $(element);
    }
    var result = 0;
    if (this.elem.offsetHeight) {
	result = this.elem.offsetHeight;
    } else if (this.elem.clip && this.elem.clip.height) {
	result = this.elem.clip.height;
    } else if (this.elem.style && this.elem.style.pixelHeight) {
	result = this.elem.style.pixelHeight;
    }
        if(element){
	this.elem=null;
    }
    return parseInt(result);
};

// make an Element have zero height
Element.flatten=function(element) {
    if(element){
	this.elem = $(element);
    }
    this.elem.style.height = "0px";
        if(element){
	this.elem=null;
    }
    return 0; 
};

// Position an Element at a specific pixel coordinate
Element.shiftTo=function(coord,element){
    if(element){
	this.elem = $(element);
    }
    this.elem.style.left = coord.toLeftStyle();
    this.elem.style.top = coord.toTopStyle();
        if(element){
	this.elem=null;
    }
    return elem;
};
// Position an object by an offset
Element.shiftBy=function(coord,element){
    if(element){
	this.elem = $(element);
    }
    var newPos=new coord(this.elem.getLeft(),this.elem.getTop());
    newPos.moveBy(coord);
    this.elem.style.left = newPos.toLeftStyle();
    this.elem.style.top = newPos.toTopStyle();
        if(element){
	this.elem=null;
    }
    return elem;
};
// Set the z-order of an object
Element.setZIndex=function(z,element) {
    if(element){
	this.elem = $(element);
    }
    this.elem.style.zIndex = z;
        if(element){
	this.elem=null;
    }
    return elem;
};

// an elapsed time printer, based on O'Reilly Javascript & DHTML
// cookbook, but not much different than perl script I wrote long ago.
// there's only so many ways to do this stuff....
function printElapsedTime( date ) {
    var diff = date.getTime();
    // days elapsed (month too hard)
    var scratchPad = diff / printElapsedTime.oneDay;
    var daysLeft = Math.floor(scratchPad);
    // hours 
    diff -= (daysLeft * printElapsedTime.oneDay);
    scratchPad = diff / printElapsedTime.oneHr;
    var hrsLeft = Math.floor(scratchPad);
    // minutes 
    diff -= (hrsLeft * printElapsedTime.oneHr);
    scratchPad = diff / printElapsedTime.oneMin;
    var minsLeft = Math.floor(scratchPad);
    // seconds
    diff -= (minsLeft * printElapsedTime.oneMin);
    scratchPad = diff / printElapsedTime.oneSec;
    var secsLeft = Math.floor(scratchPad);
    // form string
    var elapsedString="";
    if(daysLeft){
	elapsedString += daysLeft;
	if(daysLeft>1){
	    elaspedString += " days, ";
	}else{
	    elaspedString += " day, ";
	}
    }
    if(daysLeft || hrsLeft){
	if(hrsLeft<10){
	    elaspedString += "0";
	}
	elapsedString += hrsLeft+":" ;
    }
    if(hrsLeft || minsLeft){
	if(minsLeft<10){
	    elaspedString += "0";
	}
	elapsedString += minsLeft+":" ;
    }
    if(secsLeft<10){
	elaspedString += "0";
    }
    elapsedString += secsLeft+" " ;
    // trailing units
    if( daysLeft>0){//do nothing
    }else if(hrsLeft>0){
	elapsedString += " hrs";
    }else if(minsLeft>0){
	elapsedString += " mins";
    }else{
	elapsedString += " secs";
    }
    return elapsedString;
}
printElapsedTime.oneSec = 1000;
printElapsedTime.oneMin = 60 * printElapsedTime.oneSec;
printElapsedTime.oneHr = 60 * printElapsedTime.oneMin;
printElapsedTime.oneDay = 24 * printElapsedTime.oneHr;


function getContentFromURI(url,element,callback,xsltproc) {
    try{
        var e = $(element);
	var oldCursor=e.style.cursor;
	e.style.cursor = "progress";
	var req = new XMLHttpRequest();
	try{
	    req.open("GET", url, true);
	}catch(e){
	    alert("exception processing req.open" + e + ",\n url:" + url);
	}
        function fire_callback() {
		alert(req.readyState);
            if (req.readyState == 4) {
		e.style.cursor=oldCursor;
		e=null;
		if (callback) {
		    setTimeout((function() {callback(req)}).bind(this), 10);
		}else{
		    if(xsltproc){
			Sarissa.updateContentFromNode(req.responseXML, $(element), xsltproc);
		    }
		}
            };
        };
        req.onreadystatechange =fire_callback(); 
        req.send(null);
    }
    catch(exp){
	e=$(element);
	e.style.cursor=oldCursor;
	e=null;
        throw exp;
    };
};

function loadXMLDoc(url,fncallback,f){
    var e = $(f);
    var oldCursor;
    if(e){
	oldCursor=e.style.cursor;
	e.style.cursor = "progress";
    }
    var req;
    req = new XMLHttpRequest();
    try{
	req.open("GET", url, true);
    }catch(e){
	alert("exception processing req.open" + e + ",\n url:" + url);
    }
    req.onreadystatechange = function(){
	// only if req shows "complete"
	if (req.readyState == 4) {
	    if(e){
		e.style.cursor=oldCursor;
		e=null;
	    }
	    var responseText  = req.responseText;
	    var imgObject=null;
	    if(responseText){
		fncallback(req,f);
	    }
	}
    };
    req.send(null);
};


// clean up leftovers of past effects
function EffectClear(e){
    var element=$(e);
    //element.effect_scale=null;
}
  

// fix up a gap I've noticed in Sarissa
/**
 * <p> Copies the node  nodeFrom into (as a child of) nodeTo</p>
 * @argument nodeFrom the Node to copy
 * @argument nodeTo the Node into which we will copy nodeFrom 
 */
Sarissa.copyNodeInto = function(nodeFrom, nodeTo, ownerDoc) {
    if((!nodeFrom) || (!nodeTo)){
        throw "Both source and destination nodes must be provided";
    };
//     if(!ownerDoc){
//  	ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
//     }
//     if(ownerDoc.importNode && (!_SARISSA_IS_IE)) {
//  	nodeTo.appendChild(ownerDoc.importNode(nodeFrom, true));
//     }else{
	nodeTo.appendChild(nodeFrom.cloneNode(true));
//     }
};
