﻿var DSItemType = {
	Item: 0,
	Separator: 1,
	Header: 2,
	Footer : 3,
	HSeparator : 4
}

var DynamicScroll = Class.extend
({
	// the path of the web service to call
	servicePath: null,

	// a flag indicating whether layout info is populated inside initialize method or by external call
	delayedInit: false,

	// a flag indicating whether layout info is populated in tho phases or by single call.
	fullOnetimeLoad: true,

	// value, indicating whether to use auto-fit functionality
	autoFit: true,

	// right and left margins of the target element relative to the browser window.
	autoFitOffsetWidth: 0,

	// top and bottom margins of the target element relative to the browser window.
	autoFitOffsetHeight: 20,
	fixedHeight: null,

	// minimal width for the target element
	minWidth: 25,

	// minimal height for the target element
	minHeight: 40,

	// value indicating whether target element's size can be greater than size of its content
	allowSpace: true,

	containerId: null,

	// identifier of the current account
	accountId: null,

	// a CSS class that will be applied to every odd item
	oddClass: null,

	// a CSS class that will be applied to every even item
	evenClass: null,

	// value indicating how many extra items will be populated during web service calls
	cacheFactor: 1,

	// Html tag of container that wraps all items. By default it is div.
	containerElementTag: "div",

	// Container element Css class.
	containerElementCss: null,

	// The name of page method which returns dynamic scroll layout information.
	layoutInfoMethodName: "GetLayoutInfo",

	// The name of page method which returns html markup.
	itemsHtmlMethodName: "GetItemsHtml",

	// Indicates when loading message should be hidden.
	hideLoading: false,

	// Indicates when inline styles should be used for generated items.
	useInlineStyles: true,

	// Indicates when overflow of main container should be hidden.
	overflowHidden: false,

	// Contains identifier of scrolling container element.
	scrollContainerId: null,

	// Determines whether item can be taken out when its location is outside of scrolling area.
	canTakeOut: true,

	// Represents additional parameters which are included into each request to server.
	params: '',

	// Script block which allows to show progress indicator.
	showLoadingScript: '',

	// Script block which allows to hide progress indicator.
	hideLoadingScript: '',

	// The layout information of the current page, it has the same properties as LayoutInfo class (see LayoutInfo.cs in this folder).
	layout: null,

	// Object to store size of the browser window. This object is modified only when the browser window has actually changed its size.
	browserWindowSize: null,

	// The offset in pixels from the top of content to the top of loaded area.
	loadedStart: 0,

	// The offset in pixels from the top of content to the bottom of loaded area.
	loadedEnd: 0,

	// List of the items that are currently in the loaded area.
	itemsToDisplay: [],

	// an element where all dynamically loaded items are placed into
	content: null,

	// an element that displays progress and error messages
	progress: null,

	events: null,

	// the default timeout interval for any web-service call
	defaultTimeout: 120000,

	// a value indicating how many progress messages are shown one over another
	progressStack: 0,

	// height of the target element before populating any dynamic content
	initialHeight: 0,

	dimensions: [],

	init: function(config) {
		jQuery.extend(this, config);

		if (this.containerId == null)
			return;

		this.preInit();

		if (!this.layout && !this.delayedInit)
			this.PopulateLayoutInfo();
	},

	get_scroller: function() {
		if (this.scrollContainerId != null) {
			return $('#' + this.scrollContainerId);
		}
		return this.get_container();
	},

	get_container: function() {
		return $('#' + this.containerId);
	},

	get_windowsize: function(control) {
		if (this.browserWindowSize == null)
			this.browserWindowSize = this.get_size(control);

		return this.browserWindowSize;
	},

	get_size: function(control) {
		return { width: control.width(), height: control.height() };
	},

	onScroll: function(event) {
		if (this.scrollTimeout)
			window.clearTimeout(this.scrollTimeout);
		var ds = this;
		this.scrollTimeout = window.setTimeout(function() { ds.onScrollDone(event); }, 200);
	},

	onScrollDone: function(e) {
		this.UpdateScrollingArea();
	},

	onWindowResize: function(evt) {

		if (this.content == null)
			return;

		this.browserWindowSize = null;
		this.ResizeAreas();

		// Firefox quick: TODO: check for new version 3.0, 3.5
		// this.browserWindowSize = null;
		// this.ResizeAreas();

		if (!this.layout)
			return;

		this.PopulateLayoutValues();
		this.UpdateScrollingArea();
	},

	onPopulateLayoutInfoCompleted: function(result) {
		this.layout = result;

		while (this.content.hasChildNodes())
			this.content.removeChild(this.content.lastChild);

		this.GenerateVirtualContent();
		this.PopulateLayoutValues();

		this.HideProgressMessage();

		this.UpdateScrollingArea();
		this.ResizeAreas();

		//this.raiseEvent('populateLayoutComplete', result);
	},

	onGetItemsHtmlCompleted: function(data) {
		var ds = this;
		jQuery.each(data, function() {
			var data = this
			jQuery.each(ds.itemsToDisplay, function() {
				if (this.Id == data.Id && this.Type == data.Type) {
					jQuery.extend(this, data);
					ds.takeinItem(this);
					this.loading = false;
					this.stored = true;
					return false;
				}
			});
		});
		//this.raiseEvent('load', result);
	},

	onError: function(msg, error, response) {
		this.HideProgressMessage();

		if (msg == 'timeout') {
			this.ShowProgressMessage("Loading process has been timed out.", false);
		}
		else {
			var message = response.ExceptionType + ": " + response.Message;
			message += "\nStack Trace:\n" + response.StackTrace;
			message = escape(message);
			message = message.replace(/%/g, "\\x");
			var str = "Server returned an error (" + error + ")&nbsp;&nbsp;<a href='javascript://' onclick=\"alert('" + message + "')\" >View Details</a>";
			this.ShowProgressMessage(str, false);
		}
	},

	preInit: function() {
		if (!this.overflowHidden) {
			this.get_container().css("overflow", "auto");
		}

		this.initialHeight = this.get_scroller().attr("scrollHeight");

		this.content = document.createElement(this.containerElementTag);
		if (this.containerElementCss != null) {
			this.content.className = this.containerElementCss;
		}

		if (this.useInlineStyles == true) {
			$(this.content).css("padding", 0).css("margin", 0).css("zIndex", 1);
		}

		this.progress = document.createElement("div");
		this.progress.className = "inlineProgress";

		this.get_container().append(this.progress);
		this.get_container().append(this.content);

		this.registerEvents();

		this.ResizeAreas();
	},

	registerEvents: function() {
		var ds = this;
		this.get_scroller().bind("scroll.ds", function(event) { ds.onScroll(event); })
		$(window).bind("resize.ds", function(event) { ds.onWindowResize(event); });
	},

	unRegisterEvents: function() {
		$(window).unbind("resize.ds");
	},

	setProgressPosition: function(windowSize) {

		var progressWidth = this.elementSize.width - 20;

		if (progressWidth > windowSize.width - 50)
			progressWidth = windowSize.width - 50;
		if (progressWidth < 0)
			progressWidth = 0;

		$(this.progress).css("height", 23).css("width", progressWidth + "px");

		var temp = this.get_container()[0];

		var x = 0;
		var y = 0;

		if (this.progress.offsetParent != temp) {
			x += temp.offsetLeft;
			y += temp.offsetTop;
			while (temp.offsetParent && (temp.offsetParent != this.progress.offsetParent)) {
				temp = temp.offsetParent;
				x += temp.offsetLeft;
				y += temp.offsetTop;
			}
		}

		$(this.progress).css("top", y + "px");
		$(this.progress).css("left", x + "px");
	},

	generateStubElement: function(item) {
		var element = document.createElement(this.dimensions[item.Type].tag);
		element.className = this.dimensions[item.Type].className;
		element.ID = "T" + item.Type + "_I" + item.Id;
		item.elementRef = element;
	},

	takeinItem: function(item) {
		if (item.Type != DSItemType.HSeparator) {
			$(item.elementRef).setTemplate(this.dimensions[item.Type].template);
			$(item.elementRef).processTemplate(item);
		}
		item.shown = true;
		//this.raiseEvent('refreshItem', item);
	},

	takeoutItem: function(item) {
		if (item.Type == DSItemType.HSeparator)
			return;
		item.elementRef.innerHTML = this.dimensions[item.Type].incomplete;
		item.shown = false;
	},

	raiseEvent: function(eventName, eventArgs) {
		//var handler = this.getEvents().getHandler(eventName);

		//if (handler) {
		//	if (!eventArgs) {
		//		eventArgs = Sys.EventArgs.Empty;
		//	}
		//	handler(this, eventArgs);
		//}
	},

	ShowProgressMessage: function(text, cancellable) {
		if (this.showLoadingScript != '' && this.showLoadingScript != null && cancellable == true)
			eval(this.showLoadingScript);
		else {
			this.progress.innerHTML = "<span>" + text + "</span>";
			this.progress.style.visibility = "visible";
		}
		if (cancellable)
			this.progressStack++;
	},

	HideProgressMessage: function() {
		this.progressStack--;
		if (this.progressStack <= 0) {
			this.progressStack = 0;
			this.progress.style.visibility = "hidden";

			if (this.hideLoadingScript != '' && this.hideLoadingScript != null)
				eval(this.hideLoadingScript);
		}
	},

	Clear: function() {
		this.unRegisterEvents();
		this.initialHeight = 0;
		this.content = null;
		this.progress = null;
		this.get_container().css("height", null);
		this.get_container().empty();
	},

	Reload: function() {
		this.preInit();
		this.PopulateLayoutInfo();
	},

	Refresh: function() {
		this.PopulateLayoutInfo();
	},

	ResizeAreas: function() {
		var windowSize = this.get_windowsize($(window));
		this.elementSize = this.get_size(this.get_container());

		if (this.autoFit || this.fixedHeight != null) {
			var height = this.elementSize.height;
			var width = this.elementSize.width;

			if (this.autoFitOffsetWidth > 0)
				width = windowSize.width - this.autoFitOffsetWidth;

			if (this.autoFitOffsetHeight > 0)
				height = windowSize.height - this.autoFitOffsetHeight;

			if (this.content.offsetHeight < height && !this.allowSpace)
				height = this.content.offsetHeight;

			if (this.minHeight > height)
				height = this.minHeight;
			if (this.minWidth > width)
				width = this.minWidth;

			if (this.fixedHeight != null)
				height = this.fixedHeight;

			if (this.elementSize.width != width)
				this.get_container().css("width", width + "px");
			if (this.elementSize.height != height)
				this.get_container().css("height", height + "px");

			this.elementSize = { "height": height, "width": width };
		}

		this.setProgressPosition(windowSize);
	},

	UpdateScrollingArea: function() {
		var ds = this;
		if (!this.layout)
			return;

		if (this.loadedStart == 0 && this.loadedEnd == 0)
			this.elementSize = this.get_size(this.get_container());

		this.elementSize = this.get_size(this.get_scroller());
		var scrollTop = this.get_scroller().attr("scrollTop");

		if (this.loadedStart >= scrollTop || this.loadedEnd <= scrollTop + this.elementSize.height) {
			this.loadedStart = scrollTop - ((this.cacheFactor - 1) / 2.0) * this.elementSize.height;
			if (this.loadedStart < 0)
				this.loadedStart = 0;
			this.loadedEnd = this.loadedStart + this.cacheFactor * this.elementSize.height;
		}

		this.itemsToDisplay = [];

		jQuery.each(this.layout, function() {
			if (this.Type == DSItemType.HSeparator)
				return true;

			if (this.block.top < ds.loadedEnd && this.block.bottom >= ds.loadedStart) {
				if (this.block.top <= ds.loadedStart)
					ds.loadedStart = this.block.top;

				if (this.block.bottom >= ds.loadedEnd)
					ds.loadedEnd = this.block.bottom;

				$.merge(ds.itemsToDisplay, [this]);
			}
			else {
				if (this.elementRef && this.shown && ds.canTakeOut && this.Type == DSItemType.Item)
					ds.takeoutItem(this);

				if (this.loading)
					this.loading = false;
			}
		});

		this.GetItemsHtml();
	},

	PopulateLayoutValues: function() {
		var ds = this;

		var baseTop = ds.content.offsetTop - ds.initialHeight;
		var baseLeft = ds.content.offsetLeft;

		if (baseTop < 0) {
			baseTop = 0;
		}

		jQuery.each(this.layout, function() {
			if (this.elementRef) {
				this.block = {
					top: this.elementRef.offsetTop - baseTop,
					bottom: this.elementRef.offsetTop - baseTop + this.elementRef.offsetHeight
				};

				if (this.ParentId != null && this.elementRef.offsetParent != null) {
					this.top += this.elementRef.offsetParent.offsetTop;
					this.bottom += this.elementRef.offsetParent.offsetTop;
				}

				if (this.block.bottom < 0) {
					this.block.bottom = 0;
				}
			}
		});
	},

	GenerateVirtualContent: function() {
		if (this.layout == null)
			return;
		var ds = this;

		jQuery.each(this.layout, function() {
			if (!this.elementRef) {
				ds.generateStubElement(this);
				ds.takeoutItem(this);
				this.loading = false;
				if (ds.fullOnetimeLoad)
					this.stored = true;

				// VTS: add event odd classname set class name for odd/even items if provided
				// VTS: append style
				//var className = i % 2 ? ds.evenClass : ds.oddClass;
				//if (className)
				//	this.elementRef.className = className;

				var parentItem = null;
				if (this.ParentId != null) {
					//var parentItem = this.FindParent(this.layout[i].ParentId);
				}

				if (parentItem == null)
					ds.content.appendChild(this.elementRef);
				else
					parentItem.elementRef.appendChild(this.elementRef);
			}
		});

		// Restore scroll offset of the target element if it was saved.
		// We can restore scroll offset only after stub elements are added to
		// the content element, there are not enought height of the content
		// element earlier, and scroll offset could be not set.
		//if (window.__offset)
		//	this.getElement().scrollTop = window.__offset;
	},

	GetItemsHtml: function() {
		var ds = this;
		var itemsToLoad = [];
		var itemsToDisplay = [];

		jQuery.each(this.itemsToDisplay, function() {
			if (this.shown || this.loading)
				return true;
			if (this.stored) {
				$.merge(itemsToDisplay, [this]);
				return true;
			}
			$.merge(itemsToLoad, [{ 'Id': this.Id, 'Type': this.Type}]);
			this.loading = true;
		});

		if (itemsToLoad.length > 0 && this.servicePath) {
			var proxy = new JsonProxy();

			proxy.load({ url: ds.servicePath + ds.itemsHtmlMethodName,
				params: { "accountId": ds.accountId, "parameters": { 'itemsToLoad': itemsToLoad} },
				success: function(data, textStatus) {
					ds.onGetItemsHtmlCompleted(data.d)
				},
				error: function(msg, error, full) {
					ds.onError(msg, error, full)
				},
				timeout: ds.defaultTimeout
			});
		}

		jQuery.each(itemsToDisplay, function() {
			ds.takeinItem(this);
		});

		//if (itemsToLoad.length == 0) 
		//	this.raiseEvent('refresh', Sys.EventArgs.Empty);
	},

	PopulateLayoutInfo: function() {
		if (this.servicePath) {
			if (this.hideLoading == false) {
				this.ShowProgressMessage("Loading...", true);
			}
			var proxy = new JsonProxy();
			var ds = this;

			proxy.load({ url: ds.servicePath + ds.layoutInfoMethodName,
				params: { "accountId": ds.accountId, "parameters": ds.params },
				success: function(data, textStatus) {
					if (ds.content == null) {
						return;
					}
					ds.onPopulateLayoutInfoCompleted(data.d)
				},
				error: function(msg, error, full) {
					ds.onError(msg, error, full)
				},
				timeout: ds.defaultTimeout
			});
		}
	}
})
