
//
// TODO: Use WebGL and instancing for quicker renders
//


TimelineWindow = (function()
{
	var BORDER = 10;

	var ROW_START_SIZE = 210;

	var ROW_END_SIZE = 20;  // make room for scrollbar

	var box_template = "<div class='TimelineBox'></div>";

	function TimelineWindow(wm, settings, server, check_handler)
	{
		this.Settings = settings;

		// Ordered list of thread rows on the timeline
		this.ThreadRows = [ ];

		// Create window and containers
		this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100);
		this.Window.ShowNoAnim();
		this.TimelineContainer = this.Window.AddControlNew(new WM.Container(10, 10, 800, 160));
		DOM.Node.AddClass(this.TimelineContainer.Node, "TimelineContainer");

		var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
		DOM.Event.AddHandler(this.TimelineContainer.Node, mouse_wheel_event, Bind(OnMouseScroll, this));

		// Setup timeline manipulation
		this.MouseDown = false;
		this.LastMouseState = null;
		this.TimelineMoved = false;
		this.OnHoverHandler = null;
		this.OnSelectedHandler = null;
		DOM.Event.AddHandler(this.TimelineContainer.Node, "mousedown", Bind(OnMouseDown, this));
		DOM.Event.AddHandler(this.TimelineContainer.Node, "mouseup", Bind(OnMouseUp, this));
		DOM.Event.AddHandler(this.TimelineContainer.Node, "mousemove", Bind(OnMouseMove, this));		

		// Set time range AFTER the window has been created, as it uses the window to determine pixel coverage
		this.TimeRange = new PixelTimeRange(0, 200 * 1000, RowWidth(this));

		this.CheckHandler = check_handler;

		this.Window.SetOnResize(Bind(OnUserResize, this));
	}


	TimelineWindow.prototype.SetOnHover = function(handler)
	{
		this.OnHoverHandler = handler;
	}


	TimelineWindow.prototype.SetOnSelected = function(handler)
	{
		this.OnSelectedHandler = handler;
	}

	TimelineWindow.prototype.WindowResized = function(width, height, top_window)
	{
		// Resize window
		var top = top_window.Position[1] + top_window.Size[1] + 10;
		this.Window.SetPosition(10, top);
		this.Window.SetSize(width - 2 * 10, 260);

		ResizeInternals(this);
	}


	TimelineWindow.prototype.ResetTimeRange = function()
	{
		this.TimeRange.SetStart(0);
	}


	TimelineWindow.prototype.OnSamples = function(thread_name, frame_history)
	{
		// Shift the timeline to the last entry on this thread
		// As multiple threads come through here with different end frames, only do this for the latest
		var last_frame = frame_history[frame_history.length - 1];
		if (last_frame.EndTime_us > this.TimeRange.End_us)
			this.TimeRange.SetEnd(last_frame.EndTime_us);

		// Search for the index of this thread
		var thread_index = -1;
		for (var i in this.ThreadRows)
		{
			if (this.ThreadRows[i].Name == thread_name)
			{
				thread_index = i;
				break;
			}
		}

		// If this thread has not been seen before, add a new row to the list and re-sort
		if (thread_index == -1)
		{
			var row = new TimelineRow(thread_name, RowWidth(this), this.TimelineContainer.Node, frame_history, this.CheckHandler);
			this.ThreadRows.push(row);
			this.ThreadRows.sort(function(a, b) { return b.Name.localeCompare(a.Name); });
		}
	}


	TimelineWindow.prototype.DrawAllRows = function()
	{
		var time_range = this.TimeRange;
		var draw_text = this.Settings.IsPaused;
		for (var i in this.ThreadRows)
		{
			var thread_row = this.ThreadRows[i];
			thread_row.SetVisibleFrames(time_range);
			thread_row.Draw(draw_text);
		}
	}


	function RowXOffset(self)
	{
		// Add sizing of the label
		// TODO: Use computed size
		return DOM.Node.GetPosition(self.TimelineContainer.Node)[0] + ROW_START_SIZE;
	}


	function RowWidth(self)
	{
		// Subtract sizing of the label
		// TODO: Use computed size
		return self.TimelineContainer.Size[0] - (ROW_START_SIZE + ROW_END_SIZE);
	}

	function OnUserResize(self, evt)
	{
		ResizeInternals(self);
	}

	function ResizeInternals(self)
	{
		// Resize controls
		var parent_size = self.Window.Size;
		self.TimelineContainer.SetPosition(BORDER, 10);
		self.TimelineContainer.SetSize(parent_size[0] - 2 * BORDER, parent_size[1] - 40);

		// Resize rows
		var row_width = RowWidth(self);
		for (var i in self.ThreadRows)
		{
			var row = self.ThreadRows[i];
			row.SetSize(row_width);
		}

		// Adjust time range to new width
		self.TimeRange.SetPixelSpan(row_width);
		self.DrawAllRows();
	}


	function OnMouseScroll(self, evt)
	{
		var mouse_state = new Mouse.State(evt);
		var scale = 1.11;
			if (mouse_state.WheelDelta > 0)
				scale = 1 / scale;

		// What time is the mouse hovering over?
		var x = mouse_state.Position[0] - RowXOffset(self);
		var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel;

		// Calculate start time relative to the mouse hover position
		var time_start_us = self.TimeRange.Start_us - time_us;

		// Scale and offset back to the hover time
		self.TimeRange.Set(time_start_us * scale + time_us, self.TimeRange.Span_us * scale);
		self.DrawAllRows();

		// Prevent vertical scrolling on mouse-wheel
		DOM.Event.StopDefaultAction(evt);
	}


	function OnMouseDown(self, evt)
	{
		// Only manipulate the timelime when paused
		if (!self.Settings.IsPaused)
			return;

		self.MouseDown = true;
		self.LastMouseState = new Mouse.State(evt);
		self.TimelineMoved = false;
		DOM.Event.StopDefaultAction(evt);
	}


	function OnMouseUp(self, evt)
	{
		// Only manipulate the timelime when paused
		if (!self.Settings.IsPaused)
			return;

		var mouse_state = new Mouse.State(evt);

		self.MouseDown = false;

		if (!self.TimelineMoved)
		{
			// Search for the row being clicked and update its selection
			var row_node = DOM.Event.GetNode(evt);
			for (var i in self.ThreadRows)
			{
				var thread_row = self.ThreadRows[i];
				if (thread_row.CanvasNode == row_node)
				{
					var select = thread_row.UpdateSelectedSample(mouse_state, RowXOffset(self));

					// Call any selection handlers
					if (self.OnSelectedHandler)
						self.OnSelectedHandler(thread_row.Name, select);

					break;
				}
			}
		}
	}


	function OnMouseMove(self, evt)
	{
		// Only manipulate the timelime when paused
		if (!self.Settings.IsPaused)
			return;

		var mouse_state = new Mouse.State(evt);

		if (self.MouseDown)
		{
			// Get the time the mouse is over
			var x = mouse_state.Position[0] - RowXOffset(self);
			var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel;

			// Shift the visible time range with mouse movement
			var time_offset_us = (mouse_state.Position[0] - self.LastMouseState.Position[0]) / self.TimeRange.usPerPixel;
			if (time_offset_us)
			{
				self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us);
				self.DrawAllRows();
				self.TimelineMoved = true;
			}
		}

		else
		{
			// Highlight any samples the mouse moves over
			var row_node = DOM.Event.GetNode(evt);
			for (var i in self.ThreadRows)
			{
				var thread_row = self.ThreadRows[i];
				if (thread_row.CanvasNode == row_node)
				{
					var hover = thread_row.UpdateHoverSample(mouse_state, RowXOffset(self));

					if (self.OnHoverHandler)
						self.OnHoverHandler(thread_row.Name, hover);
				}
				else
				{
					thread_row.SetHoverSample(null, 0);
					if (self.OnHoverHandler)
						self.OnHoverHandler(thread_row.Name, null);
				}
			}
		}

		self.LastMouseState = mouse_state;
	}


	return TimelineWindow;
})();

