/// <reference name="MicrosoftAjax.js" />
/// <reference assembly="ESRI.ArcGIS.ADF.Web.UI.WebControls" name="ESRI.ArcGIS.ADF.Web.UI.WebControls.Runtime.JavaScript.ESRI.ADF.UI.Map.js"/>

Type.registerNamespace('ADF.Samples');

ADF.Samples._ScalebarUnits = function() {
	/// <summary>Private unit type class</summary>
};

ADF.Samples._ScalebarUnits.prototype = {
	DecimalDegrees : { "unitsPerMeter": -1, "shortName":'°' },
	Inches : { "unitsPerMeter": 0.0254, "shortName":'"' },
	Feet : { "unitsPerMeter": 0.3048, "shortName":'ft' } ,
	Yards : { "unitsPerMeter": 0.9144, "shortName":'yrd' },
	Miles : { "unitsPerMeter": 1609.344, "shortName":'miles' },
	NauticalMiles : { "unitsPerMeter": 1852, "shortName":'nm' },
	Millimeters : { "unitsPerMeter": 0.001, "shortName":'mm' },
	Centimeters : { "unitsPerMeter": 0.01, "shortName":'cm' },
	Decimeters : { "unitsPerMeter": 0.1, "shortName":'dm' },
	Meters : { "unitsPerMeter": 1, "shortName":'m' },
	Kilometers : { "unitsPerMeter": 1000, "shortName":'km' },
	getMetricUnits : function() {
		/// <summary>Gets all metric units ordered by unit size.</summary>
		/// <returns type="Array" elementType="Object">Metric units</returns>
		return [ this.Millimeters,this.Centimeters,this.Decimeters,this.Meters,this.Kilometers];
	},
	getUSUnits : function() {
		/// <summary>Gets all US units ordered by unit size.</summary>
		/// <returns type="Array" elementType="Object">US units</returns>
		return [ this.Inches,this.Feet,this.Yards,this.Miles ];
	},
	convert : function(value,from,to) {
		/// <summary>Converts a unit to another unit</summary>
		/// <param name="value" type="Number">Value to convert</param>
		/// <param name="from" type="Object">Unit of value (type of ADF.Samples.ScaleBar.Units.*)</param>
		/// <param name="to" type="Object">Unit to convert to (type of ADF.Samples.ScaleBar.Units.*)</param>
		/// <returns type="Number">Converted value</returns>
		if(from === ADF.Samples.ScalebarUnits.DecimalDegrees) { throw Error.argument("Cannot convert from geographic", "from"); }
		if(to === ADF.Samples.ScalebarUnits.DecimalDegrees) { throw Error.argument("Cannot convert to geographic", "to"); }
		return value * from.unitsPerMeter / to.unitsPerMeter;
	}
};
ADF.Samples.ScalebarUnits = new ADF.Samples._ScalebarUnits(); //Static public instance

ADF.Samples.DhtmlScaleBar = function(element) {
	/// <summary>
	/// Creates a scale bar using dynamic HTML to adjust its size
	/// </summary>
	ADF.Samples.DhtmlScaleBar.initializeBase(this, [element]);
	//Set default values
	this._earthRadius = 6378137; //Earth radius in meters (defaults to WGS84 / GRS80
	this._toRadians = Math.PI/180;
	this._degreeDist = this._earthRadius * this._toRadians; // distance of 1 degree at equator in meters
	this._width = null;
	this._map = null;
	this._displayUnit = ADF.Samples.ScalebarUnits.Kilometers;
	this._mapUnit = null;
	this._height = null;
	this._barcolor = 'black';
	this._currentBarWidth = null;
};
ADF.Samples.DhtmlScaleBar.prototype = {
	initialize : function() {
		/// <summary>Initialization. Creates the bar and hooks up event listeners</summary>
		ADF.Samples.DhtmlScaleBar.callBaseMethod(this, 'initialize');
		//Map and map units must be set
		if(this._map === null) { throw Error.argumentNull('map'); }
		if(this._mapUnit === null) { throw Error.argumentNull('mapUnit'); }
		if(this._width === null)  {
			this._width = parseInt(this.get_element().clientWidth,10);
		}
		if(this._height === null)  {
			var tmp = this.get_element().style.fontSize;
			this.get_element().style.fontSize = '0'; //measure height without font affecting it
			this._height = parseInt(this.get_element().clientHeight,10);
			if(this._height % 2 === 1) { this._height++; } //we need the height as a multiple of 2 to look good
			this.get_element().style.fontSize = tmp;
		}
		this._createBars();
		// Create and hook up event listeners to the map
		this._extentChangedHandler = Function.createDelegate(this,this._updateScalebar);
		this._map.add_extentChanging(this._extentChangedHandler); //Add listener for when the map is animating its extent
		this._map.add_extentChanged(this._extentChangedHandler); //Add listener for when the map has changed its extent
		this._updateScalebar(this._map); //Update the scalebar now
	},
	dispose : function() {
		/// <summary>Clean up</summary>
		if(this._extentChangedHandler && this._map) { 
			this._map.remove_extentChanging(this._extentChangedHandler);
			this._map.remove_extentChanged(this._extentChangedHandler);
		}
		this._extentChangedHandler = null;
		this._map = null;
		this._textbars = null;
		this._mainbar = null;
		this.get_element().innerHTML = '';
		ADF.Samples.DhtmlScaleBar.callBaseMethod(this, 'dispose');
	},
	_createBars : function() {
	    /// <summary>
	    /// This method renders the divs used to represent the bar and its and tick marks.
	    /// Basically we just create a set of divs with alternating color, and some text placeholders
	    /// for the scale values. Everything is positioned and scaled using percentages, so changing the
	    /// element size will automatically resize and reposition all the divs.
	    /// </summary>
		if(Sys.Browser.agent !== Sys.Browser.InternetExplorer) {
			//IE uses a slightly different overflow behavior than FireFox
			this.get_element().style.overflow='visible';
		}
		this.get_element().style.width = this._width + 'px';
		
		// Create the bars and text in the scalebar using a set of divs and spans
		this._mainbar = document.createElement('div'); //Holds all contents
		this._mainbar.style.width = '100%';
		this._mainbar.style.position = 'relative';
		this._mainbar.fontSize = '10px';
		
		//Create text placeholders
		var textbar = document.createElement('div');
		textbar.style.width = '100%';
		textbar.style.position = 'absolute';
		textbar.style.left = '-2px';
		textbar.style.top = (this._height + 2) + 'px';
		var arr = [0,12.5,25,50,75,100];
		this._textbars = [];
		
		Array.forEach(arr,function(val,idx) {
			var bar = document.createElement('div');
			bar.style.position = 'absolute';
			bar.style.left = val + '%';
			textbar.appendChild(bar);
			this._textbars[idx] = bar;}, this);
		
		this._textbars[0].innerHTML = '0';
		this._mainbar.appendChild(textbar);
		
		//Create bar marks
		arr = [6.25,6.25,6.25,6.25,25,25,25];
		var marks = document.createElement('div');
		marks.style.width = '100%';
		marks.style.padding = '0';
		marks.style.margin = '0';
		marks.style.fontSize = '0';
		//Top half
		var top = document.createElement('div');
		var height = this._height/2 + 'px';
		top.style.width = '100%';
		top.style.height = height;
		top.style.position = 'relative';
		top.style.overflow = 'hidden';
		var left = 0;
		Array.forEach(arr,function(val,idx) {
			var bar = document.createElement('div');
			bar.style.width = val + '%';
			bar.style.height = height;
			bar.style.position = 'absolute';
			bar.style.left = left + '%';
			left += val;
			if(idx%2 === 0) { bar.style.backgroundColor = this._barcolor; } //alternate background
			top.appendChild(bar);
		},this);
		marks.appendChild(top);
		//Bottom half
		var bottom = document.createElement('div');
		bottom.style.width = '100%';
		bottom.style.height = height;
		bottom.style.overflow = 'hidden';
		top.style.position = 'relative';
		left = 0;
		Array.forEach(arr,function(val,idx) {
			var bar = document.createElement('div');
			bar.style.width = val + '%';
			bar.style.height = height;
			bar.style.position = 'absolute';
			bar.style.left = left + '%';
			left += val;
			if(idx%2 == 1) { bar.style.backgroundColor = this._barcolor; } //alternate background
			bottom.appendChild(bar);
		},this);
		marks.appendChild(bottom);
		this._mainbar.appendChild(marks);
		this.get_element().appendChild(this._mainbar);
	},
	_getScaleForGeographic : function(extent,pxSize) {
		/// <summary>
		/// Calculates horizontal scale at center of extent
		/// for geographic / Plate Carrée projection.
		/// Horizontal scale is 0 at the poles.
		/// </summary>
		var center = extent.get_center();
		var y = center.get_y();
		if(Math.abs(y)>90) { return 0; }
		var ps = Math.cos(y*this._toRadians)*pxSize*this._degreeDist;
		return ps;
	},
	_updateScalebar : function(sender) {
		/// <summary>
		/// This method is called every time the map changes its extent and will update
		/// the scale bar width and values if necessary.
		/// </summary>
		/// <remarks>The code tries to find some nice rounded values and 
		/// tries to avoid large fractions</remarks>
		if(!sender) { return; } //The sender will be the map
		var ps = sender.get_pixelSize();
		if(this._mapUnit === ADF.Samples.ScalebarUnits.DecimalDegrees) {
			ps = this._getScaleForGeographic(sender.get_extent(),ps);
			ps = ADF.Samples.ScalebarUnits.convert(ps, ADF.Samples.ScalebarUnits.Meters, this._displayUnit);
		}
		else { ps = ADF.Samples.ScalebarUnits.convert(ps, this._mapUnit, this._displayUnit); }
		if(this._currentPixelSize === ps) {
			return;
		}
		this._currentPixelSize = ps;
		
		var maxWidth = this._width;
		var minWidth = maxWidth/2;
		var stepwidth = maxWidth / 8; //width of one sub-bar
		var val = stepwidth * ps; //width per bar unrounded

		if(val<1) {
			//create nice fractions
			var tmp = Math.ceil(1/val);
			if(tmp<5) { val = 1/tmp; }
			else if(tmp<4) { val = 0.2; }
			else if(tmp<2) { val = 0.25; }
			else if(tmp<=1) { val = 0.5; }
		}
		else {
			val = Math.floor(stepwidth * ps); //rounded width in map units
		}
		var width = val / ps * 8;
		if(width>0) { this.get_element().style.width = Math.round(width) + 'px'; }
		else { this.get_element().style.width = maxWidth + 'px'; }
		this._currentBarWidth = val*8;
		var vals = [1,2,4,6,8];
		Array.forEach(vals,function(item,j) {
			var rounded = val*item;
			var elm = this._textbars[j+1];
			if(rounded<1 && j<4) {
				if(rounded == 0.5) { rounded = '&frac12;'; }
				else if(rounded == 0.25) { rounded = '&frac14;'; }
				else if(rounded == 0.75) { rounded = '&frac34;'; }
				else if (rounded.toString().length>4) { rounded = Math.round(rounded  * Math.pow(10,j))/ Math.pow(10,j); }
			}
			else if (rounded.toString().length>4) { rounded = Math.round(rounded * Math.pow(10,j))/ Math.pow(10,j); } //add more digits to last values
			if(rounded===0) { elm.innerHTML = ''; }
			else  {
				if(j===4) { elm.innerHTML = '<nobr>'+rounded + ' '+ this._displayUnit.shortName +'</nobr>'; }
				else { elm.innerHTML = rounded; }
			}
		},this);
		var handler = this.get_events().getHandler('scaleChanged');
		if (handler) { handler(this, this._currentBarWidth); }
	},
	get_scaleBarWidth : function() {
		/// <value type="Number">Gets the current width of the scale bar in the current map unit.</value>
		return this._currentBarWidth;
	},
	get_map : function() { 
		/// <value type="ESRI.ADF.UI.MapBase">Gets or sets the map used for the scalebar.</value>
		/// <remarks>This value must be set prior to initialization.</remarks>
		return this._map;
	},
	set_map : function(value) { this._map = value; },
	get_mapUnit : function() { 
		/// <value type="ADF.Samples.ScaleBar.Units">Gets or sets the unit of the coordinate system used by the map.</value>
		return this._mapUnit;
	},
	set_mapUnit : function(value) { this._mapUnit = value; },
	get_displayUnit : function() {
		/// <value type="ADF.Samples.ScaleBar.Units">Gets or sets the display unit for the scalebar.</value>	
		/// <remarks>The display unit cannot be DecimalDegrees.</remarks>
		return this._displayUnit;
	},
	set_displayUnit : function(value) {
		if(this._displayUnit != value) {
			if(value === ADF.Samples.ScalebarUnits.DecimalDegrees) {
				throw Error.argument("displayUnit","DecimalDegrees is not a valid display unit");
			}
			this._displayUnit = value;
			if(this._map) { this._updateScalebar(this._map); }
		}
	},
	get_earthRadius : function() { 
		/// <value type="Number" integer="false">
		/// Gets or sets the earth radius used for converting geographic coordinates
		/// to distances.</value>	
		/// <remarks>Defaults to 6378137 meters (WGS84 / GRS1980).</remarks>
		return this._earthRadius;
	},
	set_earthRadius : function(value) { 
		this._earthRadius = value;
		this._degreeDist = value*this._toRadians;
	},
	get_barcolor : function() { 
		/// <value type="String">Gets or sets the color of the scalebar.</value>	
		/// <remarks>This value must be set prior to initialization to have any effect.</remarks>
		return this._barcolor;
	},
	set_barcolor : function(value) { this._barcolor = value; },
	add_scaleChanged : function(handler) {
		/// <summary>Event raised when the scalebar value changed.</summary>
		this.get_events().addHandler('scaleChanged', handler);
	},
	remove_scaleChanged : function(handler) { this.get_events().removeHandler('scaleChanged', handler); }
};

ADF.Samples.DhtmlScaleBar.registerClass('ADF.Samples.DhtmlScaleBar', Sys.UI.Control);
	