var h2s = 3600;

// Global graphs array for cleanup
if (window['allGraphs'] == null) {
	window['allGraphs'] = new Array();
}

// needs to be called on unload event or else memory will leak!
function GraphsUnload() {
	for (var i = 0; i < window['allGraphs'].length; i++) { 
		if (window['allGraphs'][i]) { 
			window['allGraphs'][i].Unload(); 
		}
	}
}

// Event handler constructor
function Event(obj, funct, oldFunct) {
	return function (e) { if (!e) e= window.event; if (oldFunct) oldFunct(e); return obj[funct](e); };
}

// Called when image is loaded
function OnImgLoad() {
	this.parent.ImgLoaded();
}

// Tile class
// Handles the display and position of the tiles in the graph
// Used by the graph class
function Tile(parent, tileID) {
	this.parent = parent;
	this.img    = document.createElement("img");
	this.tileID = tileID;

	// Initialization function
	// Sets up the image properties
	this.InitImg = function () {
		if (this.img.clearAttributes) {
			this.img.clearAttributes();
		}

		if (this.img.style.MozUserSelect) {
			this.img.style.MozUserSelect = "none";
		}
		this.img.parent = this;
		this.img.onload = OnImgLoad;
		this.img.style.position = "absolute";
		this.img.onmousedown = function () { return false; };
		this.img.ondragstart = function () { return false; };
		this.img.onselectstart = function () { return false; };

		this.img.style.width = this.parent.tileWidth + "px";
		//this.img.style.height = this.parent.tileHeight + "px";
		this.img.style.visibility = "hidden";
	}

	this.InitImg();

	this.y    = 0;
	this.time = 0;

	// Creates the URL for the image
	this.MakeUrl = function () {
		var sTime = Math.round(this.time - this.parent.deltaT_SecPix * Math.round(this.parent.tileWidth / 2));// + this.parent.deltaT_SecPix;
		var eTime = Math.round(this.time + this.parent.deltaT_SecPix * Math.round(this.parent.tileWidth / 2));// - this.parent.deltaT_SecPix;

		var url = parent.tileURL;

		url += '?siteid='   + parent.siteID;
		url += '&regionid=' + parent.regionID;
		url += '&hid='      + parent.hid;
		url += '&wid='      + parent.wid;
		url += '&tid='      + parent.tid
		url += '&width='    + this.parent.tileWidth;
		url += '&height='   + this.parent.tileHeight;
		url += '&sTime='    + sTime;
		url += '&eTime='    + eTime;

		this.sTime = sTime;
		this.eTime = eTime;


		return url;
	}

	// Cleans up circular reference
	this.Unload = function () {
		this.img.onload = null;
		this.img.parent = null;
		this.img = null;
	}


	// Sets the time(Seconds GMT) for the center of the block
	this.SetTime = function(t) {
		
		this.sTime = t - this.parent.deltaT_SecPix * Math.round(this.parent.tileWidth / 2);
		this.x = this.parent.TimeToX(this.sTime);

		if (this.time != t) {
			this.time = t;
			this.Refresh();
		}

		this.PositionTile();
	}

	// Position the left x coordinate of the block and adjust the time
	this.SetX = function(x) {
		this.x = x;

		var t = this.parent.XToTime(this.x + Math.round(this.parent.tileWidth / 2));
		if (this.time != t) {
			this.Refresh();
		}

		this.PositionTile();
	}

	// Repositions the tile when the graphs time has changed
	this.ResetTilePos = function() {
		this.x = this.parent.TimeToX(this.sTime);
		this.PositionTile();
	}

	// Moves the tile dX pixels negative to move to the left and positive to move to the right
	this.MoveTile = function (dX) {
		this.SetX(this.x + dX);
	}

	// Refreshes the tile
	this.Refresh = function () {
		this.InitImg();
		this.img.onload = OnImgLoad;
		this.img.src = this.MakeUrl();
	}

	// Sets the top-left and width of the tile
	this.PositionTile = function () {
		this.img.style.width = this.parent.tileWidth + "px";

		this.img.style.left = this.x + "px";
		this.img.style.top = this.y + this.parent.tileY + "px";
	}

	// Called after the image has successfully loaded
	this.ImgLoaded = function() {
		this.y = this.parent.tileHeight - this.img.height;
		this.img.style.visibility = "visible";
		this.PositionTile();
	}
}

// Y Axis class
// Handles the Y axis image
// Used by the Graph class
function YAxis(parent) {
	this.parent = parent;
	this.img  = document.createElement("img");

	// Sets the YAxis image source
	this.SetYAxis = function () {
		this.img.src  = this.parent.tileURL+'?yAxis&height=' + this.parent.yAxisHeight + '&width=' + this.parent.yAxisWidth;
	}

	// Initializes the iamge properties
	this.Init = function () {
		if (this.img.clearAttributes) {
			this.img.clearAttributes();
		}

		this.img.style.MozUserSelect = "none";
		this.img.style.position="absolute";
		this.img.style.top = "0px";
		this.img.style.left = "0px";
		this.img.style.zIndex = "1";
		this.img.style.width = this.parent.yAxisWidth + "px";
		this.img.parent = this;
		this.img.style.visibility = "hidden";
		this.img.onload = OnImgLoad;
		this.SetYAxis();
	}

	this.Init();

	// Add the image to the div
	this.parent.div.appendChild(this.img);

	// Sets the top of the image.  Used for times when
	// the wind speed goes above the max wind speed of the graph.
	// When that happens the tile image will be higher than normal
	// and allow the user to scroll up and down.
	this.PositionTile = function () {
		this.img.style.top = this.y + this.parent.tileY + "px";
	}

	// Called when the page or graph is unloaded.  Cleans up any memory leaks
	this.Unload = function () {
		this.img.onload = null;
		this.img.parent = null;
		this.img = null;
	}

	// Called when the image is loaded
	this.ImgLoaded = function() {
		this.y = this.parent.tileHeight - this.img.height;
		this.img.style.visibility = "visible";
		this.PositionTile();
	}
}

// Scrolling Graph
// Parameters:
// div: the div containing the graph images
// siteID: the ID of the site
// regionID: the ID of the region
// hid: access id
// tid: access id
// wid: the ID of the website
// graphTime: The time of the center of the map
// timespanHours: The number of hours displayed on the graph
function Graph(div, siteID, regionID, hid, tid, wid, graphTime, timespanHours, tileURL) {
	window['allGraphs'].push(this);

	this.tileURL = tileURL;

	this.div     = div;

	this.siteID    = siteID;
	this.regionID  = regionID;
	this.hid       = hid;
	this.tid       = tid;
	this.wid       = wid;
	this.graphTime = graphTime;

	this.timespanHours = timespanHours;

	this.minTimeSpan = 3;
	this.maxTimeSpan = 192;

	this.button  = 0;
	this.mouseX  = 0;
	this.mouseY  = 0;
	this.dX      = 0;
	this.dY      = 0;

	this.tileY   = 0;

	this.tiles   = new Array();

	this.tickEvent;

	this.stepTimerID = 0;

	this.ready = 0;

	//========================================================
	// Begin class methods

	// Clean up the memory allocation and events
	this.Unload = function() {
		for (var i = 0; i < window['allGraphs'].length; i++) {
			if (window['allGraphs'][i] == this) {
				window['allGraphs'][i] = null;
			}
		}

		this.removeEvent(this.div, 'mousedown', this.btnDownEvent);
		this.removeEvent(document, 'mousemove', this.mouseMoveEvent);
		this.removeEvent(document, 'mouseup', this.btnUpEvent);
		this.removeEvent(document, 'mouseout', this.MouseOutEvent);
		this.removeEvent(document, 'mouseover', this.MouseOverEvent);

		for (var i = 0; i < this.tiles.length; i++) {
			this.tiles[i].Unload();
		}

		this.div.ondragstart = null;
		this.div.ondrag = null;
		this.div.onselectstart = null;
		this.div.onmousedown = null;

		this.yAxis.Unload();
	}

	// Universal event handler code
	this.addEvent = function(element, eventName, listener) {
		if (element.addEventListener) {
			element.addEventListener(eventName, listener, false);
		} else {
			element.attachEvent("on"+eventName, listener);
		}
	}

	this.removeEvent = function(element, eventName, listener) {
		if (element.removeEventListener) {
			element.removeEventListener(eventName, listener, false);
		} else {
			element.detachEvent("on"+eventName, listener);
		}
	}

	// Create the tiles and setup the parent div properties
	this.Init = function() {
		if (this.ready) {
			return 0;
		}

		if (this.div.offsetWidth) {
			this.divHeight = this.div.offsetHeight - 2;
			this.divWidth = this.div.offsetWidth - 2;
		} else if (this.div.pixelWidth) {
			this.divHeight = this.div.style.pixelHeight;
			this.divWidth = this.div.style.pixelWidth;
		} else {
			this.divHeight = parseInt(this.div.style.height.replace(/px/, ''));
			this.divWidth = parseInt(this.div.style.width.replace(/px/, ''));
		}

		this.div.style.overflow = "hidden";
		this.div.style.position = "relative";
		this.div.style.fontSize = 20 + "px";
		this.div.style.textAlign = "center";

		//this.div.innerHTML = "<br><br>Loading Images";
		this.div.noWrap = 'true';

		this.div.ondragstart    = function () { return false; };
		this.div.ondrag         = function () { return false; };
		this.div.onselectstart  = function () { return false; };

		this.div.style.cursor = "w-resize";

		// Create and attach the events
		this.btnDownEvent   = new Event(this, 'btnDown');
		this.mouseMoveEvent = new Event(this, 'mouseMove');
		this.btnUpEvent     = new Event(this, 'btnUp');
		this.MouseOutEvent  = new Event(this, 'MouseOut');
		this.MouseOverEvent = new Event(this, 'MouseOver');

		this.addEvent(this.div, 'mousedown', this.btnDownEvent);
		this.addEvent(document, 'mousemove', this.mouseMoveEvent);
		this.addEvent(document, 'mouseup', this.btnUpEvent);
		this.addEvent(document, 'mouseout', this.MouseOutEvent);
		this.addEvent(document, 'mouseover', this.MouseOverEvent);

		// Set the tile width and height
		this.tileWidth   = this.divWidth/2;
		this.tileHeight  = this.divHeight;

		// Tiles need to be divisible by 2 or else a small gap may form between the tiles
		if (this.tileWidth/2 != Math.round(this.tileWidth/2)) {
			this.tileWidth++;
		}

		// Setup the yAxis
		this.yAxisWidth  = 18;
		this.yAxisHeight = this.divHeight;

		// Create the tiles and Y Axis
		this.MakeTiles();

		this.yAxis = new YAxis(this);

		// Dont call init again!
		this.ready = 1;
	}

	// Create the tiles and set the timespan of the graph
	this.MakeTiles = function() {
		var nImg = 4;

		for (var i = 0; i <= nImg; i++) {
			var tile = new Tile(this, i);
			this.tiles[this.tiles.length] = tile;
			this.div.appendChild(tile.img);
		}

		this.SetTimespan(this.timespanHours);
	}

	// Interface: Change the base url and time zoom range of the tiles
	this.SetURL = function (url, minTimespan, maxTimespan) {
		this.tileURL = url;
		this.minTimeSpan = minTimespan;
		this.maxTimeSpan = maxTimespan;

		if (this.timespanHours < this.minTimeSpan) {
			this.timespanHours = this.minTimeSpan;
		}

		if (this.timespanHours > this.maxTimeSpan) {
			this.timespanHours = this.maxTimeSpan;
		}

		this.SetTimespan(this.timespanHours);

		for (var i = 0; i < this.tiles.length; i++) {
			this.tiles[i].Refresh();
		}

		this.yAxis.Init();
	}

	// Sets the timespan of the whole graph
	this.SetTimespan = function (timespanHours) {

		this.timespanHours = timespanHours;
		// Calculate the timespan (seconds) per pixel in the graph
		this.deltaT_SecPix = Math.round((timespanHours * h2s) / this.divWidth); // Timespan in seconds per pixel 
		var blockDeltaT = this.deltaT_SecPix * this.tileWidth;

		var startBlockTime =  this.XToTime(0);
		for (var i = 0; i < this.tiles.length; i++) {
			this.tiles[i].SetTime(startBlockTime + blockDeltaT * i);
			this.tiles[i].Refresh();
		}
	}

	// Map time to div x coordinate and div x coordinate to time
	this.TimeToX = function(t) {
		return Math.round((t - this.graphTime) / this.deltaT_SecPix) + Math.round(this.divWidth/2);
	}

	this.XToTime = function(x) {
		return this.graphTime - (this.divWidth/2 - x) * this.deltaT_SecPix;
	}

	// Move the non-visible tiles to the left or right of the stack depending on the scroll direction
	this.RecycleImages = function () {

		var blockDeltaT = this.deltaT_SecPix * this.tileWidth;

		this.tiles.sort(function(a, b) { return a.time - b.time; });

		if (this.dX > 0) {
			var minTime = this.tiles[0].time;
			for (var i = this.tiles.length-1; i >= 0; i--) {
				if (this.tiles[i].x > this.divWidth) {
					minTime -= blockDeltaT;
					this.tiles[i].SetTime(minTime);
				}
			}
		}

		if (this.dX < 0) {
			var maxTime = this.tiles[this.tiles.length-1].time;
			for (var i = 0; i < this.tiles.length; i++) {
				if (this.tiles[i].x < (-this.tileWidth)) {
					maxTime += blockDeltaT;
					this.tiles[i].SetTime(maxTime);
				}
			}
		}
	}

	// Interface: Refresh all the tiles
	this.RefreshGraph = function () {
		for (var i = 0; i < this.tiles.length; i++) {
			this.tiles[i].Refresh();
		}
	}

	// Interface: Change the current site and region
	this.ChangeSite = function(siteID, regionID)  {
		if (siteID != this.siteID || regionID != this.regionID) {
			this.siteID = siteID;
			this.regionID = regionID;

			// Call Init if we are not setup yet
			if (this.ready == 0)
				this.Init();
			else 
				this.RefreshGraph();
		} 
	}

	// Event handler: Scrolls the graph
	this.MoveGraph = function () {
		this.tileY += this.dY;

		var maxHeight = this.tileHeight;
		for (var i = 0; i < this.tiles.length; i++) {
			if (this.tiles[i].img.height > maxHeight) {
				maxHeight = this.tiles[i].img.height;
			}
		}

		if (this.tileY < 0) {
			this.tileY = 0;
		}

		if (this.tileY > maxHeight - this.tileHeight) {
			this.tileY = maxHeight - this.tileHeight;
		}


		this.graphTime -= this.dX * this.deltaT_SecPix;
		for (var i = 0; i < this.tiles.length; i++) {
			this.tiles[i].ResetTilePos();
		}

		this.yAxis.PositionTile();

		this.RecycleImages();
	};

	// Event handler: Track the mouse events
	this.mouseMove = function (e) {
		if (this.button) {
			this.dX = e.clientX - this.mouseX;
			this.dY = e.clientY - this.mouseY;
			this.mouseX = e.clientX;
			this.mouseY = e.clientY;
			this.MoveGraph();
		}
	}

	this.btnDown = function (e) { 
		this.mouseX = e.clientX;
		this.mouseY = e.clientY;
		this.button = 1; 
		return false;
	};

	this.btnUp = function (e) { 
		this.button = 0;
	};

	this.MouseOut = function (e) {
		if (!e.toElement && !e.currentTarget && this.button==1) {
			this.btnUp(e);
		}
	}

	this.MouseOver = function (e) {
		if (!e.fromElement && !e.relatedTarget && this.button==1) {
			this.btnUp(e);
		}
	}

	// Interface: Scroll the graph 1/2 width forward
	this.Forward = function () {
		this.dX = -30;
		this.nSteps = this.divWidth;

		this.stepTimerID = setTimeout(this.StepEvent, 20);
	}

	// Interface: scroll the graph 1/2 width backward
	this.Backward = function () {
		this.dX = 30;
		this.nSteps = this.divWidth;

		this.stepTimerID = setTimeout(this.StepEvent, 20);
	}

	// Interface: Set the timespan of the graph to cover 1/2 of the time
	this.ZoomIn = function () {
		if (this.timespanHours/2 > this.minTimeSpan) {
			this.SetTimespan(this.timespanHours/2);
		} else {
			this.SetTimespan(this.minTimeSpan);
		}
	}

	// Interface: Set the timespan of the graph to cover 2x of the time
	this.ZoomOut = function () {
		if (this.timespanHours*2 < this.maxTimeSpan) {
			this.SetTimespan(this.timespanHours*2);
		} else {
			this.SetTimespan(this.maxTimeSpan);
		}
	}

	this.StepEvent = new Event(this, 'Step');

	this.Step = function () {

		this.MoveGraph();

		this.nSteps -= Math.abs(this.dX);
		if (this.nSteps > 0) {
			this.stepTimerID = setTimeout(this.StepEvent, 20);
		} else {
			this.stepTimerID = 0;
			this.btnUp();
		}
	}

	// Init the class
	if (this.siteID != 0)
		this.Init();
}


