var cm_map;
var cm_collection;
var cm_site_root = "http://tweemap.com/";
var cm_image_root = "http://media.tweemap.com/img/"
var enable_log = false;
var global_scale = 24.0;
var enable_grid = false;
var enable_dots = true;
var color_scheme = "magenta";
var map_header_title = "";
var map_header_img = "";

function log( msg ) {
	if( window.console && enable_log ) { 
		console.log( msg );
	}
}

function MapBranding() {
}

MapBranding.prototype = new GControl();

MapBranding.prototype.initialize = function( map ) {
	var container = document.createElement( "div" );
	container.setAttribute( "id", "mapBranding" );

	var img = document.createElement( "img" );
	img.setAttribute( "src", cm_image_root + "galleon.png" );
	img.setAttribute( "height","48" );

	var a = document.createElement( "a" );
	a.setAttribute( "href", cm_site_root )
	a.setAttribute( "target", "_blank" );
	a.setAttribute( "title", "Ahoy matey, click here to get yer own map!" );

	a.appendChild( img );
	container.appendChild( a );
	map.getContainer().appendChild( container );
	return container;
}

MapBranding.prototype.getDefaultPosition = function() {
	return new GControlPosition( G_ANCHOR_TOP_LEFT, new GSize( 10,10 ) );
}

function MapHeader() {
}

MapHeader.prototype = new GControl();

MapHeader.prototype.initialize = function( map ) { 
	var container = document.createElement( "div" );
	container.setAttribute( "id", "mapHeader" );

	var tbl = document.createElement( "table" );
	tbl.setAttribute( "cellpadding", 0 );
	tbl.setAttribute( "cellspacing", 0 );
	tbl.setAttribute( "border", 0 );
	tbl.setAttribute( "style", "height:55px;" );

	var tr = document.createElement( "tr" );
	//var td_img = document.createElement( "td" );
	var td_title = document.createElement( "td" );
	td_title.setAttribute( "class", "title" );
	td_title.setAttribute( "valign", "middle" );
	
	/*
	var img = document.createElement( "img" );
	img.setAttribute( "src", map_header_img );
	img.setAttribute( "align", "left" );
	img.setAttribute( "height", 48 );
	img.setAttribute( "width", 48 );
	*/

	var instructions = document.createElement( "a" );
	instructions.setAttribute( "class", "instructions" );
	instructions.appendChild( document.createTextNode( "Zoom in for a closer look" ) );
	GEvent.addDomListener( instructions, "click", function() {
		map.zoomIn();
	});


	//td_img.appendChild( img );
	td_title.appendChild( document.createTextNode( map_header_title ) );
	td_title.appendChild( document.createElement( "br" ) );
	td_title.appendChild( instructions );
	//tr.appendChild( td_img );
	tr.appendChild( td_title );
	tbl.appendChild( tr );
	container.appendChild( tbl );

	map.getContainer().appendChild( container );
	return container;
}

MapHeader.prototype.getDefaultPosition = function() {
	return new GControlPosition( G_ANCHOR_TOP_LEFT, new GSize( 10,10 ) );
}

MapHeader.prototype.setButtonStyle_ = function( button ) {
	button.style.margin = "0px";
	button.style.pdding = "0px";
}



function PGroup() {
	this.usernames = [];
	this.avatars = [];
	this.length = 0;

	this.push = function( username, avatar ) {
		var len = this.usernames.length;
		this.usernames[ len ] = username;
		this.avatars[ len ] = avatar;
		this.length = this.usernames.length;
	}

	this.htmls = function() { 
		var rtn = [];
		for( var i = 0; i < this.length; i++ ) {
			rtn.push( "<a href='http://twitter.com/" + this.usernames[ i ] + "' target='_blank'>" + this.usernames[ i ] + "</a>" ); 
		}
		return rtn;
	}

	this.html = function() {
		var rtn = "";
		for( var i = 0; i < this.length; i++ ) {
			rtn += "<a href='http://twitter.com/" + this.usernames[ i ] + "' target='_blank'>" + this.usernames[ i ] + "</a>"; 
		}
		return rtn;
	}
}

function PCollection() {
	var pc = this;
	this.map = null;
	this.zoom = null;
	this.groups = [];
	this.overlays = [];
	this.grid = [[]]; // lats then lngs, 2d array
	this.gridsize = 5;
	this.currMax = 0;
	this.latMin = null;
	this.latMax = null;
	this.lngMin = null;
	this.lngMax = null;
	this.gridLatMin = null;
	this.gridLatMax = null;
	this.gridLngMin = null;
	this.gridLngMax = null;

	this.snapToGrid = function( lat, lng ) { 
		// returns [i,j] indicating grid indices
		var gridsize = pc.gridsize;
		var ki = parseInt( 180.0 / parseFloat( gridsize ) );
		var kj = parseInt( 360.0 / parseFloat( gridsize ) );
		while( lat < ( ki * gridsize ) - 90 ) { ki--; } 
		while( lng < ( kj * gridsize ) - 180 ) { kj--; } 
		return [ ki, kj ]; 
	}

	this.registerMap = function( map ) {
		pc.map = map;
		var mapWidth = map.getSize().width;
		if( mapWidth > 500 ) { 
			var brandPos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10,10));
			var titlePos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(80,10));
			var zoomPos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10,48+32));
			var mapTypePos = new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(10,10));
		} else { 
			var brandPos = new GControlPosition(G_ANCHOR_BOTTOM_RIGHT, new GSize(5,20));
			var titlePos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10,10));
			var zoomPos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10,48+22));
			var mapTypePos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(30,48+22));
		}
		pc.map.addControl( new GSmallZoomControl(), zoomPos );
		pc.map.addControl( new MapHeader(), titlePos );
		pc.map.addControl( new MapBranding(), brandPos );
		pc.map.setCenter( new GLatLng(0,0), 1 );
		
		var modLayer = new GTileLayer( new GCopyrightCollection(""), 0, 15 );
		modLayer.getTileUrl =  function(tile, zoom) {
			return cm_image_root + "mod.png";
		}
		modLayer.isPng = function() { return true; }

		var layerTerMod = [ G_PHYSICAL_MAP.getTileLayers()[0], modLayer ];
		var mtTerMod = new GMapType( layerTerMod, G_PHYSICAL_MAP.getProjection(), "Ter+" );
		pc.map.addMapType( mtTerMod );
		pc.map.setMapType( mtTerMod );
	}

	this.init = function( bounds ) { 
		pc.map.setZoom( pc.map.getBoundsZoomLevel( bounds ) );
		pc.map.setCenter( bounds.getCenter() );
		pc.zoom = pc.map.getZoom();
		pc.buildGrid( pc.zoom );
		pc.placeGroups();
		GEvent.addListener( pc.map, "zoomend", function( oldLevel, newLevel ) {
			pc.currMax = 0;
			pc.buildGrid( newLevel );
			pc.placeGroups(); 
			pc.render( true );
		});
		GEvent.addListener( pc.map, "moveend", function() { 
			pc.updateGrid();
			if( pc.placeGroups() ) { 
				pc.render( false );
			}
		});
		GEvent.addListener( pc.map, "maptypechanged", function() { 
			pc.render( true );
		});
	}

	this.push = function( lat, lng, pgroup ) {
		pc.groups.push( { 'lat':lat, 'lng':lng, 'pgroup':pgroup } );
	}

	this.convert = function( min_in, max_in, min_out, max_out, val ) { 
		val = parseFloat( Math.min( max_in, Math.max( min_in, val ) ) )
		return parseInt( parseFloat( val - min_in ) / parseFloat( max_in - min_in ) * ( max_out - min_out ) + min_out )
	}

	this.createMarker = function( point, title, val, html ) { 
		var icon = new GIcon( G_DEFAULT_ICON );
		var sz,mid;
		if( val >= 50 ) { 
			sz = parseInt( Math.sqrt( (1+50) * 100 / Math.PI ) );
			mid = parseInt( sz * .5 );
			icon.image = cm_image_root + color_scheme + "/img_huge.png";
		} else { 	
			sz = parseInt( Math.sqrt( (1+val) * 100 / Math.PI ) );
			mid = parseInt( sz * .5 );
			icon.image = cm_image_root + color_scheme + "/img_" + parseInt( val ) + ".png";
		}
		icon.shadow = "";
		icon.shadowSize = new GSize( 0, 0 );
		icon.iconSize = new GSize( sz, sz ); 
		icon.iconAnchor = new GPoint( mid, mid );
		icon.infoWindowAnchor = new GPoint( mid, mid );	
		icon.imageMap = [ 0,0, sz,0, sz,sz, 0,sz ];

		var markerOpts = {};
		markerOpts.icon = icon;
		markerOpts.title = title;
		markerOpts.zIndexProcess = function() { return 200 - val; }
		var marker = new GMarker( point, markerOpts );

		var maintainOver = false;
		GEvent.addListener( marker, "click", function() { 
			marker.openInfoWindowHtml( "<div class='balloonContainer'><strong>" + title + "</strong><br/>" + html + "</div>" );
			maintainOver = true;
		});
		GEvent.addListener( marker, "mouseover", function() { 
			if( val >= 50 ) { 
				marker.setImage( cm_image_root + color_scheme + "_hover/img_huge.png" );
			} else { 
				marker.setImage( cm_image_root + color_scheme + "_hover/img_" + parseInt( val ) + ".png" );
			}
		});
		GEvent.addListener( marker, "mouseout", function() { 
			if( !maintainOver ) {
				if( val >= 50 ) { 
					marker.setImage( cm_image_root + color_scheme + "/img_huge.png" );
				} else { 
					marker.setImage( cm_image_root + color_scheme + "/img_" + parseInt( val ) + ".png" );
				}
			}
		});
		GEvent.addListener( marker, "infowindowclose", function() {
			if( val >= 50 ) { 
				marker.setImage( cm_image_root + color_scheme + "/img_huge.png" );
			} else { 
				marker.setImage( cm_image_root + color_scheme + "/img_" + parseInt( val ) + ".png" );
			}
			maintainOver = false;
		});

		return marker;
	}

	this.createRect = function( sw, ne, val ) { 
		var gborder = [];
		var nw = new GLatLng( ne.lat(), sw.lng() );
		var se = new GLatLng( sw.lat(), ne.lng() );
		gborder.push( se );
		gborder.push( sw );
		gborder.push( nw );
		gborder.push( ne );
		gborder.push( se );
		var color = "#4466aa";
		if( pc.map.getCurrentMapType() != G_NORMAL_MAP ) {
			var color = "#ffffff";
		}
		var polygon = new GPolygon( gborder, color, 1, .3, color, Math.max( .1, Math.min( val * .1, .70 ) ) );
		return polygon;
	}

	this.boundsToGrid = function( zoomlevel ) { 
		var sw = pc.map.getBounds().getSouthWest();
		var ne = pc.map.getBounds().getNorthEast();
		var sw_snap = pc.snapToGrid( sw.lat(), sw.lng() );
		var ne_snap = pc.snapToGrid( ne.lat(), ne.lng() );
		pc.latMax = ne.lat();
		pc.latMin = sw.lat();
		pc.lngMax = ne.lng();
		pc.lngMin = sw.lng();
		pc.gridLatMax = ne_snap[0] + (2 * pc.gridsize);
		pc.gridLatMin = sw_snap[0];
		pc.gridLngMax = ne_snap[1] + (2 * pc.gridsize);
		pc.gridLngMin = sw_snap[1];
	}

	this.loopGrid = function( cellFn, rowFn ) {
		var idl = ( pc.lngMax < pc.lngMin );
		var idlLngMax = pc.snapToGrid( 0, 180 )[ 1 ]; 
		var idlLngMin = pc.snapToGrid( 0, -180 )[ 1 ]; 
		if( !idl ) {
			for( var i = pc.gridLatMin; i < pc.gridLatMax; i++ ) {
				if( rowFn ) { 
					rowFn( i ); 
				}
				for( var j = pc.gridLngMin; j < pc.gridLngMax; j++ ) {
					cellFn( i, j );
				}
			}
		} else {
			// International Date Line foo
			for( var i = pc.gridLatMin; i < pc.gridLatMax; i++ ) {
				if( rowFn ) { 
					rowFn( i ); 
				}
				for( var j = idlLngMin; j < pc.gridLngMax; j++ ) {
					cellFn( i, j );
				}
				for( var k = pc.gridLngMin; k < idlLngMax; k++ ) {
					cellFn( i, k );
				}
			}
		}
	}

	this.buildGrid = function( zoomlevel ) { 
		pc.grid = [];
		pc.zoom = zoomlevel;
		pc.gridsize = global_scale / Math.pow( 2, pc.zoom ); 
		pc.boundsToGrid();
		pc.loopGrid( function( i, j ) { pc.grid[ i ][ j ] = [ 0, [], false ]; }, function( i ) { pc.grid[ i ] = []; } ); 
	}

	this.updateGrid = function() { 
		// only add new grid components, don't push the reset button
		pc.boundsToGrid();
		pc.loopGrid( function( i, j ) { 
				if( typeof( pc.grid[ i ][ j ] ) != "object" ) {
					pc.grid[ i ][ j ] = [ 0, [], false ]; 
				}
			}, 
			function( i ) { 
				if( typeof( pc.grid[ i ] ) != "object" ) { 
					pc.grid[ i ] = [];
				} 
			} 
		); 
	}

	this.placeGroups = function() { 
		var forceDirty = false;
		var lastCycleMax = pc.currMax;
		for( var k = 0; k < pc.groups.length; k++ ) {
			var lat = pc.groups[ k ].lat;
			var lng = pc.groups[ k ].lng;
			if( !( pc.latMin <= lat && pc.latMax >= lat && 
			       ( ( pc.lngMin <= lng && pc.lngMax >= lng ) 
			         // check for wrap on International Date Line
			         || ( pc.lngMin > pc.lngMax && ( ( pc.lngMin <= lng && lng <= 180 ) || ( pc.lngMax >= lng && lng > -180 ) ) ) 
				  ) 
				) 
			  ) 
			{
				continue;
			} 
			try {
				//only place the group if that grid cell hasn't been rendered
				var snap = pc.snapToGrid( pc.groups[ k ].lat, pc.groups[ k ].lng );
				if( !pc.grid[ snap[0] ][ snap[1] ][ 2 ] ) {  // "rendered" flag
					pc.grid[ snap[0] ][ snap[1] ][ 0 ] += pc.groups[ k ].pgroup.length;
					pc.grid[ snap[0] ][ snap[1] ][ 1 ].push( pc.groups[ k ] );
					pc.currMax = Math.max( pc.grid[ snap[0] ][ snap[1] ][ 0 ], pc.currMax );
				}
			} catch( e ) { log( snap + " ... " + e ); }
		}
		if( lastCycleMax < pc.currMax ) { 
			pc.buildGrid( pc.zoom );
			if( pc.placeGroups() ) {
				pc.render( true );
			}
			return false;
		}
		return true;
	}

	this.clearMarkers = function() { 
		for( var i = 0; i < pc.overlays.length; i++ ) {
			try { 
				pc.map.removeOverlay( pc.overlays[ i ] );
			} catch( e ) { 
				log( e );
			}
		}
		pc.overlays = [];
	}

	this.render = function( clear ) {
		if( clear ) { 
			pc.clearMarkers();
		}
		var point, val, html, marker, rendered;

		pc.loopGrid( function( i, j ) { 
			rendered = pc.grid[ i ][ j ][ 2 ];
			val = pc.grid[ i ][ j ][ 0 ];
			if( val > 0 && ( !rendered || clear ) ) {
				var titleVal = val;
				if( pc.currMax > 50 ) { 
					val = pc.convert( 1, pc.currMax, 1, 50, val );
				} else if( pc.currMax < 30 ) { 
					val = pc.convert( 0, pc.currMax, 1, 30, val );
				}
				if( enable_dots ) { 
					point = new GLatLng( ( i + .5 ) * pc.gridsize - 90, ( j + .5 ) * pc.gridsize - 180 );
					html = pc.htmlFromGroups( pc.grid[ i ][ j ][ 1 ] ); 
					marker = pc.createMarker( point, titleVal.toString() + ( ( titleVal > 1 ) ? " Followers" : " Follower" ), val, html );
					pc.map.addOverlay( marker );
					pc.overlays.push( marker );
				} 
				if( enable_grid ) { 
					var sw = new GLatLng( i * pc.gridsize - 90, j * pc.gridsize - 180 );
					var ne = new GLatLng( (i+1) * pc.gridsize - 90, (j+1) * pc.gridsize - 180 );
					polygon = pc.createRect( sw, ne, val );
					pc.map.addOverlay( polygon );
					pc.overlays.push( polygon );
				}
				pc.grid[ i ][ j ][ 2 ] = true; // set "rendered" flag
			} 
		});
	}

	this.htmlFromGroups = function( groups ) {
		var rtn = "<div class='balloonFollowers' style='height:100px;max-height:100px;overflow:auto;'><ol>";
		for( var i = 0; i < groups.length; i++ ) { 
			var groupHtmls = groups[ i ].pgroup.htmls();
			for( var j = 0; j < groupHtmls.length; j++ ) {
				rtn += "<li>" + groupHtmls[ j ] + "</li>";
			}
		}
		return rtn + "</ul></div>";
	}
}

