/**
 * Lighter implementation of GLatLng
 */
function MyLatLng(lat,lng) {
	this._lat = lat;
	this._lng = lng;
}

MyLatLng.prototype.lat = function() { return this._lat;};
MyLatLng.prototype.lng = function() { return this._lng;};

/**
 * Lighter implementation of GLatLngBounds
 * @param sw
 * @param ne
 * @return
 */
function MyBounds(sw,ne) {
	this.sw=sw;
	this.ne=ne;
}

MyBounds.computeCenter= function( bounds ) {
	if ( bounds.getCenter ) {
		return bounds.getCenter();
	}
	var sw = bounds.getSouthWest();
	var ne = bounds.getNorthEast();
	return new MyLatLng((sw.lat()+ne.lat())/2,(sw.lng()+ne.lng())/2);
};

MyBounds.prototype.getSouthWest = function() { return this.sw;};
MyBounds.prototype.getNorthEast = function() { return this.ne;};
MyBounds.prototype.containsBounds = function( other ) {
	var otherSW = other.getSouthWest();
	if ( otherSW.lat() < this.sw.lat() || otherSW.lng()<this.sw.lng() ) {
		return false;
	}
	var otherNE = other.getNorthEast();
	if ( otherNE.lat() > this.ne.lat() || otherNE.lng()>this.ne.lng() ) {
		return false;
	}
	return true;
};


MyBounds.prototype.containsLatLng = function( ll ) {
	if ( ll.lat() < this.sw.lat() || ll.lng()<this.sw.lng() ) {
		return false;
	}
	if ( ll.lat() > this.ne.lat() || ll.lng()>this.ne.lng() ) {
		return false;
	}
	return true;
};



/*********************************************** GeoCluster */
/**
 * Define an object to hold clustered (indexed) geographical information.
 */


/**
 * Constructor, all the members will come from the remote side.
 * @return
 */
function GeoCluster() {
	this.computeIndices = function(latlng) {
		var xy = { y: 0,x: 0};
		xy.y=Math.floor((latlng.lat()-this.latOrig)/this.latSize);
		xy.x=Math.floor((latlng.lng()-this.lngOrig)/this.lngSize);

		// cap to the data we have available in the cluster
		if ( xy.x < 0 ) { xy.x = 0; }
		if ( xy.y < 0 ) { xy.y = 0; }
		if ( xy.x >= this.numTiles ) { xy.x = this.numTiles-1; }
		if ( xy.y >= this.numTiles ) { xy.y = this.numTiles-1; }
		return xy;
	};
	this.minPriced=null;
	this.totalCount=0;
}


/**
 * Initialize the object with the member data from the remote side.
 * @param other
 * @return
 */
GeoCluster.prototype.init = function(other) {
    for (var property in other) {
    	if ( typeof this[property] == "undefined" ) {
    		this[property] = other[property];
    	}
    }
    // compute the total count
    if ( typeof this.tiles != "undefined" && this.tiles) {
    	for( var i=0;i<this.tiles.length;i++ ) {
    		this.totalCount += this.tiles[i]?this.tiles[i].count:0;
    	}
    }
};

/**
 * Call the foo callback for all the listings.
 * @param foo
 * @param obj
 * @return
 */
GeoCluster.prototype.forEach = function( obj, sourceName, foo ) {
	if( typeof this.tiles == "undefined" ) {
		return;
	}
	if ( typeof(sourceName) == "undefined" ) {
		sourceName = 'listings';
	} else {
		sourceName = 'listings_' + sourceName;
	}
	for( var i=0; i<this.tiles.length ; i++ ) {
		var tile = this.tiles[i];
		if ( tile ) {
			var listings = tile[sourceName];
			if ( typeof(listings) == "undefined" ) {
				//console.log( "BANG-all " + sourceName);
				continue;
			}
			for ( var l=0; l<listings.length;l++) {
				foo.call(obj, obj, listings[l]);
			}
		}
	}
};

/**
 * Equivalent of the google function but does not require creating a new GLatLng per test.
 * 
 * @param lat
 * @param lng
 * @return
 */
GeoCluster.prototype.containsLatLng = function( lat, lng, sw, ne ) {
	if ( lat < sw.lat() || lng<sw.lng() ) {
		return false;
	}
	if ( lat > ne.lat() || lng>ne.lng() ) {
		return false;
	}
	return true;
};


/**
 * Call the foo callback for all the listings that are in the bounds.
 *   foo( obj, listing );
 * @param foo
 * @param obj
 * @param bounds
 * @param sourceName the source listing to use by default tile.listing
 * @return
 */
GeoCluster.prototype.forEachInBounds = function( obj, bounds, sourceName, foo ) {
	if ( typeof this.tiles == "undefined" ) {
		return;
	}
	var swLL = bounds.getSouthWest();
	var neLL = bounds.getNorthEast();
	var swXY = this.computeIndices(swLL);
	var neXY = this.computeIndices(neLL);
	if ( typeof(sourceName) == "undefined" ) {
		sourceName = 'listings';
	} else {
		sourceName = 'listings_' + sourceName;
	}
	for ( var i=swXY.x; i<=neXY.x; i++ ) {
		for ( var j=swXY.y;j<=neXY.y; j++ ) {
			var index = i + this.numTiles*j;
			if ( this.tiles[index]) {
				var listings = this.tiles[index][sourceName];
				if ( typeof(listings) == "undefined" ) {
					//console.log( "BANG: " + sourceName );
					continue;
				}
				for ( var l=0;l<listings.length;l++) {
					var one  = listings[l];
					if ( this.containsLatLng(one.lat, one.lon, swLL, neLL ) ) {
						foo.call( obj, obj, listings[l]);
					}
				}
			}
		}
	}
};

/**
 * Filter the listings through the callback. The callback will be called with an array.
 * The result is stored back in each tile.
 * @param foo
 * @param obj
 * @param sourceName
 * @param destName
 * @return
 */
GeoCluster.prototype.filter = function ( obj, sourceName, destName, foo ) {
	if ( typeof this.tiles == "undefined" ) {
		return;
	}
	if ( typeof(sourceName) == "undefined" ) {
		sourceName = 'listings';
	} else {
		sourceName = 'listings_'  + sourceName;
	}
	if ( typeof(destName) == "undefined" ) {
		destName = 'listings_dest';
	} else {
		destName = 'listings_' + destName;
	}
	for( var i=0; i<this.tiles.length ; i++ ) {
		var tile = this.tiles[i];
		if ( tile ) {
			var listings = tile[sourceName];
			tile[destName]= foo.call(obj, obj, listings);
		}
	}
};

GeoCluster.prototype.resetMinPriced = function () {
	this.minPriced = null;
};

/**
 * Track the item with the minimum price and some other rendering related information.
 * @param listings
 * @return
 */
GeoCluster.prototype.trackMinPriced = function(listings) {
	if ( !listings ) {
		return;
	}
	for( var i=0;i<listings.length;i++) {
		var l = listings[i];
		if ( l.entityType == 'Auction' ) {
			l.markerType = 'auc';
		} else {
			l.markerType = l.saleType?l.saleType.toLowerCase():'reo';
		}
		if ( l.minBid && l.minBid > 0 && l.markerType == 'reo') {
			if ( this.minPriced === null ) {
				this.minPriced = l;
			} else if ( this.minPriced.minBid > l.minBid ){
				this.minPriced = l;
			}
		}
	}
};



/**
 * Compute the bounds used when fetching data, they are an extension of the current map bounds.
 */
function GeoFetchParams(  )  {
	this.nelat=null;
	this.nelon=null;
	this.swlat=null;
	this.swlon=null;
	this.sw = null;
	this.ne =null;
	this.zoom = null;
}

GeoFetchParams.prototype.initFromMap = function(map,extendFactor) {
    this.zoom = map.getZoom();
	
    var mapBounds = map.getBounds();
    var mapSpan = mapBounds.toSpan();
    this.ne = mapBounds.getNorthEast();
    this.sw = mapBounds.getSouthWest();
    var extendLat = mapSpan.lat() * extendFactor;
    var extendLng = mapSpan.lng() * extendFactor;
    this.nelat = this.ne.lat() + extendLat;
    this.nelon = this.ne.lng() + extendLng;
	this.swlat = this.sw.lat() - extendLat;
	this.swlon = this.sw.lng() - extendLng;
};

GeoFetchParams.prototype.getSouthWest = function() {
	return this.sw;
};

GeoFetchParams.prototype.getNorthEast = function() {
	return this.ne;
};

GeoFetchParams.prototype.initFromCenter = function(center) {
    this.zoom = 13;
	
    this.nelat = center.lat;
    this.nelon = center.lon;
	this.swlat = center.lat;
	this.swlon = center.lon;
};

/**
 * Build an object that respects the current server API for fetching new data.
 * @return
 */
GeoFetchParams.prototype.content = function() {
    return  {
		nelat: this.nelat, 
		nelon: this.nelon, 
		swlat: this.swlat,
    	swlon: this.swlon,
		zoomLevel: this.zoom
    };
};



/**
 * Check to see if this object contains the other bound.
 * @param other
 * @return
 */
GeoFetchParams.prototype.containsBounds = function( other ) {
	var otherSW = other.getSouthWest();
	if ( otherSW.lat() < this.swlat || otherSW.lng()<this.swlon ) {
		return false;
	}
	var otherNE = other.getNorthEast();
	if ( otherNE.lat() > this.nelat || otherNE.lng()>this.nelon ) {
		return false;
	}
	return true;
};

LatLon = {};

LatLon.toRad = function(a) {
	return a*Math.PI/180;
};

LatLon.toBearing=function(a) {
	return (LatLon.toDeg(a)+360) % 360;
};

LatLon.toDeg = function(a) {
	 return a * 180 / Math.PI;	
};

LatLon.bearing = function(lat1, lon1, lat2, lon2) {
	lat1 = LatLon.toRad(lat1); lat2 = LatLon.toRad(lat2);
	var dLon = LatLon.toRad(lon2-lon1);
	var y = Math.sin(dLon) * Math.cos(lat2);
	var x = Math.cos(lat1)*Math.sin(lat2) -
	Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
	return LatLon.toBearing(Math.atan2(y, x));
};

GeoLib = {};

(function() {

/**
 * Private global to store the parsed parameters in.
 * Since the parameters are parsed from the URL they do not change
 * once the page is displayed.
 * 
 */
var qsParm = undefined;

/**
 * Helper function to parse the parameters passed in
 */
GeoLib.parseParams = function() {
	if ( typeof(qsParm) != "undefined" ) {
		return qsParm;
	}
	qsParm = [];
	var query = window.location.search.substring(1);
	if ( !query && window.location.hash  ) {
		var qq = window.location.hash.indexOf("?");
		if ( qq > -1 ) {
			query = window.location.hash.substring(qq+1);
		}
	}
	var parms = query.split('&');
	for ( var i = 0; i < parms.length; i++) {
		var pos = parms[i].indexOf('=');
		if (pos > 0) {
			var key = parms[i].substring(0, pos);
			var val = parms[i].substring(pos + 1);
			if ( qsParm[key] ) {
				// key already exists, if it's an array then append to it otherwise create as an array
				if ( qsParm[key].push ) {
					qsParm[key].push(val);
				} else {
					var moveToArray = [];
					moveToArray.push(qsParm[key]);
					moveToArray.push(val);						
					qsParm[key] = moveToArray;
				}
			} else {
				qsParm[key] = val;
			}
		}
	}
	return qsParm;
};
})();

/**
 * Check the URL and decide if the given panel should be activated.
 */
GeoLib.isPanelActivated=function(panel) {
	if (window.location.hash) {
		// if there is a hash then check for the wanted panel
		var w_hash=window.location.hash;
		if ( w_hash.indexOf("#" + panel ) > -1 ) {
			return true;
		}
	}
	var parms = GeoLib.parseParams();
	if ( !parms || !parms.ui_panel ) {
		// this is the default panel
		if ( panel == "map" ) {
			return true;
		}
		return false;
	}
	if ( parms.ui_panel != panel) {
		return false;
	}
	return true;
};


HeatColorGenerator = {};

HeatColorGenerator.legend_steps = 7;
/**
 * Adjust the maximum value to mostly zeros.
 * @param a
 * @return
 */
HeatColorGenerator.adjustMax = function(a) {
	if ( a  < 10 ) {
		return a;
	}
	var scaler = Math.floor(Math.log(a)/Math.LN10 + 0.000001);
	scaler = scaler - 1;
	if ( scaler < 1 ) {
		scaler = 1;
	}
	scaler = Math.pow(10, scaler);
	a = Math.floor(a / scaler+0.5) * scaler;
	return a;
};

HeatColorGenerator.adjustMin = function(a) {
	if ( a < 10) {
		return 0;
	}
	var scaler = Math.floor(Math.log(a)/Math.LN10 + 0.000001);
	/*
	scaler = scaler - 1;
	if ( scaler < 1 ) {
		scaler = 1;
	}*/
	scaler = Math.pow(10, scaler);
	a = Math.floor(a / scaler) * scaler;
	return a;
};

HeatColorGenerator.generateColor01 = function(value, range) {
	
	// find our range
	var start = range.start;
	var end = range.mid;
	if ( value > 0.5 ) {
		value = value - 0.5;
		start = range.mid;
		end = range.end;
	}
	// now expand to get the maximum in the range
	value = value * 2;
	var oc=[],i=0;
	oc[i++] = 'rgb(';
	oc[i++] = Math.floor(start.red + (end.red - start.red) * value);
	oc[i++] = ',';
	oc[i++] = Math.floor(start.green + (end.green - start.green) * value);
	oc[i++] = ',';
	oc[i++] = Math.floor(start.blue + (end.blue - start.blue) * value);
	oc[i++] = ')';
	var color= oc.join('');
	/*
	if ( false && console && console.log ) {
		console.log( "Generated " + color + " for " + value );
	}*/
	return color;
};

HeatColorGenerator.default_range = {
	start : { red : 255, green : 255, blue : 0 },
	mid : { red : 255, green : 128, blue : 0 },
	end : { red : 255, green : 0, blue : 0 }	
};

HeatColorGenerator.Linear = function () {
    this.range = HeatColorGenerator.default_range;
    // the minimum value of the data being represented
    this.minStats = null;
    // the maximum value of the data being represented
    this.maxStats = null;
    
    // map the colors to the range
    this.ceilToRange = true;
};


/**
 * Analyze the data passed in to generate the proper range for colors.
 * @param stats
 * @return
 */
HeatColorGenerator.Linear.prototype.analyze = function(stats,activeIndex) {
	if ( stats && stats.length > 0 ) {
		//clone the array so that we can sort it
    	var sorted = [];
		for( var i=0;i<stats.length;i++) {
			sorted.push( stats[i][activeIndex] );
		}
    	sorted.sort( function(a,b){return (a-b);});
		this.maxReal = sorted[sorted.length-1];
		this.minReal = sorted[0];

		this.minStats = HeatColorGenerator.adjustMin(this.minReal);
		this.maxStats = HeatColorGenerator.adjustMax(sorted[sorted.length-1]);
		                      
		//console.log( "Real min " + this.minReal + ", max "+  this.maxReal + ", used min " + this.minStats + " max " + this.maxStats );
	} else {
    	this.minStats = null;
    	this.maxStats = null;
    }
	
};

/**
 * Generate the color for the given value in the range passed in earlier.
 * 
 * @param {Number} value
 * @return color
 */
HeatColorGenerator.Linear.prototype.generateColor = function(value) {
	// normalize value
	value = (value-this.minStats)/this.maxStats;
	if ( value < 0 ) { value = 0; }
	if ( value > 1 ) { value = 1; }
	// generate rgb()
	
	if ( this.ceilToRange ) {
		value = Math.ceil(value*HeatColorGenerator.legend_steps)/HeatColorGenerator.legend_steps;
	}
	return HeatColorGenerator.generateColor01(value, this.range);	
};

HeatColorGenerator.generateLegendLine = function( output, color, start, end, scale ) {
	var l = output.length;
	output[l++] = "<tr><td style='width: 20px;background-color:  ";
	output[l++] = color;
	output[l++] = "'></td><td width='30%'>";
	output[l++] = Math.floor(start/scale);
	if ( scale == 1000 ) {
		output[l++] = "k";
	}
	output[l++] = "</td>";
	output[l++] = "<td>-</td><td width='30%'>";
    // show the difference
    // output[l++] = Math.floor((end-start)/scale);
    output[l++] = Math.floor(end/scale);
	if ( scale == 1000 ) {
		output[l++] = "k";
	}
	output[l++] = "</td>"
	output[l++] = "</tr>";
	
};

HeatColorGenerator.Linear.prototype.generateLegend = function() {
	if ( this.maxStats === null || this.minStats === null ) {
		return null;
	}
	
	var lg=[],l=0;
	
	var adjustedMax = HeatColorGenerator.adjustMax(this.maxStats);
	var adjustedMin = HeatColorGenerator.adjustMin(this.minStats);
	var step = (adjustedMax-adjustedMin)/HeatColorGenerator.legend_steps;
	var scale = step>3000?1000:1;
	lg[l++] = "<table width='100%'>";
	for ( var i=0; i<HeatColorGenerator.legend_steps; i++ ) {
		var start =adjustedMin+i*step;
		var end = (start + step);
		if ( i==HeatColorGenerator.legend_steps-1) {
			end = HeatColorGenerator.adjustMax(this.maxReal);
		}
		var color = this.generateColor(end);
		HeatColorGenerator.generateLegendLine( lg, color, start, end,scale );		
	}
	l = lg.length;
	lg[l++] = "</table>";
	return lg.join('');
};

HeatColorGenerator.Interval = function() {
	this.buckets = [];
    this.range = HeatColorGenerator.default_range;	
};

HeatColorGenerator.Interval.prototype.analyze = function(stats,activeIndex) {
	if ( stats && stats.length > 0 ) {
		var count = stats.length;
    	var sorted = [];
		for( var i=0;i<stats.length;i++) {
			sorted.push( stats[i][activeIndex] );
		}
    	sorted.sort( function(a,b){return (a-b);});
    	
    	if ( count < HeatColorGenerator.legend_steps ) {
    		this.buckets = sorted;
    	} else {
    		var bucketSize = count / HeatColorGenerator.legend_steps;
    		for( var i=0;i<HeatColorGenerator.legend_steps;i++) {
    			this.buckets[i] = sorted[Math.floor(bucketSize*i)];
    		}
    	}
	}
};

HeatColorGenerator.Interval.prototype.findBucketPosition = function(value) {	
	for( var i=0; i<this.buckets.length; i++ ) {
		if ( value < this.buckets[i] ) {
			return (i>0?i-1:0)/this.buckets.length;
		}
	}
	return 1;
};

HeatColorGenerator.Interval.prototype.generateColor = function(value) {	
	return HeatColorGenerator.generateColor01( this.findBucketPosition(value), this.range);
};

HeatColorGenerator.Interval.prototype.generateLegend = function() {
	if ( this.buckets === null || this.buckets.length === 0 ) {
		return null;
	}
	
	var lg=[],l=0;
	
	var adjustedMax = HeatColorGenerator.adjustMax(this.buckets[this.buckets.length-1]);
	var adjustedMin = HeatColorGenerator.adjustMin(this.buckets[0]);
	var steps = Math.min(HeatColorGenerator.legend_steps,this.buckets.length);
	var step = (adjustedMax-adjustedMin)/steps;
	var scale = step>3000?1000:1;
	lg[l++] = "<table width='100%'>";
	for ( var i=0; i<this.buckets.length-1; i++ ) {
		var start = this.buckets[i];
		var end = "";
		if ( i<steps-1) {
			end = this.buckets[i+1];
		}
		if ( start == end ) {
		    continue;
		}
		var color = this.generateColor(end);
		HeatColorGenerator.generateLegendLine( lg, color, start, end,scale );		
	}
	l = lg.length;
	lg[l++] = "</table>";
	return lg.join('');
};


MapLegend = function(_domId) {
	this.domId = _domId;
};

MapLegend.prototype.displayContent = function( title,content ) {
	if ( !content ) {
		return this.noContent();
	}
	var target = dojo.byId(this.domId);
	if ( !target ) {
		return;
	}
	target.style.display = 'inline';
	target.style.opacity = 1;
	contentDiv = dojo.query('div',target)[0];
	contentDiv.innerHTML = content;
	titleSpan = dojo.query('h4',target)[0];
	titleSpan.innerHTML = title;
	/*
	var anim = dojo.fx.chain(
			[ dojo.fadeIn( {
						node :target,
						duration : 1000
					}), 
					dojo.fadeOut( {
						node :target,
						duration :1000,
						delay :5000
					})		
					]).play();
	*/	
};

MapLegend.prototype.noContent = function() {
	var target = dojo.byId(this.domId);
	if ( target ) {
		target.style.display = 'none';
	}
};

