/*
	layout3col.js - CopyLeft 2011 Greg Macdonald, greg@ggmac.com
*/


/*
 * The LayoutManager is designed for a page that has a header, one or more columns, and a footer.  The primary goal is to ensure the relative positioning of these primary layout elements, to support liquid columns, and to keep the footer at the bottom of the window when the browser window is taller than the page content. The manager also supports the absence of a header or footer, and a varialbe numebr of columns, including zero columns.
 *
 * Most of the elements are optional, but the pageContainer element is required. Also, the user may change element identifiers and constraint values after instantiating the LayoutManager; however, they should not be changed after the onBodyLoad method is invoked. If present, the header may contain a logo to the left with a banner area to the right of the logo.  The logo and banner elements must be enclosed in the header element.
 *
 * The basic html element layout should look like this:
 *		Header:
 *			Logo | Banner
 *		Page container
 *			Column 1 | Column 2 ...
 *		Footer
 */
function LayoutManager(arrayOfColumnIDs, hasNavBar) {
//	gmDebug.enable(true);
//	gmDebug.clear();

	this.columns = arrayOfColumnIDs;	// optional, if specified is an array of div element identifiers
	this.navCol = null;					// optional element
	this.footer = null;					// optional element

	// TODO: verify arrayOfColumnsIDs is really an array and not an object with properties
	if (this.columns && this.columns.length > 1) {
		gmDebug.log("LayoutManager(" + gmDebug.thingToString(this.columns, "arrayOfColumnIDs") + ")");
	}

	// Default identifiers of standard layout elements
	this.elementID = {
		header			: 'header',
		logo			: 'logo',
		banner			: 'banner',
		pageContainer	: 'pageContainer',
		navCol			: 'navCol',
		pageContent		: 'pageContent',
		footer			: 'footer'
	};

	/*	Constraints are set in code instead of style sheets because of the difficulty in retrieving an element's real
		style settings depending on how they are set. The property appears to be set "on" the element if it was set
		using inline css; but, if a css class is used, or the style is inherited, then it is just too much trouble to
		accurately retrieve. May be modified by user. */
	this.config = {
		bannerMinWidth: 400,
		bannerMinHeight: 200,
		columnMinWidth: 200,
		normalizeColumns: true,		// Force columns to height of tallest column
		fillContentHeight: true,	// Force column heights to fill any white space above the footer
		columnWidthHack: 12,		// Baaaad!  accounts for padding and the border between columns
		columnBorderStyle: "thin ridge"
	};
}	


/*
 * should be called on the HTML body.onload event
 */
LayoutManager.prototype.onBodyLoad = function () {
	this.initialize();
	this.updateLayout();
};


/*
 * should be called on the HTML body.onresize event
 */
LayoutManager.prototype.onResizeBody = function () {
	this.updateLayout();
};


/*
 * true if the HTML page has defined one or more columns for the content area
 */
LayoutManager.prototype.gotColumns = function () {
	return this.columns && this.columns.length > 0;
};


/*
 * Calculate conforming layout config. These adjust to dynamic content, but only need to be calculated once when
 * the page is loaded.
 *
 * If there is a navigational sidebar, we assume it is to the left of the pageContent area and has a fixed width.
 */
LayoutManager.prototype.initialize = function () {
	var iCol, logo, col, colWidth, colWidthExtra,
		pageContainer = document.getElementById(this.elementID.pageContainer),
		header = document.getElementById(this.elementID.header),
		contentMinWidth, headerMinWidth = this.config.bannerMinWidth;

	gmDebug.log("--- LayoutManager.initialize ---");

	if (!pageContainer) {
		throw "LayoutManager required pageContainer HTML element '" + this.elementID.pageContainer + "' not found";
	}
	if (!document.getElementById(this.elementID.pageContent)) {
		throw "LayoutManager required pageContent HTML element '" + this.elementID.pageContent + "' not found";
	}
	this.footer = document.getElementById(this.elementID.footer);	// optional
	this.navCol = document.getElementById(this.elementID.navCol);	// optional

	// A banner and log are only supported as children of the header.
	if (header) {
		logo = document.getElementById(this.elementID.logo);
		if (logo) {
			headerMinWidth += logo.offsetWidth;
			header.style.height = Math.max(logo.offsetHeight, this.config.bannerMinHeight) + 'px';
			header.style.minWidth = logo.offsetWidth + this.config.bannerMinWidth;
		} else {
			header.style.height = this.config.bannerMinHeight + 'px';
			header.style.minWidth = this.config.bannerMinWidth;
		}
	}

	if (this.navCol) {
		this.navCol.style.borderRight = this.config.columnBorderStyle;
	}

	// If multiple columns, set column widths and add right borders to left column(s)
	if (this.gotColumns()) {
		gmDebug.log("Setting column styles:");

		// hack: using 99% of page width allows for the width of the borders on the sides of the columns
		colWidth = Math.round(99 / this.columns.length);
		colWidthExtra = 99 - (this.columns.length * colWidth);

		for (iCol = 0; iCol < this.columns.length; iCol += 1) {
			gmDebug.log("&nbsp;&nbsp; this.columns[" + iCol + "] = " + this.columns[iCol]);
			col = document.getElementById(this.columns[iCol]);
			
			col.style.minWidth = this.config.columnMinWidth + 'px';
			col.style.width = (iCol === 0 ? (colWidth + colWidthExtra) : colWidth) + '%';

			if (iCol < (this.columns.length - 1)) {
				col.style.borderRight = this.config.columnBorderStyle;
			}
		}
	}

	// Set config for page container to protect minimum width of header and columns
	contentMinWidth = (this.columns ? this.columns.length : 1) * (this.config.columnMinWidth + this.config.columnWidthHack);
	pageContainer.style.minWidth = Math.max(headerMinWidth, contentMinWidth) + 'px';
};


/*
 * Update layout of primary page elements when the html body is resized. Ensures the footer never crawls up above the
 * bottom of the browser window. May force all columns to same height.  May extend height of columns to fill any white
 * space between the page content and the footer.
 */
LayoutManager.prototype.updateLayout = function () {
	var iCol = 0,
		col = null,
		colID = 0,
		colRect = {},
		contentElement,
		contentBottom = 0,
		maxColHeight = 0,
		newColumnHeight = 0,
		gap = 0,
		newFooterTop = 0,
		windowHeight = getWindowHeight();

	gmDebug.log("--- LayoutManager.updateLayout ---");

	// maintain position of footer at bottom of window
	if (windowHeight > 0) {
		gmDebug.log("windowHeight = " + windowHeight);

		// If the pageContent div contains the page content directly (there are no columns), or the columns are not
		// using the float property, then the height of the page container should imply the bottom of the content area.
		if (this.gotColumns()) {
			for (iCol = 0; iCol < this.columns.length; iCol += 1) {
				colID = this.columns[iCol];
				col = document.getElementById(colID);
				if (!col) { throw "Cannot locate column element " + colID; }

				// Get "preferred" rect of columns
				col.style.height = 'auto';
				colRect[colID] = col.getBoundingClientRect();
				gmDebug.log("colRect[" + colID + "] : bottom(" + colRect[colID].bottom + "), height(" + colRect[colID].height + ")");
				maxColHeight = Math.max(maxColHeight, colRect[colID].height);
				contentBottom = Math.max(contentBottom, colRect[colID].bottom);
			}
			gmDebug.log("maxColHeight = " + maxColHeight);
		} else {
			contentElement = document.getElementById(this.elementID.pageContent);
			if (!contentElement) {
				throw this.elementID.pageContent + "element not found";
			}
			contentBottom = contentElement.getBoundingClientRect().bottom;
		}
		// The nav col could be taller than the page content
		if (this.navCol) {
			this.navCol.style.height = 'auto';
			colRect[this.elementID.navCol] = this.navCol.getBoundingClientRect();
			maxColHeight = Math.max(maxColHeight, colRect[this.elementID.navCol].height);
			contentBottom = Math.max(contentBottom, colRect[this.elementID.navCol].bottom);
		}
		contentBottom = Math.round(contentBottom);
		gmDebug.log("contentBottom = " + contentBottom);
		gmDebug.log("footer.offsetHeight = " + this.footer.offsetHeight);

		gap = windowHeight - (contentBottom + this.footer.offsetHeight);  // gap may be negative here
		gmDebug.log("gap = " + gap);
		newFooterTop = (gap < 0 ? 0 : gap);

		if (this.gotColumns() || this.navCol) {
			newColumnHeight = Math.round(maxColHeight);
			if (this.config.fillContentHeight) {
				if (gap > 0) {
					newColumnHeight += gap;
					newFooterTop = 0;
				}
			}
			gmDebug.log("newColumnHeight = " + newColumnHeight);

			if (this.config.normalizeColumns || this.config.fillContentHeight) {
				for (iCol = 0; iCol < this.columns.length; iCol += 1) {
					colID = this.columns[iCol];
					if (colRect[colID].height !== newColumnHeight) {
						document.getElementById(colID).style.height = newColumnHeight + 'px';
					}
				}
				this.navCol.style.height = newColumnHeight + 'px';
			}

		}

		gmDebug.log("newFooterTop = " + newFooterTop);
		this.footer.style.top = newFooterTop + 'px';
	}
};

