/**
 * @namespace Namespace for products-related code
 */
var productPage = {};

/**
 * ID of the outermost node of overlay windows
 * @constant
 * @fieldOf productPage
 * @see overlay
 */
productPage.OVERLAY_CONTAINER_ID = "overlay-container";
/**
 * Intended for use in URLs, this is the name of the directory that contains product-related HTML templates.
 * @constant
 * @fieldOf productPage
 */
productPage.TEMPLATE_DIR = "products/";
/**
 * Message that appears for products that are Out of Stock.
 * @fieldOf productPage
 * @constant
 */
productPage.TOS_MSG = "*Temporarily Out Of Stock";
/**
 * ID of the outermost node of Quickshop windows
 * @constant
 * @see productPage.ProductView.Quickshop
 */
productPage.QUICKSHOP_CONTAINER_ID = "quickshop-container";
/**
 * ID of the 2nd-outermost node of Quickshop windows. It is removed from the DOM
 * when Quickshop windows are closed.
 * @constant
 * @see productPage.ProductView.Quickshop
 */
productPage.QUICKSHOP_CONTENT_ID = "quickshop-content";
/** @namespace Holds the constants that list JSON-RPC query fields for Detail Views (SPP & Quickshop Windows). */
 productPage.DETAIL_VIEW_QUERY = {};
/**
 * This variable contains a JS Object hash with one record. The key is "product_fields". The value
 * is an array of the Product field names that are passed to the JSON-RPC query for the SPP page and Quickshop view.
 * @constant
 * @type Object
 */
productPage.DETAIL_VIEW_QUERY.PRODUCT_FIELDS = {
	"product_fields" : [ "PRODUCT_ID", "DEFAULT_CAT_ID", "PARENT_CAT_ID", "PRODUCT_NAME", "product_altname", "DESCRIPTION", "SHORT_DESC", "PROD_SKIN_TYPE", "prod_skin_type_string", "IMAGE_NAME", "DISPLAY_ORDER", "SMALL_IMAGE", "LARGE_IMAGE", "THUMBNAIL_IMAGE", "PRODUCT_USAGE", "AVERAGE_RATING", "TOTAL_REVIEW_COUNT",  "RECOMMENDED_PERCENT", "ONLY_RATINGS_COUNT" , "RATING_IMAGE", "FORMULA", "COVERAGE", "BENEFITS", "BRUSH", "SKIN_CONCERN", "SKIN_CONCERN_1", "SKIN_CONCERN_2", "SKIN_CONCERN_3", "sku", "shaded", "sized", "url"]
};
/**
 * This variable a JS Object hash with one record. The key is "sku_fields". The value
 * is an array of the SKU field names that are passed to the JSON-RPC query for the SPP page and Quickshop view.
 * @constant
 * @type Object
 */
productPage.DETAIL_VIEW_QUERY.SKU_FIELDS = {
	"sku_fields" : ["SKU_ID", "PRODUCT_ID", "DISPLAYNAME", "SHADENAME", "SHADE_DESCRIPTION", "DISPLAY_ORDER", "SKIN_TYPE", "skin_type_string", "PRODUCT_SIZE", "shopping_id", "STRENGTH", "PRODUCT_PRICE", "SMOOSH_DESIGN", "smoosh_path", "INVENTORY_STATUS", "REFILLABLE", "PRICE", "FORMATTED_PRICE", "HEX_VALUE", "hex_value_string", "FINISH", "COLOR_FAMILY_NAME", "LIFE_OF_PRODUCT", "shoppable", "promotional"]
};
/** @namespace Holds the constants that list JSON-RPC query fields for SPP Content tabs */
productPage.CONTENT_TAB_QUERY = {};
/**
 * This variable is an array of the field names that are passed to the JSON-RPC query for the
 * content tabs (e.g., "Articles" and "Videos") on the SPP page.
 * @constant
 */
productPage.CONTENT_TAB_QUERY.FIELDS = ["PRODUCT_ID", "TAB_NUMBER", "DISPLAY_ORDER", "DISPLAY_STATUS", "ITEM_ID", "ITEM_TYPE", "TITLE", "DESCRIPTION", "LINK_COPY", "LINK_URL", "MEDIA"];
/**
 * This variable contains the definitions for filter menus on the Shade Tables, organized by category.
 * @constant
 * @type Hash
 * @see productPage.ShadePicker.Table#initFilterMenus
 */
productPage.shadeFilterMenus = new Hash({
	CATEGORY4906 : { // Powder
		filters: [ { label: "Skin Tone", field: "SKINTONE" } ]
	},
	CATEGORY4900 : { // Foundations
		filters: [ { label: "Skin Tone", field: "SKINTONE" } ]
	},
	CATEGORY4898 : { // Eye Shadow
		filters: [ { label: "Finish", field: "FINISH" },
				   { label: "Colour Family", field: "COLOR_FAMILY_NAME" } ]
	},
	CATEGORY4897 : { // Eye Liner
		filters: [ { label: "Colour Family", field: "COLOR_FAMILY_NAME" } ]
	},
	CATEGORY4903 : { // Lipstick
		filters: [ { label: "Finish", field: "FINISH" },
				   { label: "Colour Family", field: "COLOR_FAMILY_NAME" } ]
	},
	CATEGORY4901 : { // Lip Gloss
		filters: [ { label: "Finish", field: "FINISH" },
				   { label: "Colour Group", field: "COLOR_GROUP" } ]
	},
	CATEGORY21361 : { // Lip Liner
		filters: [ { label: "Colour Group", field: "COLOR_GROUP" } ]
	},
	CATEGORY4902 : { // Lip Pencil
		filters: [ { label: "Colour Group", field: "COLOR_GROUP" } ]
	}
});

/**
 * List of category IDs and supercat IDs for products that do not display Skin Type.
 */
productPage.hideSkinTypeGroups = [ "CATEGORY3882", "CATEGORY19551", "CATEGORY4896", "CATEGORY4894", "CATEGORY4904", "CATEGORY4898", "CATEGORY4897", "CATEGORY4895", "CATEGORY4903", "CATEGORY4901", "CATEGORY21361", "CATEGORY7567", "CATEGORY22357", "CATEGORY22358", "CATEGORY4910" ];
/**
 * Iterate through the categories in  productPage.shadeFilterMenus and add the definitions for sort menus.
 * @see productPage.shadeFilterMenus
 * @see productPage.ShadePicker.Table#initFilterMenus
 */
productPage.shadeFilterMenus.each(function(record) {
	var cat = productPage.shadeFilterMenus.get(record.key);
	cat.sort= [ { label: "Bestseller" , field: "DISPLAY_ORDER" },
				{ label: "Alphabetically" , field: "SHADENAME" }];
});


/**
 * @class This singleton class is a wrapper for a pop-over module.
 * It supports one visible window at a time.
 * @see productPage.OVERLAY_CONTAINER_ID
 */
overlay = function() {
	var win = null;
	var containerNode = null;
	var isVisible = false;
	var options = {};
	return {
		/**
		 * This function displays a pop-over window. If a pop-over is already showing, the
		 * launch() function will do nothing.
		 * @param {Object} args.cssStyle Hash of CSS style definitions for the window.
		 * Uses JS notation (i.e., "marginLeft").
		 * @param {string|Node} args.content HTML, node, or text that will display in the window.
		 */
		launch : function(args) {
			if (win === null) {
				win = new AsyncLighterbox();
			}
			if (!isVisible) {
				containerNode = $("overlay-container");
				if (!containerNode) {
					containerNode = new Element('div', {id: "overlay-container", style:"display:none"});
				}
				Object.extend(options, args || {});
				containerNode.setStyle(options.cssStyle);
				containerNode.update(options.content);
				$(document.body).insert(containerNode);
				win.setContent(containerNode);
				win.show();
				isVisible = true;				
			}
		},
		/**
		 * This function "closes" the pop-over window. It completely removes
		 * the contents of the window from the DOM.
		 */
		hide: function() {
			if (win !== null) {
				containerNode.update('');
				win.hide();
				isVisible = false;
			}
		}
	};
}();

/**
 * Generic base class for UI elements. It keeps track of instances of its derived classes
 * and provides the ability to delete those instances (along with any child widgets they
 * contain) from memory. It can also retrieve child nodes with IDs that are duplicated
 * elsewhere in the DOM.
 * ViewContainerNode is a central concept for widgets. The idea is to identify a DOM node as
 * a parent node for the widget; the child nodes of this container are considered child
 * nodes of the widget.
 * @class
 */
productPage.Widget =
	/**
	 * @memberOf productPage
	 * @lends productPage.Widget#
	 */
	{
	viewContainerID: '',
	viewContainerNode: null,
	idSuffix: '',
	isWidget: true,
	/**
	 * This function sets a node as the ViewContainerNode for an instance.
	 * @param {Node|string} ele the node (or ID of the node) that is to act as the ViewContainerNode.
	 * The element must be present in the DOM when the Widget is instantiated.
	 * @public
	 */
	setViewContainerNode: function(ele) {
		ele = $(ele);
		if (!Object.isElement(ele)) {
			return;
		}
		this.viewContainerNode = ele;
		return this;
	},
	/**
	 * This function returns the ViewContainerNode for a Widget instance. If the viewContainerID
	 * string has been set but no node has been explicitly set, this function will
	 * retrieve the node with that ID from the DOM and set it as the viewContainerNode.
	 * If no viewContainerID has been set and there is no viewContainerNode set, this
	 * function returns the document's body node.
	 * @returns {Node} the ViewContainerNode
	 * @public
	 */
	getViewContainerNode: function() {
		if (!this.viewContainerNode && (!this.viewContainerID || this.viewContainerID === '')) {
			this.setViewContainerNode(document.body);
		} else if (this.viewContainerNode === null && this.viewContainerID.length > 0) {
			if (Object.isElement($(this.viewContainerID))) {
				this.setViewContainerNode(this.viewContainerID);
			} else {
				this.setViewContainerNode(document.body);
				this.viewContainerID = '';
			}
		}
		return this.viewContainerNode;
	},
	/**
	 * Searches through the child nodes of the widget's viewContainer for
	 * an element whose ID matches nodeID. This is preferable to other retrieval
	 * methods because it works if there are elements elsewhere in the DOM with the same ID.
	 * @param {string} nodeID The ID of the node to retrieve.
	 * @returns {Node}
	 */
	getChildNode: function(nodeID) {
		var returnNode = this.getViewContainerNode().descendants().find(function(ele) {
			return ele.id === nodeID;
		});
		return returnNode;
	},
	/**
	 * Creates & returns a string that can be used as a selector in CSS-based queries for child nodes
	 * of the Widget's viewContainer.
	 * @returns {string} a string formatted "#[node ID] ". Returns an empty string if no
	 * viewContainer is found.
	 */
	getSelectorPrefix: function() {
		if (this.getViewContainerNode().id.length > 0) {
			return '#' + this.getViewContainerNode().id + ' ';
		} else {
			return '';
		}
	},
	/**
	 * This function must be called in order to track a Widget instance. This enables
	 * unique ID's to be created for child elements and recursive destruction of the Widget
	 * and its child Widgets.
	 */
	register: function() {
		var c = this.constructor;
		if (!c.instances) {
			c.instances = [];
		}
		this.idSuffix = '_' + c.instances.length;
		if(this.viewContainerID) {
			this.setViewContainerNode($(this.viewContainerID));
		}
		c.instances.push(this);
	},
	/**
	 * This function appends an "_x" suffix to an element, where x equals the Widget instance's
	 * index among other instances of the same class.
	 * @param {string|Node} e The node (or ID of the node) whose ID is to be changed.
	 * @returns {string} The node's new ID.
	 */
	appendSuffix: function(e) {
		if (Object.isElement(e)) {
			e.id += this.idSuffix;
			return e.id;
		} else if (Object.isString(e) && e.length > 0) {
			var ele = this.getChildNode(e);
			if (ele) {
				ele.id += this.idSuffix;
				return ele.id;
			} else {
				return;
			}
		} else {
			return;
		}
	},
	/**
	 * This function must be called in order to remove the reference to a instance
	 * from the class' internal register of instances. It will do the same for any
	 * Widget instances that were instantiated as "child widgets" (i.e., as members or properties
	 * of the Widget instance being destroyed.
	 */
	destroy: function() {
		var prop;
		var memo = [];
		var childWidgets = function(obj) {
			if (obj.isWidget) {
				memo.push(obj);
			}
			if (obj.hasOwnProperty) {
				var props = [];
				var tempHash = $H(obj);
				tempHash.each(function(prop) {
					if (obj.hasOwnProperty(prop) &&
							obj[prop] !== null &&
							typeof obj[prop] === "object" &&
							props.indexOf(prop) === -1) {
						props.push(prop);
						arguments.callee(obj[prop]);
					}
				});
			}
			return memo;
		}(this);
		childWidgets.each(function(widget){
			var c = widget.constructor;
			if (c.instances) {
				var match = $A(widget.constructor.instances).find(function(instance) {
					return instance === widget;
				});
				if (match) {
					widget.constructor.instances.splice(widget.constructor.instances.indexOf(widget), 1);
				}
			}
		});
	}
};

productPage.FilterTable = Class.create( productPage.Widget,
	/**
	 * @lends productPage.FilterTable#
	 * @memberOf productPage
	 */
	{
	/**
	 * The FilterTable base class can be extended to build tabular HTML out of an array
	 * of data in JSON format. Select menus are registered with the FilterTable, which in turns assigns
	 * events to the menus.
	 * @class
	 * @augments productPage.Widget
	 * @constructs
	 * @param {Array} args.tableData the data to be displayed by the table. It should be an Array of
	 * objects; each object should have top-level properties that will be parsed by the filtering &
	 * sorting routines.
	 */
	initialize: function (args) {
		this.containerID = null;
		this.filterNodes = new Hash();
		this.sortNodes = new Hash();
		this.sortNode = null;
		this.parentFormNode = null;
		this.tableData = {};
		this.allData = {};
		this.cellsPerRow = 3;
		this.sortDisplayOrder = true;
		Object.extend(this, args || {});
		this.tableData.each(function(item) {
			item.display = true;
		});
		this.register();
	},
	/**
	* This function registers a select menu as a Filter control for the table.
	* @param {Node} selectNode the select node
	* @param {string} field the name of the field on which this menu will filter.
	* The field must the name of a top-level property of a tableData item. For example,
	* a table that displays Product items might filter on a field Product.PRODUCT_NAME.
	* The parameter passed in this case would be "PRODUCT_NAME".
	* @methodOf productPage.FilterTable
	*/
	addFilterNode: function(selectNode, field) {
		if (this.validateSelectNode(selectNode)) {
			this.filterNodes.set(field, selectNode);
		}
		this._initFilterChangeEvent(selectNode);
	},
	/**
	* This function registers a select menu as a Sort control for the table.
	* @param {Node} selectNode the select node
	* @param {string} field the name of the field on which this menu will filter.
	* The field must the name of a top-level property of a tableData item. For example,
	* a table that displays Product items might sort on a field Product.PRODUCT_NAME.
	* The parameter passed in this case would be "PRODUCT_NAME".
	* @methodOf productPage.FilterTable
	*/
	addSortNode: function(selectNode, field) {
		this.sortNode = selectNode;
		this._initFilterChangeEvent(selectNode);
	},
	/**
	 * This method creates the event listener and handler for a select menu.
	 * on the select menu's change event, a custom Prototype event "table:filter"
	 * is fired by the select menu and the object's #filter method is called.
	 * @private
	 * @methodOf productPage.FilterTable
	 * @see productPage.FilterTable#filter
	 */
	_initFilterChangeEvent: function(selectNode) {
		var self = this;
		selectNode.observe('change', function(evt) {
			evt.target.fire("table:filter", evt.target);
			self.filter(evt.target);
		});
	},
	/**
	 * This method is empty in the FilterTable class. Derived classes must override it
	 * with a function that parses the tableData and renders HTML.
	 * @methodOf productPage.FilterTable
	 */
	build: function() {},
	/**
	 * This function reads the values from filtering & sort menus,
	 * puts the requested shades in an array, then passes that array to the build function.
	 * It is called when the user changes a filter or sort criteria menu.
	 * @methodOf productPage.FilterTable
	 */
	filter: function () {
		var self=this;
		//
		// A hash of functions that are used to filter data on various fields.
		// Each returns true if the data matches one of the items in the valuesToFind array
		var filterFunctions = function(field) {
			switch(field) {
				case "prod_skin_type_string" :
					return function(valueToFind, valueToSearch) {
						if (valueToSearch.substring(2,6) === "0000") {
							return true;
						}
						var pos = parseInt(valueToFind) + 1;
						//
						// check valueToSearch.substr(pos, 1) because we are interested in the
						// 3rd - 6th characters of skin_stype_string. valueToFind will be digits 1-4
						if (valueToSearch.substr(pos, 1) === "1") {
							return true;
						}
						return false;
					};
					break;
				default :
					return function(valueToFind, valueToSearch) {
						// WDR: bug 30838: changed this line:
						//return valueToSearch === valueToFind;
						// Note - valueToSearch can be null!
						if ( valueToSearch === valueToFind ) return true;
						if ( valueToSearch && valueToSearch.indexOf(valueToFind) >= 0 ) return true;
						return false;
					};
					break;
			}
		};
		//
		// iterate through data and filter out non-matching items
		this.tableData.each(function(item) {
			item.display = false;
			//
			// the keys of the filter_node hash are the names of the
			// fields on which we're going to filter
			self.filterNodes.keys().each(function(menuName) {
				//
				// menu name can contain multiple field names separated by ::
				var fields = menuName.split('::');
				var menuValue = $F(self.filterNodes.get(menuName));
				for (var i=0, len=fields.length; i<len; i++ ) {
					var field = fields[i];
					//
					// if the data fails the test and if the menu option value
					// is a non-empty string, do not display the item
					if (menuValue.length < 1 || filterFunctions(field)(menuValue, item[field])) {
						item.display = true;
						break;
					} else {
						item.display = false;
					}
				}
				if (!item.display) {
					throw $break;
				}
			});
		});
		//
		// Sort data. The menu option value is the name of a product field.
		if (this.sortNode) {
			var sortCriteria = $F(this.sortNode);
			if (this.tableData[0].hasOwnProperty(sortCriteria)) {
				this.tableData = this.tableData.sortBy(function(p) {
					return p[sortCriteria];
				});
			}
		}
		this.build();

	},
	/**
	* This function removes all HTML nodes that are children of the table's container node.
	* @methodOf productPage.FilterTable
	* @returns {Node} the container node
	*/
	clearContainer: function() {
		var container = $(this.containerID);
		//
		// clear out all child elements of the table
		while (container && container.down()) {
			container.down().remove();
		}
		return container;
	},
	/**
	* This method creates an HTML select menu node, adds options, and inserts the
	* select menu node in the DOM.
	* @param {Node} args.containerNode the node into which the sort menu will be insterted
	* @param {number} args.menuWidth width in pixels of a custom menu that gets rendered later
	* the select menu takes this number X and gives the node a class of width_X.
	* @param {string} args.selectNodeID the ID that will be given to the select menu node.
	* @param {Array} args.sortMenuArray an array of JS hash objects. These objects define the options that
	* are to be inserted in the menu - the format must be { field: *string*, label: *string* } where
	* field is the property that the option sorts on and label is the text displayed in the menu.
	* @methodOf productPage.FilterTable
	* @returns {Node} the select menu
	*/
	buildSortMenu: function(args) {
		if (!args.containerNode) {
			return;
		}
		options = Object.extend({
			menuWidth: 100,
			selectNodeID: "sort-menu"
		}, args || {});
		var slct = new Element("select", {
			'class' : "width_" + options.menuWidth,
			id	  : options.selectNodeID,
			name	: options.selectNodeID });
		options.sortMenuArray.each(function(sortMenuRecord){
			slct.insert(new Element("option", {
				value	: sortMenuRecord.field,
				selected : "selected" }).insert(sortMenuRecord.label));
		});
		options.containerNode.insert(slct);
		return slct;
	},
	/**
	* This method does the heavy lifting of building the HTML select menu node.
	* @requires niceform_init (from niceform.js)
	* @methodOf productPage.FilterTable
	* @param {Node} args.filterMenuContainerNode the node into which the filter menus will be insterted
	*/
	createFilterMenus: function(args) {
		var self = this;
		var options = Object.extend({
			filterMenuContainerNode : null,
			tableData	: null,
			menuMetaData : null,
			menuWidth	: 130 }, args || {});
		for (var i in options) {
			if (i === null) {
				return null;
			}
		}
		var getMenuElements = function() {
			var self = this;
			var menus = [];
			options.menuMetaData.filters.each(function(filterObj) {
				var slct = initFilterMenu({
					field: filterObj.field,
					label: filterObj.label
				});
				if (slct) {
					menus.push({label: "Filter by " + filterObj.label + ':', node: slct});
				}
			});
			return menus;
		};
		var initFilterMenu = function(menuArgs) {
//		  args.tableData args.field args.label args.menuWidth
			var fields = menuArgs.field.split('::');
			var menuOptions = getMenuOptions(fields);
			if (menuOptions) {
				return buildFilterMenu({
					menuOptions: menuOptions,
					fieldName  : menuArgs.field,
					menuLabel  : menuArgs.label });
			} else {
				return null;
			}
		};
		var buildFilterMenu = function(buildArgs) {
			var slct = new Element("select", {
				'class' : "width_" + options.menuWidth,
				id	: buildArgs.fieldName,
				name  : buildArgs.fieldName });
			slct.insert(new Element("option", {
				value : "" }).insert("All"));
			buildArgs.menuOptions.each(function(val) {
				slct.insert(new Element("option", {
					value : val
				}).insert(val));
			});
			return slct;
		};
		var getMenuOptions = function(fields) {
			var menuOptions = [];
			for (var i=0, len=fields.length; i<len; i++) {
				var fieldName = fields[i];
				switch(fieldName) {
					case "prod_skin_type_string" :
						[1,2,3,4].each(function(n) {
							menuOptions.push(n);
						});
						break;
					default:
						options.tableData.each(function(record) {
							var menuVal = record[fieldName];
							if (menuVal) {
								// WDR: bug 30838:
								var arVals = menuVal.split("/");
								$A(arVals).each(function(mval){ menuOptions.push(mval); });
//								console.log(record);
//								console.log(menuVal);
								//WDR:took out:  menuOptions.push(menuVal);
							}
						});
						break;
				}
			}
			menuOptions = $A(menuOptions).uniq();

			if (menuOptions.length < 2) {
				return null;
			}
			return menuOptions;
		};
		var filterMenuObjects = getMenuElements();
		if (filterMenuObjects.length > 0) {
			filterMenuObjects.each(function(record) {
				var lbl = new Element('label', {'class':'filterby'});
				lbl.insert(record.label);
				options.filterMenuContainerNode.insert(lbl);
				var slct = record.node;
				options.filterMenuContainerNode.insert(slct);
				self.addFilterNode(slct, slct.name);
			});
		}
		niceform_init();
	},
   /**
	* validates that an element is an HTML select node
	* @methodOf productPage.FilterTable
	*/
	validateSelectNode: function(selectNode) {
		return (Object.isElement(selectNode) && selectNode.tagName.toLowerCase() === 'select');
	}

});

/**
 * stores the instances of all ProductView-derived classes
 * @see Widget#register
 * @memberOf productPage
 * @type Array
 */
productPage.productViewQueue = [];

productPage.ProductView = Class.create( productPage.Widget,
	/**
	 * @lends productPage.ProductView#
	 */
	{
	/**
	 * Base class for product-level view. Provides getter/setter methods for DOM node IDs along with
	 * the framework for loading & displaying UI modules. Much of this work is broken up into fine-grained
	 * steps in order to allow flexibility for the different derived classes and their various configurations.
	 * @class
	 * @augments productPage.Widget
	 * @constructs
	 */
	initialize: function(args) {
//		console.log("initialize");
		this.detailFilename = "product_detail";
		this.descriptionFilename = "product_description";
		this.childIDs = {};
		this.childDomNodes = {};
		this.productTabs = {};
		Object.extend(this, args || {});
//		this.setViewContainerNode($(this.viewContainerID));
		//
		// unlike other productPage.Widgets, ProductView and its child classes
		// share the same Queue for instances
		this.constructor.instances = productPage.productViewQueue;
		this.register();
		this.productData.url_domain = window.URL_DOMAIN;
		this.productData.url_domain_http = window.URL_DOMAIN_HTTP;
	},
	/**
	 * This method renders the main element of the view (the Detail) by passing data to a Template.
	 * The Template that is loaded must be stored in the detailFilename property.
	 * @methodOf productPage.ProductView
	 */
	build: function() {
		var self = this;
		templatefactory.get(productPage.TEMPLATE_DIR + this.detailFilename).evaluateCallback({
			object: self.productData,
			callback: function(html) {
				self.detailCallbackFn(html);
			}
		});
	},
	/**
	 * This is the method that actually draws the Detail content onto the page.
	 * @param {string|Node} html the content of the Detail portion of the view.
	 * @methodOf productPage.ProductView
	 * @returns {Object} the ProductView instance
	 */
	renderDetailHtml: function(html) {
		var node = this.getDetailContainerNode();
		node.update(html);
		return this;
	},
	/**
	 * This is the method that loads the Template for the view's tab container.
	 * @param {string|Node} html the content of the Detail portion of the view.
	 * @methodOf productPage.ProductView
	 * @returns {Object} the ProductView instance
	 */
	initProductTabs: function(args) {
		var tabsContainerNode = this.getTabsContainerNode();
		if (!tabsContainerNode) {
			return;
		}
		var self = this;
		tabsArgs = Object.extend({
			viewContainerID: this.viewContainerID,
			containerNode : tabsContainerNode,
			callback : function(productTabsObj) {
				self.productTabs = productTabsObj;
				self.tabsLoadedCallbackFn();
			}
		}, args || {});

		this.productTabs = new productPage.ProductView.ProductTabs(tabsArgs);
		return this;

	},
	/**
	 * This is the method that loads the template of the Description section. The Description
	 * section includes the product name, description, price, Add to Bag button, the Skin Types,
	 * Replenish menu, etc.
	 * The Template that is loaded must be stored in the descriptionFilename property.
	 * @param {Object} productData the JSON-formatted data that gets parsed into the template.
	 * @methodOf productPage.ProductView
	 * @returns {Object} the ProductView instance
	 */
	initDescription: function(productData) {
		var self = this;
		templatefactory.get(productPage.TEMPLATE_DIR + this.descriptionFilename).evaluateCallback({
			object: productData,
			callback: function(html) {
				self.descriptionCallbackFn(html);
			}
		});
		return this;
	},
	/**
	 * This method creates an HTML node that will contain the Replenishment menu, then
	 * instantiates the Replenishment menu object.
	 * @param {Node} containerNode the node into which the menu gets inserted. The menu
	 * is insterted as the last child in the containerNode.
	 * @methodOf productPage.ProductView
	 * @returns {Object} the ProductView instance
	 */
	initReplenishMenu: function(containerNode) {
	   var self = this;
	   if (productPage.validateSkusArray(this.productData.skus) && this.productData.skus[0].REFILLABLE) {
			var newDiv = new Element('div', {id: 'replenish-container' + this.idSuffix, 'class': 'clear replenish-container'});
			containerNode.insert({'bottom': newDiv});
			this.replenishMenu = new productPage.ReplenishMenu ({
				containerID : 'replenish-container' + self.idSuffix,
				skuData	 : this.productData.skus[0]
			});
		}
		return this;
	},
	/**
	 * This method locates the add button HTML nodes and passes them, along with SKU data,
	 * as arguments to the constructor of the addButton class. It also adds an event
	 * listener & handler to the button so that the correct SKU_ID, shopping_id, and Replenish
	 * options are passed to the button whenever a new selections are made elsewhere in the ProductView.
	 * @methodOf productPage.ProductView
	 * @requires addButton from /js/buttons.js
	 */
	initAddButton: function() {
		var skus = this.productData.skus;
		if (productPage.validateSkusArray(skus)) {
			var self = this;
			this.addButton = addButton({
				domNode	  : this.getChildNode("add_link"),
				progressNode : this.getChildNode("add_progress"),
				cartArgs	 : {
					sku_id	  : skus[0].SKU_ID,
					shopping_id : skus[0].shopping_id
				 }
			});
			this.viewContainerNode.observe('select:sku', function(evt) {
				var skuData = evt.memo;
				self.addButton.cartArgs.sku_id = skuData.SKU_ID;
				self.addButton.cartArgs.shopping_id = skuData.shopping_id;
			});
			productPage.initReplenishButton(this.addButton, this.viewContainerNode);
		}
	},
	/**
	 * This method creates a tab that will hold the view's ShadePicker widget. Instantiation
	 * of the ShadePicker object happens in the derived classes' overridden initShadePicker methods.
	 * @methodOf productPage.ProductView
	 * @param {Object} productData product & SKU data in JSON format.
	 */
	initShadePicker: function(productData) {
		var self = this;
		if (productData.shaded && this.productTabs) {
			this.productTabs.createTab({
				tabID			: "tab_shaded",
				tabLabel		 : "SHADES",
				content		  : "",
				contentStyle	 : "padding: 10px; min-height: 260px;",
				afterChangeHandler : function() {
					if (self.shadePicker.table) {
						self.shadePicker.table.initScroller();
					}
				}
			});
		}
	},
	/**
	 * This method updates the displayed price.
	 * @methodOf productPage.ProductView
	 * @param {string} priceStr the value to be displayed in the DetailView's price field.
	 */
	updatePrice: function(priceStr) {
		if (priceStr && priceStr.length > 0) {
			this.getPriceFieldNode().update(priceStr);
		}
	},
	/**
	 * This method adds an ID to the ProductView's internal Hash of child node IDs.
	 * That Hash is used for retrieval of child nodes.
	 * @methodOf productPage.ProductView
	 * @param {string} childEle The key that will be used to retrieve the ID string.
	 * @param {string} idStr The node ID to be stored.
	 * @example this.setChildID('detailContainer', 'detail-container-div');
	 */
	setChildID: function(childEle, idStr) {
		if (productPage.validateString(idStr) && productPage.validateString(childEle)) {
			this.childIDs[childEle] = idStr;
		}
	},
	setDetailContainerID: function(idString) {
		this.setChildID('detailContainer', idString);
		return this;
	},
	setTabsContainerID: function(idString) {
		this.setChildID('tabsContainer', idString);
		return this;
	},
	setDescriptionContainerID: function(idString) {
		this.setChildID('descriptionContainer', idString);
		return this;
	},
	setPriceFieldID: function(idString) {
		this.setChildID('priceField', idString);
		return this;
	},
	/**********************
	* get functions
	***********************/
	getNode: function(key) {
		var self = this;
		try {
			if (this.childDomNodes[key]) {
				return this.childDomNodes[key];
			} else if(productPage.validateString(this.childIDs[key])) {
//				this.childDomNodes[key] = $(this.childIDs[key]);
//				this.childDomNodes[key] = $$(this.getSelectorPrefix() + '#' + this.childIDs[key])[0];
				this.childDomNodes[key] = this.getViewContainerNode().descendants().find(function(ele) {
					return ele.id === self.childIDs[key];
				});
				if (this.childDomNodes[key]) {
				   return this.childDomNodes[key];
				} else {
					throw new Error([key + ' Node ' + this.childIDs[key] + ' not found']);
				}
			} else {
				throw new Error([key + ' ID not found']);
			}
		} catch (err) {
			console.log(err.message);
			return;
		}
	},
	getDetailContainerNode: function() {
		return this.getNode('detailContainer');
	},
	getTabsContainerNode: function() {
		return this.getNode('tabsContainer');
	},
	getDescriptionContainerNode: function() {
		return this.getNode('descriptionContainer');
	},
	getPriceFieldNode: function() {
		return this.getNode('priceField');
	},
	/**********************
	* callback functions
	***********************/
	detailCallbackFn: function(html) {
		this.renderDetailHtml(html);
		this.initProductTabs();
	},
	tabsLoadedCallbackFn: function() {
		this.initShadePicker(this.productData);
	},
	// description html is added to the page in the child classes' descriptionCallbackFn()
	descriptionCallbackFn: function(html) {
		var self = this;
		//
		// Coverage, Skin Types, Benefits, etc
		var attrContainer = $('attributes-container');
		if (attrContainer) {
			productPage.displayAttributes({
				containerNode : attrContainer,
				productData   : this.productData
			});
		}
		this.initReplenishMenu($("product-options"));
		//
		// SKU Menu (menu of sizes, shades or formulas)
		var skuMenuContainerNode = this.getChildNode("sku_select_container");
		if (skuMenuContainerNode) {
			var skus = this.productData.skus;
			if (productPage.validateSkusArray(skus)) {
				// sort by price before creating menu so that sized items appear in order
				this.productData.skus = skus.sortBy(function(s){
					return s.PRODUCT_PRICE;
				});
				this.skuMenu = new productPage.SkuMenu({
					menuContainerNode : skuMenuContainerNode,
					viewContainerNode : this.viewContainerNode,
					productData	   : this.productData });
			}
		}
		this.initAddButton();
		this.setPriceFieldID('price-span').viewContainerNode.observe('select:sku', function(evt) {
			self.updatePrice(evt.memo.FORMATTED_PRICE);
		});
		var skus = this.productData.skus;
		if (productPage.validateSkusArray(skus)) {
			this.tosMessage = new productPage.TosMessage({
				domNode : this.getChildNode("main_tos"),
				skuData : skus[0]
			});
		}
	}
});


productPage.ProductView.ProductTabs = Class.create( productPage.Widget, {

	tabsFilename: "product_tabs",

	initialize: function(args){
		this._tabs = null;
		this.register();
		this.tabLinksContainerID = "product-tabs";
		this.contentContainerID = "tabs-content";
		this.tabsWrapperID = 'tabs_container';
		this.showTabID = ''; // from query string, if present
//		this.activeTabID = null;
		Object.extend(this, args || {});
		var self = this;
		this.callbackFn = function(html) {
//			console.log(self.insert);
			if (self.insert) {
				self.containerNode.insert(html);
			} else {
				self.containerNode.update(html);
			}
			self.tabLinksContainerID = self.appendSuffix(self.tabLinksContainerID);
			self.contentContainerID = self.appendSuffix(self.contentContainerID);
			self._tabs = new Control.Tabs(self.tabLinksContainerID, {
				setClassOnContainer: true,
				activeClassName: 'current',
				afterChange	: function(containerNode){
					containerNode.fire('tab:afterChange');
				}
			});
			self.callback(self);
		};

		templatefactory.get(productPage.TEMPLATE_DIR + this.tabsFilename).evaluateCallback({
			object: {},
			callback: self.callbackFn
		});

	},
	getTabLinksContainerNode: function() {
		return this.getChildNode(this.tabLinksContainerID);
	},
	getContentContainer: function() {
		return this.getChildNode(this.contentContainerID);
	},
	getTabsWrapperNode: function() {
	   return this.getChildNode(this.tabsWrapperID);
	},
	createTab : function (args) {
		var self = this;
		if (this._tabs === null) {
			return;
		}
		// See if a tab id is given on the query string
		var qstabid = uri.queryKey['showtabid'];
		if ( qstabid && args.tabID == qstabid ) {
			// Remember this for later
			this.showTabID = args.tabID + self.idSuffix;
		}
		args.tabID = args.tabID + self.idSuffix;
		var lnk = new Element('a', {href: '#' + args.tabID}).update(args.tabLabel);
		var li = new Element('li').update(lnk);
		this.getTabLinksContainerNode().insert(li);
		var contentDiv = new Element('div', {
				"class": "tab-content",
				"id": args.tabID,
				"style": args.contentStyle });
		contentDiv.update(args.content);
		this.getContentContainer().insert(contentDiv);
		this._tabs.addTab(lnk);
		// until a tab has been activated, it can't be de-activated
		// so we set the new tab to active, then set the first tab to active
		this.setActiveTab(args.tabID);
		this._tabs.first();

		if (args.afterChangeHandler) {
			contentDiv.observe('tab:afterChange', function(evt) {
				args.afterChangeHandler(evt.target);
			});
		}

		// See if a specific tab should be shown
		// (note - this may have been from a previous createTab)
		if ( this.showTabID ) {
			this.setActiveTab(this.showTabID);
			this.getTabLinksContainerNode().scrollTo();
		}
	},
	setActiveTab: function(tagID) {
//		console.log(tagID);
//		this.activeTabID = tagID;
		this._tabs.setActiveTab(tagID);
	}
});


// Return true if the passed in product id should not have an add-to-bag on quickshop.
// This is currently only for the Custom Bottle product, but others may need this someday.
// Ok, probably not, but doing it in a function is still cooler than hardcoding this everywhere... :-P
productPage.disableQuickAddProduct = function(prodid) {
	return ( prodid == 'PROD12303' ? true : false );
}


productPage.ProductView.Quickshop = Class.create( productPage.ProductView,
	/**
	 * @lends productPage.ProductView.Quickshop#
	 */
	{
	/**
	 * This class renders Quickshop windows.
	 * @class
	 * @augments productPage.ProductView
	 * @constructs
	 * @see productPage.QUICKSHOP_CONTAINER_ID
	 * @see productPage.QUICKSHOP_CONTENT_ID
	 */
	initialize: function($super, args) {
		if (productPage.validateSkusArray(args.productData.skus)) {
			this.viewContainerID = productPage.QUICKSHOP_CONTAINER_ID;
			this.insertContainers();
			$super(args);
			this.setDetailContainerID(productPage.QUICKSHOP_CONTENT_ID);
		}
	},
	build: function($super) {
		if (this.productData && productPage.validateSkusArray(this.productData.skus)) {
			var self = this;
			this.setTabsContainerID('product-details');
			$super();
			try {
				mpp_tag(this.productData.PRODUCT_ID, 1, 1); // CoreMetrics
			} catch (err) {
				console.log(err);
			}
			this.getViewContainerNode().observe("cart:add:success", function () {
					self.hide();
					if (self.onAdd) {
						self.onAdd();
					}
				 } );
			this.getViewContainerNode().observe("cart:add:fail", function () { self.hide(); } );
		}
	},
	detailCallbackFn: function($super, html) {
		var self = this;
		this.setDescriptionContainerID('product-description');
		$super(html);
		var closelink = this.getChildNode('quickshop-close-container');
		if (closelink) {
			closelink.show();
			Event.observe(closelink, 'click', function(evt) {
				self.hide();
				evt.preventDefault();
			});
		}
	},
	tabsLoadedCallbackFn: function($super) {
		$super();
		this.initDescription(this.productData);
	},
	descriptionCallbackFn: function($super, html) {
		if (this.productData.shaded) {
			this.productTabs.createTab({
				tabID		: "tab_description",
				tabLabel	 : "DESCRIPTION",
				content	  : html,
				contentStyle : "padding: 10px; _height: 260px; height: 260px; min-height: 260px;"
			});
			var productTitle = this.getChildNode('product-title-container').remove();
			this.productTabs.getTabLinksContainerNode().insert({'before': productTitle});
//			this.productTabs.setActiveTab("tab_shaded" + this.productTabs.idSuffix);
		} else {
			var imgContainer = this.getChildNode('quickshop-img-container');
			if (imgContainer) {
				imgContainer.insert({'after': html});
			}
		}
		$super();
		overlay.launch({
			content  : $(productPage.QUICKSHOP_CONTAINER_ID),
			cssStyle : {width: '740px', padding: '10px'}
		});
	},

	// insert quickshop window divs on page if necessary
	insertContainers: function() {
		if (!$(productPage.QUICKSHOP_CONTENT_ID)) {
			var qsDiv1 = new Element("div", {id: productPage.QUICKSHOP_CONTAINER_ID });
			var qsDiv2 = new Element("div", {id: productPage.QUICKSHOP_CONTENT_ID });
			qsDiv1.update(qsDiv2);
		   $(document.body).insert(qsDiv1);
		}
	},
	initShadePicker: function($super, productData) {
		if (productData.shaded && this.productTabs) {
			$super(productData);
			var args = {
				cellsPerRow	   : 2,
				pickerContainerID : "tab_shaded" + this.productTabs.idSuffix,
				productData	   : productData,
				viewContainerID   : this.viewContainerID,
				includeFilters	: false
			};
			this.shadePicker = new productPage.ShadePicker(args);
		}
	},
	initAddButton: function($super) {
		$super();
//		console.log("Quickshop#initAddButton");
		var self = this;
		if ( productPage.disableQuickAddProduct(this.productData.PRODUCT_ID) ) {
			this.addButton.domNode.hide();
		} else {
			Object.extend(this.addButton, {
				"onFailure" : function() {self.hide();}
			});
			this.addButton.onSuccess = this.addButton.onSuccess.wrap(
				function(proceed) {
					proceed();
					self.addButton.domNode.fire("cart:add:success");
					self.hide();
				}
			);
		}
	},
	initProductTabs: function($super) {
		if (this.productData.shaded) {
			$super({insert: true});
		} else {
			this.initDescription(this.productData);
		}
	},
	hide: function() {
//		console.log("closing");
		if (this.onHide) {
			this.onHide();
		}
		this.viewContainerNode.stopObserving();
		this.destroy();
		overlay.hide();
	}

});

productPage.ProductView.Inline = Class.create( productPage.ProductView, {

	build: function($super) {
		this.setTabsContainerID('tabs-container');
		$super();
	},
	detailCallbackFn: function($super, html){
//		console.log("productPage.ProductView.Inline#detailCallbackFn");
		this.setDescriptionContainerID('product-description');
		$super(html);
//		console.log(this.productData);
		this.initDescription(this.productData);
		this.initUtilityLinks();
	},
	tabsLoadedCallbackFn: function($super) {
		$super();
		var self = this;
		if (self.productData.PRODUCT_USAGE) {
			this.productTabs.createTab({
				tabID			: "tab_howto",
				tabLabel		 : "HOW TO USE",
				content		  : self.productData.PRODUCT_USAGE,
				contentStyle	 : "padding: 20px; min-height: 260px;"
			});
		}
		this.loadContentTabsData(self.productData.PRODUCT_ID);
	},
	descriptionCallbackFn: function($super, html) {
		var imgContainer = this.getChildNode('quickshop-img-container');
		if (imgContainer) {
			imgContainer.insert({'after': html});
		}
//		$('quickshop-img-container').insert({'after': html});
		$super();
		this.initBazaarVoice(this.productData);
	},
	initShadePicker: function($super, productData) {
		if (productData.shaded && this.productTabs) {
			$super(productData);
			var args = {
				cellsPerRow	   : 4,
				pickerContainerID : "tab_shaded" + this.productTabs.idSuffix,
				productData	   : productData,
				viewContainerID   : this.viewContainerID,
				categoryID		: CATEGORY_ID 
			};
			this.shadePicker = new productPage.ShadePicker(args);
		}
	},
	initUtilityLinks: function() {
		this.getChildNode('product-utility-links').show();
		var prnt = new PrintButton( this.getChildNode('print-link'), { divToPrint: $('container') } );
	},
	loadContentTabsData: function(productID) {
//		console.log('loadContentTabsData');
		var self = this;
		var tabParams = [{}];
		tabParams[0].tab_fields = productPage.CONTENT_TAB_QUERY.FIELDS;
		tabParams[0].product = [productID];

		jsonrpc.fetch(
			"prodtab.byid", //method
			tabParams, //params
			{
				onSuccess: function (r) {
					var responseData = r.responseText.evalJSON(true);
					if (responseData[0].result.items) {
						self.initContentTabs(responseData[0].result.items);
					} else {
						// if there is no tab content, no shade table, and no How-To-Use
						// hide the entire tab container element
						if ( (!self.productData.shaded) && (!self.productData.PRODUCT_USAGE) ) {
							var tabsContainerNode = self.getTabsContainerNode();
							if (tabsContainerNode) {
								tabsContainerNode.style.display = "none";
							}
						}
					}
				},
				onFailure: function (r) { console.log(r); }
			}
		);
	},
	initContentTabs: function(tabDataArray) {
		var self = this;
		var isEmptyObject = function(obj) {
			if (!obj) { return false; }
			if (! typeof obj == "object") { return false; }
			for (var p in obj) { return false; }
			return true;
		};
		var tabLabels = [
			'VIDEO',
			'PRESS',
			'EXPERT TIPS',
			'MORE'
		];
		var tabDataGroupedByTab = [];
		for (var i=1; i<5; i++) {
			tabDataGroupedByTab[i] = tabDataArray.findAll(function(tabData){
				return tabData.TAB_NUMBER == i;
			});
		}
		for (var j=1; j<5; j++) {
			var tabGroup = tabDataGroupedByTab[j];
			if (tabGroup && tabGroup.length > 0) {
				tabGroup = tabGroup.sortBy(function(p) { return p.DISPLAY_ORDER ? p.DISPLAY_ORDER : 0; });
				var videoItem = tabGroup.find(function(tabData) {
					return tabData.ITEM_TYPE == 1;
				});
				var paneCssClass = 'content_tab_pane';
				if (!!videoItem) {
					paneCssClass += ' video_pane';
					tabGroup.swfDataObj = [];
				}
				var paneContainer = new Element('div', {
					id	 : 'content_tab_pane_' + j,
					'class': paneCssClass });
				var tabArgs = {
					tabID	  : "tab_content_" + j,
					tabLabel   : tabLabels[j-1],
					content	: paneContainer
				};
				if (!!videoItem) {
					var vidContainerID = 'content_tab_pane_' + j;
					tabArgs.afterChangeHandler = function(swfDataObj) {

						var so = new SWFObject("/media/flash/sppvideo/sppVideo.swf", "sppVideo", "657", "421", "9.0.45", "#FFFFFF");
						so.addVariable("assetsDomain", "/media/flash/sppvideo/");
						so.addParam("allowFullScreen", "true");
						so.addParam("scale", "noscale");
						so.addParam("wmode", "opaque");
						so.write(vidContainerID);
						productPage.getVideoData = function() {
							return swfDataObj;
						}
					}.curry(tabGroup.swfDataObj);
				}
				// create the tab & pane
				var productTab = self.productTabs.createTab(tabArgs);
				// iterate through each content item
				tabGroup.each(function(tabData, idx){
					tabData.url_domain = window.URL_DOMAIN;
					tabData.url_domain_http = window.URL_DOMAIN_HTTP;
					if (!videoItem) {
						templatefactory.get(productPage.TEMPLATE_DIR + 'tabbed_content_item').evaluateCallback({
							object   : tabData,
							callback : function(pCon, tData, i, html) {
								pCon.insert(html);
								// toggle display of text or link
								if (tData.LINK_URL === null) {
									pCon.select('.header_link')[i].hide();
									pCon.select('.header_text')[i].show();
								} else {
									pCon.select('.header_link')[i].show();
									pCon.select('.header_text')[i].hide();
								}
								if (tData.LINK_COPY === null) {
									pCon.select('.tabbed_content_link')[i].hide();
								}
							}.curry(paneContainer, tabData, idx)
						});
					} else {
						if (tabData.LINK_URL) {
							tabGroup.swfDataObj.push({
                                id : idx,
                                coremetricsmethod : "cmCreatePageviewTag",
                                coremetricscatid  : "CAT532",
                                coremetricspageid : "SlideName1",
                                thumbnailimage	  : tabData.MEDIA,
                                video			  : tabData.LINK_URL,
                                linktitle		  : tabData.LINK_COPY,
                                title			  : tabData.TITLE,
                                description	      : tabData.DESCRIPTION,
                                displayorder	  : tabData.DISPLAY_ORDER,
                                product           : self.productData.PRODUCT_NAME
							});
						}
					}
				});
			}
		}
	},
	initBazaarVoice: function(productData) {
		var iframeSrc = "http://reviews.clinique.com/3813/" +
				productData.PRODUCT_ID +
				"/reviews.htm?format=embedded";
		var bvIframe = new Element("iframe", {
			id	   : "BVFrame",
			name	 : "BVFrame",
			src	  : iframeSrc
		});
		bvIframe.setStyle({
			visibility: "hidden",
			width   : "1px",
			height  : "1px",
			position: "absolute",
			left	: "-999px",
			top	 : "-999px"
		});
		$(document.body).insert(bvIframe);
		//
		// confirm that BV reviews loaded into the iFrame
		// if not, redirect the iframe to the logging URL
		window.BVisLoaded = false;
		bvIframe.observe('load', function() {
			if (!window.BVisLoaded) {
				var ele = document.getElementById("BVFrame");
				if (ele) {
					var page = ele.src;
					ele.src='http://reviews.clinique.com/logging?page=' + escape(page);
				}
			}
		});
	}
});


productPage.SkuMenu = Class.create( productPage.Widget, {

	initialize: function(args) {
		this.filename = "quickshop_price_select_single";
		this.menuContainerID = "sku_select_container";
		this.menuContainerNode = null;
		this.formSelector = "form#sku_options_form";
		this.productData = {};
		this.htmlSelectID = 'sku_select';
		this.htmlSelectNode = null;
		Object.extend(this, args || {});
		var self = this;
		this.register();

		if (this.menuContainerNode === null) {
			this.menuContainerNode = $(this.menuContainerID);
		}
		var skus = this.productData.skus;
		if (productPage.validateSkusArray(skus)) {
			var prod_skus_length = this.productData.skus.length;
			if (prod_skus_length > 1)  {
				var fieldObj = this.findMenuField(this.productData.skus);
				if (fieldObj){
					this.filename = "quickshop_price_select_multi";
					this.productData.selectLabel = fieldObj.label;
					this.selectField = fieldObj.field;
				}
			}
			templatefactory.get(this.filename).evaluateCallback({
				object: self.productData,
				callback: function(html) {
					self.menuContainerNode.update(html);
					if (prod_skus_length > 1)  {
						self.htmlSelectID = self.appendSuffix(self.htmlSelectID);
						self.htmlSelectNode = self.getChildNode(self.htmlSelectID);
						self.initMenu();
						self.htmlSelectNode.fire("skuMenu:loaded");
					}
				}
			});
		}
	},
	initMenu: function() {
		var self = this;
		var skus = this.productData.skus;
		if (productPage.validateSkusArray(skus) && this.selectField) {
			// include price in the text of the select options if there
			// is more than one unique price in the Array of SKUs
			var includePrice = false;
			if (skus.pluck("PRODUCT_PRICE").uniq().length > 1) {
				includePrice = true;
			}
			skus.each(function(sku, i) {
				var txt = sku[self.selectField];
				if (includePrice) {
					txt += ' ' + sku.FORMATTED_PRICE;
				}
				var opt = new Element('option', {value: sku.SKU_ID}).update(txt);
				self.htmlSelectNode.insert(opt);
			});
			niceform_init();
			//this.displayTos(this.productData.skus[0]);
			self.htmlSelectNode.observe('change', function(evt) {
				var sku_id = $F(evt.target);
				if (typeof(sku_id) !== "undefined" && sku_id.length > 0) {
					var sku = skus.detect(function(sku) {
						return sku.SKU_ID === sku_id;
					});
				}
				evt.target.fire("select:sku", sku);
			});
			this.viewContainerNode.observe('select:sku', function(evt) {
				if (evt.target !== self.htmlSelectNode) {
					self.selectSku(evt.memo);
				}
			});
		}
	},
	selectSku: function(skuData) {
		var self = this;
		var slct = this.htmlSelectNode;
		var optsArray = $A(slct.options);
		var opt = optsArray.detect(function(opt, idx) {
			if (opt.value === skuData.SKU_ID) {
				return opt;
			}
		});
		slct.selectedIndex = optsArray.indexOf(opt);
		selectMe(this.htmlSelectID, optsArray.indexOf(opt), selects.indexOf(slct), false);
	},
	displayTos: function(sku) {
		//
		// insert "Out of Stock" msg
		if (productPage.isTos(sku)) {
			var txt = "   " + productPage.TOS_MSG;
			var span = new Element('span', {'class': 'error tos'}).update(txt);
			$$(this.formSelector + ' .center')[0].insert(span);
		}
	},
	findMenuField: function(skus) {
		var fields = [
			{ label: 'Concern', field: 'STRENGTH' },
			{ label: 'Shade', field: 'SHADENAME' },
			{ label: 'Size', field: 'PRODUCT_SIZE' }
		];
		var countUnique = function(field) {
			return skus.pluck(field).uniq().length;
		}
		for (var i=0, len=fields.length; i<len; i++) {
			if (countUnique(fields[i].field) > 1) {
				return fields[i];
			}
		}
		return null;
	}
});


productPage.ReplenishMenu = Class.create( productPage.Widget, {

	initialize: function(args) {
		var self = this;
		this.containerID = '';
		this.skuData = {};
		this.filename = 'replenish_menu';
		this.htmlSelectID = 'replenish_select';
		Object.extend(this, args || {});
		this.register();

		callbackFn = function(html) {
			self.getChildNode(self.containerID).update(html);
			self.htmlSelectID = self.appendSuffix(self.htmlSelectID);
			niceform_init();
			self.htmlSelectNode = self.getChildNode(self.htmlSelectID);
			self.htmlSelectNode.observe('change', function(evt) {
				var replenish_val = $F(evt.target);
				evt.target.fire("select:replenish", replenish_val);
			});
		};
		templatefactory.get(productPage.TEMPLATE_DIR + this.filename).evaluateCallback({
			object: self.skuData,
			callback: callbackFn
		});
	}
});


productPage.ShadePicker = Class.create( productPage.Widget, {

	pickerContainerID: "",
	pickerContainerNode : null,
	filename: "shade_picker",
	productData: {},

	initialize: function (args) {
//		console.log("ShadePicker#initialize");
		var self = this;
		this.pickerContainerID = "";
		this.pickerContainerNode = null;
		this.productData = {};
		this.cellsPerRow = 4;
		this.includeFilters = true;
		Object.extend(this, args || {});
//		console.log(this.cellsPerRow);
		var skus = this.productData.skus;
		if (!productPage.validateSkusArray(skus)) {
			return null;
		}
		this.register();
		this.pickerContainerNode = this.getChildNode(this.pickerContainerID);
//		console.log(this.pickerContainerID);

		var callbackFunc = function() {
//			console.log("ShadePicker callbackFunc");
			// give each select menu in the Filters a unique ID
			// so that the niceforms get formatted
			var filterSelects = self.getChildNode('shadetable-filter-controls').select('select');
			filterSelects.each(function(slct){
				self.appendSuffix(slct);
			});
			self.table = new productPage.ShadePicker.Table({
				tableContainerID : "swatch_table_container",
				viewContainerID  : self.viewContainerID,
				tableData		: self.productData.skus,
				cellsPerRow	  : self.cellsPerRow,
				includeFilters   : self.includeFilters,
				categoryID	   : self.categoryID,
				menuContainerNode	   : self.getChildNode('filter-toolbar'),
				filterMenuContainerNode : self.getChildNode('shade_filter_container')
			});
			self.detail = new productPage.ShadePicker.Detail.Inline({
				viewContainerID : self.viewContainerID,
				shadeDetailContainerID: "swatch-container",
				skuData : self.productData.skus[0]
			});
			self.initListeners();
		};

		templatefactory.get(productPage.TEMPLATE_DIR + self.filename).evaluateCallback({
			object: self.productData,
			callback: function(html) {
				self.pickerContainerNode.update(html);
				callbackFunc();
			}
		});
	},

	initListeners: function() {
		var self = this;
		self.onState = false;
		this.pickerContainerNode.observe('swatch:mouseover', function(evt) {
			self.onState = true;
			if (self.mouseoutTimeout) {
				clearTimeout(self.mouseoutTimeout);
			}
			self.detail.display(evt.memo);
		});
		var displaySelected = function() {
			if (!self.onState) {
				self.detail.display(self.detail.selectedSkuData);
			}
		}
		this.pickerContainerNode.observe('swatch:mouseout', function(evt) {
			self.onState = false;
			self.mouseoutTimeout = setTimeout(displaySelected, 250);
		});
		this.viewContainerNode.observe('select:sku', function(evt) {
			self.detail.selectSku(evt.memo);
			var tbl = self.table.getTableNode();
			if (tbl !== evt.target) {
//				console.log("tbl !== evt.target");
				self.table.selectSku(evt.memo);
			}
		});
	}

});

var getFoundationAnswers = function() {
	if ( typeof foundationAnswers == "object" ) {
		return foundationAnswers;
	}
	return null;
}

productPage.ShadeTableQueue = [];
productPage.ShadePicker.Table = Class.create( productPage.FilterTable, {
	//
	// this function iterates through an array of shade objects,
	// builds a shade table, and renders it on the page
	initialize: function ($super, args) {
		this.tableContainerID = "";
		this.tableContainerNode = null;
		this.tableID = "table-swatches";
		this.filename = productPage.TEMPLATE_DIR + (this.filename || "shade_table_cell");
//		console.log('productPage.ShadePicker.Table: ' + this.filename);
		this.swatchWidth = this.swatchWidth || 88;
		this.swatchHeight = this.swatchHeight || 23;
//		if (typeof this.includeScrollbar === "undefined") {
//			this.includeScrollbar = true;
//		}
		if (typeof this.includeSort === "undefined") {
			this.includeSort = false;
		}
		$super(args);
		//
		// unlike other productPage.Widgets, ShadePicker.Table and its child classes
		// share the same Queue for instances
		this.constructor.instances = productPage.ShadeTableQueue;
		this.register();
		this.build();
		this.initFilterMenus(this.tableData);
	},

	build: function() {
		var self = this;
		this.tableContainerNode = this.getChildNode(this.tableContainerID);
		if (!this.tableContainerNode) {
			return null;
		}
		var tbl = new Element("table", {id:this.tableID} );
		var tbod = new Element("tbody", {id:"tbody-swatches"} );
		tbl.insert(tbod);
		this.tableContainerNode.update(tbl);
//		console.log(this.appendSuffix(this.tableID));
		this.tableID = this.appendSuffix(this.tableID);
		var trow;
		var skus = this.tableData;

		this.skusToDisplay = skus.select(function(s) {
			return !!s.display;
		});
		
		// if we get a product with only one sku and it aint a shade we dont display shade table
		if (this.skusToDisplay.length == 1 && getFoundationAnswers() && this.skusToDisplay[0].HEX_VALUE === null) {
			trow = new Element("tr");
			tbod.insert(trow);
			var tcell = new Element("td", {
				"style": "height:10px",
				"valign": "middle"
				});
			tcell.update("This product is suitable for all skin tones.");
			trow.insert(tcell);
			$('shade_head').hide();
			}
		else if (this.skusToDisplay.length > 0) {
			var maxShades = this.skusToDisplay.length;
			if ( this.maxShades && maxShades > this.maxShades ) {
				maxShades = this.maxShades;
			}
			for (var i=0; i<maxShades; i++) {
				//
				// create new row when necessary
				if (i % self.cellsPerRow === 0) {
					trow = new Element("tr");
					tbod.insert(trow);
				}
				templatefactory.get(self.filename).evaluateCallback({
					object: self.skusToDisplay[i],
					callback: function(trow, i, tblObj, html) {
						trow.insert(html);
						self.drawSwatches(trow.down('td', i%self.cellsPerRow), tblObj.skusToDisplay[i]);
//						self.initListeners();
						//
						// once all the swatches have been rendered, select the first one
						if (i + 1 == maxShades) {
							self.postRenderCallback();
						}
					}.curry(trow, i, self)
				});
			}
		//
		// if there are no SKUs, inform the user that there are no matches
		} else {
			trow = new Element("tr");
			tbod.insert(trow);
			if ( getFoundationAnswers() ) {
				var tcell = new Element("td", {
					"style": "height:10px",
					"valign": "middle"
				});
				tcell.update("This product is suitable for all skin tones.");
				trow.insert(tcell);
				$('shade_head').hide();
				}
			else{
				var tcell = new Element("td", {
					"style": "height:200px",
					"valign": "middle"
				});
				tcell.update("No Shades could be found that match these criteria.");
				}
		}
	},
	initListeners: function() {
		var self = this;
		this.viewContainerNode.observe('select:sku', function(evt) {
			self.selectSku(evt.memo);
		});
	},
	getTableNode: function() {
		return this.getChildNode(this.tableID);
	},
	selectSku: function(skuData) {
		//var cells = this.getChildNode(this.tableID).select("td");
		var tbl = this.getChildNode(this.tableID);
		if ( tbl ) {
			var cells = tbl.select("td");
			cells.each(function(cell){
				$(cell).removeClassName('active');
				if (cell.skuID === skuData.SKU_ID) {
					$(cell).addClassName('active');
				}
			});
		}
	},
	initFilterMenus: function(skus) {
		var self = this;
		var removeMenus = function() {
			if (self.menuContainerNode) {
				self.menuContainerNode.remove();
			}
		};
		if (!self.includeFilters) {
			removeMenus();
		} else {
			if (this.categoryID && productPage.shadeFilterMenus.get(this.categoryID)) {
				var menuMetaData = productPage.shadeFilterMenus.get(this.categoryID);
				self.createFilterMenus({
					filterMenuContainerNode : self.filterMenuContainerNode,
					tableData	: skus,
					menuMetaData : menuMetaData,
					menuWidth	: 100 });
			} else {
				removeMenus();
			}
		}
	},
	initScroller: function() {
		var self = this;
		self.scroller = null;
		if(self.getChildNode('scroll-container')) {
			var scrollcontent = self.getChildNode('scrollcontent');
			var handle = self.getChildNode('handle');
			var track = self.getChildNode('track');
			// set the elements back to their pre-Control.Scroller states
			[scrollcontent,
					handle,
					track].each(function(ele){
				ele.show();
			});
			handle.setStyle({top: '0px'});
			scrollcontent.setStyle({marginTop: 0, height: ''});
			// create a new Control.Scroller object
			self.scroller = new Control.Scroller(
			scrollcontent,
			handle,
			track, {
				up: "button-up",
				down: "button-down",
				visibleHeight: 260
			});
		}
	},
	drawSwatches : function(cellNode, skuData) {
		var self = this;
		var s = skuData;
		if (s.HEX_VALUE === null) {
			return;
		}
		cellNode.skuID = s.SKU_ID;
		var swatchContainerNode = $(cellNode).down('div');
		var linkNode = $(cellNode).down('a');
		var hexVals = s.hex_value_string.split(',');
		
		// Make sure container has the right size
		swatchContainerNode.style.width = self.swatchWidth + "px";
		swatchContainerNode.style.height = self.swatchHeight + "px";
		cellNode.style.width = self.swatchWidth + "px";

		var swatchShadeWidth = Math.ceil(self.swatchWidth/hexVals.length);

		var defaultStyle = self.getSwatchBaseStyle(cellNode, skuData, hexVals);

		for (var i=0; i<hexVals.length; i++) {
			var d = new Element("div", {});
			d.setStyle(Object.extend(defaultStyle, {
				left: (swatchShadeWidth * i) + "px",
				width: swatchShadeWidth + "px",
				height: self.swatchHeight + "px",
				zIndex: 10, //need a z-index to make IE6 rollover state work
				backgroundColor: hexVals[i] }));
			swatchContainerNode.insert(d);
		}

		var don = new Element("div", { 'class':'onstate' });
		don.setStyle({
				width: self.swatchWidth -4 + "px",
				height: self.swatchHeight -4 + "px",
				display: 'none'
		});
		swatchContainerNode.insert(don);
		swatchContainerNode.addClassName("swatch-container");
		self.onState = "";
		cellNode.observe('mouseover', function(skuData, evt) {
			linkNode.fire("swatch:mouseover", skuData);
//			this.toggleClassName('over');
			self.onState = skuData.SKU_ID;
			this.addClassName('over');
		}.curry(s));

		cellNode.observe('mouseout', function(skuData, evt) {
			linkNode.fire("swatch:mouseout", skuData);
			self.onState = "";
			var offState = function() {
				if (self.onState != skuData.SKU_ID) {
					cellNode.removeClassName('over');
				}
			};
			setTimeout(offState, 10);
		 }.curry(s));
		linkNode.observe('click', function(skuData, evt) {
			linkNode.fire("swatch:click", skuData);
			var tbl = self.getTableNode();
			if (tbl) {
				tbl.fire("select:sku", skuData);
			}
			self.selectSku(skuData);
			evt.preventDefault();
		}.curry(s));
//		cellNode.observe('swatch:mouseover', function(evt) {
//		});
//		cellNode.observe('swatch:mouseout', function(evt) {
//			this.toggleClassName('over');
//		});
	},
	getSwatchBaseStyle: function(cellNode, skuData, hexVals) {
		var s = skuData;
		var defaultStyle = {
			position: "absolute",
			top: "0px",
			backgroundImage: "none"
		};

		// Bad data - some skus are missing the smoosh path.  Bummer.
		if ( !s.smoosh_path )
			return defaultStyle;
        
        var swatchSrc;
        if ( s.swatch_source ) {
            swatchSrc = s.swatch_source;
        } else {
    		var swatchVals = s.smoosh_path.split(',');
    		swatchSrc = swatchVals[0];
    		// Create the thumbnail SRC by inserting the number of swatches
    		// and trimming extra characters if necessary
    		// e.g. "/images/swatches/duo_02_ns_ng_t.png"
    		// ---> "/images/swatches/02_ns_ng_2.png"
    		var re = /^(\/(?:[\w_\-]+\/)+)(?:.*_)?(\d{2}[a-z]*_\w{2}_\w{2})(?:_\w+)?(\.\w+)$/;
    		var reResult = re.exec(swatchSrc);
    		if ( !reResult )
    			return defaultStyle;
    		swatchSrc = reResult[1] + reResult[2] + "_" + hexVals.length +  reResult[3];
        }

		defaultStyle.backgroundImage = swatchSrc;
		if (/MSIE (\d+\.\d+)/.test(navigator.userAgent) && parseFloat(RegExp.$1) < 7) {
			defaultStyle.filter = "progid:dximagetransform.Microsoft.AlphaImageLoader(src='" +
							  defaultStyle.backgroundImage + "', sizingMethod='image')";
			defaultStyle.backgroundImage = "none";
		} else {
			defaultStyle.backgroundImage = productPage.bgImgAttr(defaultStyle.backgroundImage);
		}
		return defaultStyle;
	},
	postRenderCallback : function() {
		this.selectSku(this.skusToDisplay[0]);
		// initialize scroll container
		this.initScroller();
	}
});


productPage.ShadePicker.Table.Small = Class.create( productPage.ShadePicker.Table, {
	initialize: function($super, args) {
		this.includeFilters = false;
		this.filename = 'shade_table_small_cell';
		this.swatchWidth = 31;
		this.swatchHeight = 8;
		this.includeScrollbar = false;
		$super(args);
		this.getChildNode(this.tableID).addClassName("foundation-finder-populated");
	},
	getSwatchBaseStyle: function(cellNode, skuData, hexVals) {
		var defaultStyle = {
			position: "absolute",
			top: "0px"
		};
		return defaultStyle;
	}
});


productPage.ShadePicker.Table.ColorFinder = Class.create( productPage.ShadePicker.Table, {
	initialize: function ($super, args) {
		var self = this;
		this.filename = "colorfinder-cell"; // productPage.TEMPLATE_DIR +
		this.cellsPerRow =6;
		this.includeSort = false;
		$super(args);
	},
	postRenderCallback: function() {
		// lock in overlay height
		var wrapperNode = $(productPage.OVERLAY_CONTAINER_ID);
		wrapperNode.style.height = wrapperNode.offsetHeight + 'px';
	}

});

productPage.ShadePicker.Detail = Class.create( productPage.Widget, {
	initialize: function (args) {
		// set default property values
		this.filename = this.filename || productPage.TEMPLATE_DIR + 'shade_thumb';
//		 console.log('productPage.ShadePicker.Detail: ' + this.filename);
		this.shadeDetailContainerID = "swatch-container";
		this.smooshContainerID = 'smoosh_container';
		this.shadeDetailContainerNode = null;
		this.smooshContainerNode = null;
		this.skuData = {};
		this.selectedSkuData = {};
		this.smooshPanels = {};
		this.tosNode = null;
		// mix in instance constructor parameters
		Object.extend(this, args || {});
		this.shadeDetailContainerNode = this.getChildNode(this.shadeDetailContainerID);
		this.initDetailPane(this.skuData);
	},
	initDetailPane: function(s) {
		var self = this;
		templatefactory.get(this.filename).evaluateCallback({
			object: s,
			callback: function (s, html) {
				self.shadeDetailContainerNode.update(html);
				for (var i=0; i<4; i++) {
					self.smooshPanels[i] = self.getChildNode('smoosh_panel_' + i);
				}
				self.renderCallback(s);
			}.curry(s)
		});
	},
	renderCallback: function(s) {
		var self = this;
		this.descriptionNode = this.getChildNode('shade_details');
		this.tosNode = this.getChildNode("smoosh_btn_tos");
		this.shadeNameNode = this.getChildNode("shade_name");
		this.limitedNode = this.getChildNode("limited");
		this.addButton = addButton({
			"domNode" : this.getChildNode("shaded_add_link"),
			"progressNode" : this.getChildNode("shaded_add_progress"),
			onFailure: function() {this.shadeDetailContainerNode.fire("cart:add:fail");},
			onSuccess: function() {},
			cartArgs: {
				"sku_id" : s.SKU_ID,
				"shopping_id" : s.shopping_id
			 }
		});
		this.addButton.onSuccess = this.addButton.onSuccess.wrap(
			function(proceed) {
				proceed();
				self.shadeDetailContainerNode.fire("cart:add:success");
			}
		);
	},
	display: function(s) {
		var self = this;
		var shade_details = "";
		//if (s.SHADE_DESCRIPTION !== null) {
		if (s.SHADE_DESCRIPTION) {
			shade_details += "<p>" +
					s.SHADE_DESCRIPTION +
					"</p>\n";
		}
		//if (s.FINISH !== null) {
		if (s.FINISH) {
			shade_details += "<strong>Finish: </strong>" +
					s.FINISH +
					"<br />\n";
		}
		//if (s.COLOR_FAMILY_NAME !== null) {
		if (s.COLOR_FAMILY_NAME) {
			shade_details += "<strong>Colour Group: </strong>" +
					s.COLOR_FAMILY_NAME +
					"<br />\n";
		}
		self.descriptionNode.update(shade_details);
		if (s.HEX_VALUE === null) {
			return;
		}
		//
		// Now we display the correct thumb images with BG colors.
		// Because IE6's support for PNG transparency is insane,
		// we have to jump through some serious hoops.
		// 'smoosh_container' = the top-level container of all the smoosh image nodes
		// 'smoosh_panel_X' = smoosh_container contains these 4 child divs, which break it into
		//	  a 4-panel grid. They serve as containers for...
		// 'smoosh_panel_inner_X' = These divs are generated below and placed into
		//	  their corresponding parent nodes by index. The smoosh image is placed
		//	  in them by CSS background image (non-IE6) or filter (IE6). The divs are
		//	  positioned in their parents according to the number of hex values in
		//	  the SKU. These positions are stored in an associative array along with the
		//	  image names and BG colors.
		var hexVals = s.hex_value_string.split(',');
		var smooshVals = s.smoosh_path.split(',');
		//
		// break up smoosh path string, removing positional suffix
		var re = /^(.*_\wg)(?:_\w+)?(\.png)$/;
		var reResult = re.exec(smooshVals[0]);
		var styleArray = [
			[   // ONE SHADE
				{},
				{ left: "-75px" },
				{ top: "-75px" },
				{ left: "-75px", top: "-75px" }
			],
			[   // TWO SHADES
				{ backgroundImage: reResult[1] + "_t" + reResult[2] },
				{ backgroundImage: reResult[1] + "_t" + reResult[2],
				  left: "-75px" },
				{ backgroundColor: hexVals[1],
				  backgroundImage: reResult[1] + "_b" + reResult[2] },
				{ backgroundColor: hexVals[1],
				  backgroundImage: reResult[1] + "_b" + reResult[2],
				  left: "-75px" }
			],
			[   // THREE SHADES
				{ backgroundImage: reResult[1] + "_top" + reResult[2] },
				{ backgroundImage: reResult[1] + "_top" + reResult[2],
				  left: "-75px" },
				{ backgroundColor: hexVals[1],
				  backgroundImage: reResult[1] + "_bl" + reResult[2] },
				{ backgroundColor: hexVals[2],
				  backgroundImage: reResult[1] + "_br" + reResult[2] }
			],
			[   // FOUR SHADES
				{ backgroundImage: reResult[1] + "_tl" + reResult[2] },
				{ backgroundColor: hexVals[1],
				  backgroundImage: reResult[1] + "_tr" + reResult[2] },
				{ backgroundColor: hexVals[2],
				  backgroundImage: reResult[1] + "_bl" + reResult[2] },
				{ backgroundColor: hexVals[3],
				  backgroundImage: reResult[1] + "_br" + reResult[2] }
			]
		];
		//
		// Retrieve each panel. Create & append the child panel. Assign
		// browser-specific style attributes to the child.
		for (var i=0; i<4; i++) {
			var defaultStyle = {
				backgroundImage: reResult[1] + reResult[2],
				backgroundColor: hexVals[0],
				top  : "0px",
				left : "0px"
			};
			var styleObj = Object.extend(defaultStyle, styleArray[hexVals.length-1][i]);
			//
			// Test for IE<7. Browser sniffing is problematic, but it seems
			// like the only choice here.
			// the goal is to get the following line into the CSS of each inner div:
			// filter:progid:dximagetransform.Microsoft.AlphaImageLoader(src='/images/example.png', sizingMethod='image')
			if (/MSIE (\d+\.\d+)/.test(navigator.userAgent) && parseFloat(RegExp.$1) < 7) {
				styleObj.filter = "progid:dximagetransform.Microsoft.AlphaImageLoader(src='" +
								  styleObj.backgroundImage + "', sizingMethod='image')";
				styleObj.backgroundImage = "none";
			} else {
				styleObj.backgroundImage = productPage.bgImgAttr(styleObj.backgroundImage);
			}
			var innerDiv = new Element("div", {
					'class': 'smoosh_panel_inner',
					'id': 'smoosh_panel_inner_' + i });
			innerDiv.setStyle(styleObj);
			var spacerImg = new Element("img", {
					'src' : '/images/global/transparent.gif',
					'width' : '75',
					'height': '75' });
			innerDiv.update(spacerImg);
			self.smooshPanels[i].update(innerDiv);
		}
		self.shadeNameNode.update(s.SHADENAME);
		if (s.LIFE_OF_PRODUCT != 1) {
			self.limitedNode.style.display = "none";
		} else {
			self.limitedNode.style.display = "block";
		}
		self.tosMessage = new productPage.TosMessage({
			domNode : self.tosNode,
			skuData : s
		});
		this.addButton.cartArgs.sku_id = s.SKU_ID;
		this.addButton.cartArgs.shopping_id = s.shopping_id;
	},
	selectSku: function(s) {
		this.selectedSkuData = s;
		this.display(s);
	}
});

productPage.ShadePicker.Detail.Inline = Class.create( productPage.ShadePicker.Detail, {
	renderCallback: function($super, skuData) {
		$super(skuData);
		var self = this;
//		productPage.initReplenishButton(this.addButton, this.viewContainerNode);
		this.selectSku(skuData);
	},
	display: function($super, skuData) {
		$super(skuData);
	}
});

productPage.ShadePicker.Detail.ColorFinder = Class.create( productPage.ShadePicker.Detail, {
	initialize: function($super, args) {
		var self = this;
		this.filename = productPage.TEMPLATE_DIR + 'colorfinder-detail';
		$super(args);
//		this.viewContainerNode.observe('swatch:mouseover', function(evt) {
//		});
		this.viewContainerNode.observe('swatch:click', function(evt) {
			self.display(evt.memo, evt.target);
//			self.selectSku(evt.memo);
		});
//		this.viewContainerNode.observe('swatch:mouseout', function(evt) {
//		  self.shadeDetailContainerNode.style.display = "none";
//		});
	},
	selectSku: function(skuData) {},
	display: function($super, skuData, selectionNode) {
		var self=this;
		$super(skuData);
		// insert Product Name link
		var lnk = new Element("a", ({href: skuData.product.url}));
		lnk.insert(skuData.product.PRODUCT_NAME);
		$("product-name").update(lnk);
		$("shade_price").update(skuData.FORMATTED_PRICE);
		this.descriptionNode.insert("<strong>Coverage: </strong>" +
					skuData.product.COVERAGE + "<br />\n");
		var addBtnNode = this.getChildNode("shaded_add_link");
		this.shadeDetailContainerNode.style.display = "block";
		var startPos = selectionNode.positionedOffset();
		var leftOffset = 0;
		if (selectionNode.offsetWidth) {
			leftOffset = selectionNode.offsetWidth/2
		}
		this.shadeDetailContainerNode.style.top =
				startPos.top - this.shadeDetailContainerNode.getComputedHeight() - 4 + 'px';
		this.shadeDetailContainerNode.style.left = startPos.left + leftOffset + 'px';
		$('colorfinder-detail-close-link').observe('click', function() {
			self.shadeDetailContainerNode.style.display = "none";
		})
	}
});

productPage.howToVideo = {};
productPage.howToVideo.launchQuickshop = function(inputProductID) {
	var options = {};
	options.onHide = function() {
		var flashMovie = $('clinique-flash');
		if (flashMovie) {
		   flashMovie.javascriptToFlash('resumeVideo');
		}
	};
	productPage.launchQuickshop(inputProductID, options);
}

productPage.launchQuickshop = function(inputProductID, options) {
//	alert("productPage.launchQuickshop()");
//	console.log(inputProductID);
	if (!Object.isString(inputProductID)) {
		return;
	}
	var params = {"product" : [inputProductID]};
	params = Object.extend ( params, productPage.DETAIL_VIEW_QUERY.PRODUCT_FIELDS );
	params = Object.extend ( params, productPage.DETAIL_VIEW_QUERY.SKU_FIELDS );
	params = [params];
	jsonrpc.fetch(
		"prodcat.byid", //method
		params, //params
		{
			onSuccess: function (r) {
				var responseData = r.responseText.evalJSON(true);
				if (typeof responseData[0].result === "object") {
					var responseProductData = responseData[0].result;
					var responseProductDataHash = $H(responseProductData);
					var productDataObject = new CatProdPageData();
					productDataObject.fillinData(responseProductDataHash);

					var args = {};
					args.productData = productDataObject.getProducts()[0];
					args.productData.skus = args.productData.sku = productDataObject.getSkus();

					mergeIntoGlobalCatProdData(productDataObject);
					if (options) {
						if(options.onHide) {
							args.onHide = options.onHide;
						}
						if (options.onAdd) {
							args.onAdd = options.onAdd;
						}
					}
					this.qsw = new productPage.ProductView.Quickshop(args);
					this.qsw.build();
				}
			},
			onFailure: function() {}
		});
};


/**
 * QuickshopLauncher is a mixin for the ProductThumb classes.
 * It handles the rollover and onclick events for product images
 * that launch quickshop windows.
 */
productPage.QuickshopLauncher = {
	initQuickshopLinks: function(args) {
		var self = this;
		var defaultArgs = {
			containerNode	  : null,
			quickshopLinkClass : 'quickshop-link',
			quickshopIconClass : 'quick-info-link',
			thumbImageClass	: 'frame',
			doPositionRollover : false
		}
		var options = Object.extend(defaultArgs, args || {});
		if (options.containerNode) {
			var match = options.containerNode.descendants().find( function(ele) {
				return ele.hasClassName(options.quickshopIconClass);
			});
			if (match) {
				var sImg = options.containerNode.descendants().find( function(ele) {
					return ele.hasClassName(options.quickshopIconClass);
				});
				var bImg = options.containerNode.descendants().find( function(ele) {
					return ele.hasClassName(options.thumbImageClass);
				});

				// WDR: the parent of the quickshop icon needs to be relative positioned
				// so we can position the icon within it.  But we need to be non-positioned
				// in other places so the dashboard lays on top.  (In IE, positioning
				// screws up the zindex... oy...)  So here we get the parent so we can
				// set/unset the positioning style.

				var imgParent = $(bImg.parentNode);

//				console.log(bImg);
//				console.log(sImg);
				Event.observe(sImg, 'mouseover', function(){ imgParent.makePositioned(); sImg.show(); });
				Event.observe(sImg, 'click', function(){ imgParent.undoPositioned(); sImg.hide(); });
				Event.observe(bImg, 'mouseout', function(){ imgParent.undoPositioned(); sImg.hide(); });
				Event.observe(bImg, 'click', function(){ imgParent.undoPositioned(); sImg.hide(); });
				Event.observe(bImg, 'mouseover', function(){
					imgParent.makePositioned();
					self.positionRollover(sImg,bImg);
					sImg.show();
				});
			//
			// add click event that launches quickshop window
				var links = options.containerNode.descendants().findAll( function(ele) {
					return ele.hasClassName(options.quickshopLinkClass);
				});
				$A(links).each(function(lnk) {
					Event.observe(lnk, 'click', function(evt){
						self.initQuickshop();
						evt.preventDefault();
					});
				});
			}
		}
	},
	initQuickshop: function() {
		this.qsw = new productPage.ProductView.Quickshop({
			productData : this.productData
		});
		this.qsw.build();
	},
	positionRollover: function(qlinkImg,parentImg) {
		if ( this.doPositionRollover && qlinkImg && parentImg ) {
			var qlink = qlinkImg.down(".quick-info");
			var x = parentImg.getWidth();
			x = (x/2) - 43;
			var y = parentImg.getHeight();
			y = (y/3);
			qlink.style.left = x+'px';
			qlink.style.bottom = y+'px';
		}
	}
};

productPage.ProductThumb = Class.create( productPage.Widget, productPage.QuickshopLauncher, {
	/*
	WDR: these should be init'd in initialize, not here:
	container_node: null,
	product_name: null,
	quickshop_options_form_selector: "",
	addButton: null,
	productData: null,
	doPositionRollover : false,
	*/

	initialize: function(args) {
		this.container_node = null;
		this.product_name = null;
		this.quickshop_options_form_selector = "";
		this.addButton = null;
		this.productData = null;
		this.doPositionRollover = false;

		Object.extend(this, args || {}); // pass values from args into Product Thumb obj
		//
		// set up mouseover events to show quickshop link
		this.initQuickshopLinks({
			containerNode: this.container_node,
			doPositionRollover : this.doPositionRollover
		});
		var attrContainer = this.container_node.select('.attributes')[0];
		if (attrContainer) {
			// Coverage, Skin Types, Benefits, etc
			productPage.displayAttributes({
				containerNode : attrContainer,
				productData   : this.productData
			});
		}
	}
});

productPage.ProductThumb.Small = Class.create( productPage.Widget, productPage.QuickshopLauncher, {
	initialize: function(args) {
//		console.log(this);
		this.filename = 'product_thumb_small';
		this.productData = {};
		this.thumbContainerID = '';
		this.thumbContainerNode = null;
		this.addToBagLink = false;
		Object.extend(this, args || {});
		this.register();
		if (!this.thumbContainerNode) {
			this.thumbContainerNode = this.getChildNode(this.thumbContainerID);
		}
		this.productData = Object.extend(this.productData, {
			url_domain : window.URL_DOMAIN,
			url_domain_http : window.URL_DOMAIN_HTTP,
			skinTypeString: productPage.parseSkinTypes(this.productData.prod_skin_type_string),
			priceString: productPage.formatPriceRange(this.productData)
		});
	},
	initThumb: function() {
//		console.log("initThumb");
		var self = this;
		try {
			templatefactory.get(productPage.TEMPLATE_DIR + self.filename).evaluateCallback({
				object: self.productData,
				callback: function(html) {
					var dl = new Element ('dl', {"class": "thumb-75x75", "id": "mini-thumb" + self.idSuffix});
					dl.insert(html);
					self.thumbContainerNode.insert(dl);
					if (!productPage.displaySkinTypes()) {
						var skinTypeEle = dl.select(".type")[0];
						if (skinTypeEle) {
							skinTypeEle.hide();
						}
					}
					if ( self.addToBagLink ) {
						var quickshopLink = self.getChildNode('mini-thumb' + self.idSuffix).select(".grey-arrow")[0];
						if ( productPage.disableQuickAddProduct(self.productData.PRODUCT_ID) ) {
							quickshopLink.hide();
						} else {
							quickshopLink.update('Add to bag');
							quickshopLink.observe('click', function(evt) {
								// Make sure we have something to add
								if (self.productData && productPage.validateSkusArray(self.productData.skus)) {
									var sid = self.productData.skus[0].SKU_ID;
									var addid = cart.add(
										{ sku_id   : sid,
										  increment: true },
										{ success  : function (t) { cart.userNotify(addid); } }
									);
								}
								evt.preventDefault();
							});
						}
					} else {
						self.initQuickshopLinks({
							containerNode: dl,
							doPositionRollover : false
						});
						// Use quickshop link
//					  var quickshopLink = self.getChildNode('mini-thumb' + self.idSuffix).select(".grey-arrow")[0];
//					  quickshopLink.observe('click', function(evt) {
//						  self.qsw = new productPage.ProductView.Quickshop({
//							  productData	 : self.productData
//						  });
//						  self.qsw.build();
//						  evt.preventDefault();
//					  });
					}
					if (self.callback) {
						self.callback();
					}
				}
			});
		} catch (err) {
			console.log(err);
		}
	}
});

productPage.displayAttributes = function(args) {
	if (args.containerNode) {
		var ul = new Element('ul', {'class':'attributes'});
		args.containerNode.update(ul);
		var p = args.productData;

		var fields = [
			{ label: 'Skin Types',
			  field: productPage.parseSkinTypes(p.prod_skin_type_string, {format: 'long'})
			},
			{ label: 'Formula', field: p.FORMULA },
			{ label: 'Coverage', field: p.COVERAGE },
			{ label: 'Benefits', field: p.BENEFITS },
			{ label: 'Concern', field: p.SKIN_CONCERN }
		];
		if (!productPage.displaySkinTypes()) {
			fields.shift();
		};
		for (var i=0, len=fields.length; i<len; i++) {
			if (fields[i].field && fields[i].field.length > 0) {
				// WDR: "field" can be either a string or an array of strings.
				var strField = ( typeof fields[i].field == 'object' ? $A(fields[i].field).join(', ') : fields[i].field );
				var strEle = new Element('strong').update(fields[i].label + ': ');
				var li = new Element('li').update(strEle);
				li.insert(strField);
				ul.insert(li);
			}
		}
	}
};

productPage.displaySkinTypes = function () {
	// check the Category ID & Supercat ID against the list of IDs that don't use Skin Type
	var match = false;
	if (CATEGORY_ID && SUPERCAT_ID) {
		match = productPage.hideSkinTypeGroups.find(function(groupID) {
		   return groupID === CATEGORY_ID || groupID === SUPERCAT_ID;
		});
	}
	return !match;
}

/*
* This class takes an array of product objects and uses the template-loader to
* return a <div> containing <dl> elements representing each product.
* It's used by accordion controls throughout the site.
*/
productPage.miniThumbTable = Class.create( productPage.Widget, {
	initialize: function(args) {
//		console.log("miniThumbTable#initialize" );
		this.containerID = '';
		this.containerNode = null;
		this.tableData = [];
		this.filename = 'product_mini_thumb';
		this.addToBagLink = false;

		Object.extend(this, args || {});
		this.register();
		this.containerNode = $(this.containerID);
		if (this.maxItems === undefined) {
			this.maxItems = this.tableData.length;
		}
//		console.log(this.maxItems);
//		this.loadTemplates(this.tableData);
		this.buildPanes(this.tableData);
	},
	buildPanes: function(productData) {
		var self = this;
//		var container = new Element ('div', { "class": "mini-thumb-container" });
//		this.containerNode.insert(container);

		// WDR: sort on display order
		productData = productData.sortBy(function(p) { return p.DISPLAY_ORDER ? p.DISPLAY_ORDER : 0; });
		productData.each(function(product, idx) {
			if (self.maxItems && idx >= self.maxItems) {
				throw $break;
			}
			var lastPane = (idx + 1 == self.maxItems);
			self.buildPane({
				productData: product,
				wrapperID: 'mini-thumb-wrapper-' + idx,
				paneContainerNode: self.containerNode,
				lastPane: lastPane
			});
			var ruleCssClass = (idx + 1 < self.maxItems ? "hr clear" : "clear");
			var rule = new Element('div', {"class": ruleCssClass});
			self.containerNode.insert(rule);

		});
	},
	buildPane: function (args) {
		var self = this;
		var wrapperNode = new Element ('div', {"id": args.wrapperID });
		args.paneContainerNode.insert(wrapperNode);
		var sml = new productPage.ProductThumb.Small({
			productData: args.productData,
			addToBagLink: this.addToBagLink,
			thumbContainerID: args.wrapperID,
			thumbContainerNode: wrapperNode,
			// WDR: took this out and instead use args.paneContainerNode - reselecting has issues in IE.
			//thumbContainerNode: $$(self.getSelectorPrefix() + '#' + self.containerID + ' #' + args.wrapperID)[0],
			callback : function() {
				if (args.lastPane) {
					self.containerNode.fire("productPage.miniThumbTable:loaded", self);
//					console.log("productPage.miniThumbTable:loaded");
				}
			}
		});
		sml.initThumb();
	}
});


productPage.TosMessage = Class.create({
	initialize: function(args) {
		Object.extend(this, args || {});
		if(Object.isElement(this.domNode)) {
			if (this.skuData) {
				this.toggleDisplay(this.skuData);
			}
			var self = this;
			document.observe('select:sku', function(evt) {
				self.toggleDisplay(evt.memo);
			});
		}
	},
	toggleDisplay: function(skuData) {
		if(Object.isElement(this.domNode)) {
			if (productPage.isTos(skuData)) {
				this.domNode.update(productPage.TOS_MSG);
				this.domNode.removeClassName('hidden');
			} else {
				this.domNode.update('');
				this.domNode.addClassName('hidden');
			}
		}
	}
});

productPage.ProductsTable = Class.create( productPage.FilterTable, {
	initialize: function($super, args) {
		$super(args);
		this.build();
	},
	startIndex: 0,
	//
	// this function iterates through an array of product objects,
	// builds a product table, and renders it on the page
	build: function () {

		this.container_div = this.clearContainer();
		var prods = this.tableData.select(function(p) {
			return p.display && productPage.validateSkusArray(p.skus);
		});
		if ( this.sortDisplayOrder ) {
			// WDR: sort on display order
			prods = prods.sortBy(function(p) { return p.DISPLAY_ORDER ? p.DISPLAY_ORDER : 0; });
		}
		var ul;
		var self = this;
		if (this.maxItems === undefined) {
			this.maxItems = prods.length;
		}
		// make sure startIndex is numeric
		if ( !this.startIndex ) {
			this.startIndex = 0;
		}
		
		if (prods.length > 0 && this.startIndex < prods.length) {

			var itemIndex = this.startIndex;  // index of item within a row
			var rowIndex = 0;
			var prodsDone = 0;
			var prodsToDo = ( self.maxItems ? self.maxItems : prods.length );
			
			for ( var i=this.startIndex; i < prods.length && prodsDone < prodsToDo; i++,prodsDone++ ) {
				var prod = prods[i];
				
				var average_rating = ( typeof prod.AVERAGE_RATING == "undefined" ? 'null' : prod.AVERAGE_RATING );
				var review_count = ( typeof prod.TOTAL_REVIEW_COUNT == "undefined" ? 'null' : prod.TOTAL_REVIEW_COUNT );
				var recommended_percent = ( typeof prod.RECOMMENDED_PERCENT == "undefined" ? 'null' : prod.RECOMMENDED_PERCENT );
				var ratings = ( typeof prod.ONLY_RATINGS_COUNT == "undefined" ? 'null' : prod.ONLY_RATINGS_COUNT );
                
                var prodattributes = average_rating + "-_-" +review_count+ "-_-" +recommended_percent+ "-_-" +ratings;

				// coremetrics tagging
				//console.log(prod);
				addMppTag(prod.PRODUCT_ID,prod.PRODUCT_NAME,prod.PARENT_CAT_ID,prodattributes);
  				//addMppTag(prod.PRODUCT_ID,prod.PRODUCT_NAME,prod.PARENT_CAT_ID);

				var templt_args = {
					li_class : "",
					price_string : "",
					skinTypeString: "",
					url_domain : window.URL_DOMAIN,
					url_domain_http : window.URL_DOMAIN_HTTP };
				//
				// every Xth <LI> gets class="last"
				templt_args.li_class = (i % self.cellsPerRow == (self.cellsPerRow-1)) ? "thumb last" : "thumb";
				
				if ( this.showShadeTable ) {
					templt_args.li_class += ( this.mode == 3 ? " thumb_shades_mini" : " thumb_shades" );
				}
				//
				// new row every X cells
				if (i % self.cellsPerRow === 0) {
					var ulClass = 'product-thumb-row';
					ulClass += ( rowIndex++ % 2 ? ' even-row' : ' odd-row' );
					self.ul = new Element("ul", {'class': ulClass});
					self.container_div.insert(self.ul);
					itemIndex = 0;
				}
				prod.lastItemFlag = false;
				
				// first <ul> gets "firstrow"
				if ( i == this.startIndex ) {
					self.ul.addClassName("firstrow");
				}

				//
				// last <UL> gets class="lastrow"
				if (prods.length - 1 === i || (self.maxItems && self.maxItems - 1 === i)) {
					self.ul.addClassName("lastrow");
					prod.lastItemFlag = true;
				}
				if (productPage.validateSkusArray(prod.skus)) {
				    // WDR: we're getting some products with empty skus.
				    // Probably a .net glitch, but clean up here to prevent an error
				    // from messing up the page.
				    prod.skus = prod.skus.findAll(function(sku) {
				        return ( sku ? true : false );
				    });
					prod.skus = prod.skus.sortBy(function(sku) {
						return parseFloat(sku.PRODUCT_PRICE);
					});
				}

				templt_args.ratingReviewString = prod.TOTAL_REVIEW_COUNT > 1 ? 'reviews' : 'review';
				templt_args.ratingDisplay = typeof prod.AVERAGE_RATING == 'number' && isFinite(prod.AVERAGE_RATING) ? 'block' : 'none';
				templt_args.ratingRounded = Math.round(prod.AVERAGE_RATING*10)/10;

				templt_args.price_string = productPage.formatPriceRange(prod);
				templt_args.skinTypeString = productPage.parseSkinTypes(prod.prod_skin_type_string, {format: 'numerals'});

				// Pick the file to use based on the mode setting.
				var templateFile = ( this.mode == 2 ? 'product_large' : 
									 this.mode == 3 ? 'product_thumb_shade_mini' :
									 'product_thumb' );

				templt_args.LARGE_PRODUCT_IMAGE =
						( this.productImages && this.productImages[prod.PRODUCT_ID] ?
						  this.productImages[prod.PRODUCT_ID] :
						  prod.LARGE_IMAGE );

				//
				// add constructed parameters that the template requires
				Object.extend(prod, templt_args);
				//
				// build HTML from template then instantiate ProductThumb object
				templatefactory.get(productPage.TEMPLATE_DIR + templateFile).evaluateCallback({
					object: prod,
					callback: function(self, prod, ul, i, html) {
						self.initThumb(self, prod, ul, i, html);
					}.curry(self, prod, self.ul, itemIndex)
				});
				
				itemIndex++;
			}
		}

	},
	initThumb: function(self, prod, ul, li_index, html) {
		var prod_skus_length = productPage.validateSkusArray(prod.skus) ? prod.skus.length : 0;
		ul.insert(html);
		var li = ul.select("li.thumb")[li_index];
		var thumb = new productPage.ProductThumb({
			container_node: li,
			quickshop_icon_class: "quick-info-link",
			thumb_image_class: "frame",
			quickshop_link_class: "quickshop-link",
			productData: prod,
			doPositionRollover: ( self.mode == 2 )
		});

		if ( self.showShadeTable ) {
			var shadeWrapper = li.down('.thumb_shade_wrapper');
			var shadeDiv = li.down('.thumb_shades');
			if ( shadeWrapper && shadeDiv ) {
				shadeWrapper.show();
				shadeDiv.id = self.containerID + prod.PRODUCT_ID + '-shade-table';
	
				// Only build a shade table if there are actually shades to show
				if ( prod.shaded && prod_skus_length > 0 ) {
					// If there is more than one sku and a heading is present, show it.
					var shadeHeading = shadeWrapper.down('.thumb_shade_heading');
					if ( shadeHeading && prod_skus_length > 1 ) {
						shadeHeading.show();
					}
					
					// How many shades should we show?
					var maxShades = prod_skus_length;
					if ( this.maxShades && this.maxShades < maxShades ) {
						maxShades = this.maxShades;
						var viewAll = shadeWrapper.down('.thumb_shade_viewall');
						if ( viewAll ) {
							var html = '<b>Top ' + maxShades + ' shades shown.</b> ' +
							           '<a href="' + prod.url_domain + prod.url + '" class="grey-arrow">' +
							           'Shop all ' + prod_skus_length + ' shades</a>';
							viewAll.update(html);
							viewAll.show();
						}
					}
						
					var shadeTableArgs = {
						tableData : prod.skus,
						tableContainerID: shadeDiv.id,
						viewContainerID : self.containerID,
						includeFilters  : false,
						cellsPerRow	 : 4,
						maxShades : maxShades,
						menuContainerNode	   : null,
						filterMenuContainerNode : null
					};
					
					if ( self.swatchWidth ) shadeTableArgs.swatchWidth = self.swatchWidth;
					if ( self.swatchHeight ) shadeTableArgs.swatchHeight = self.swatchHeight;
	
					thumb.shadeRangeTable = new productPage.ProductView.ShadeRange.Table(shadeTableArgs);
				} else if ( Prototype.Browser.IE && self.mode != 3 ) {
					// HACK!!
					// No shade table, but make a spacer so the add button
					// aligns on the bottom of the thumb
					shadeDiv.style.height = '110px';
					shadeDiv.style.width  = '180px';
				}
	
	   			var addDiv = li.down('.thumb_shade_add_button');
				addDiv.id = self.containerID + prod.PRODUCT_ID + '-add-button';
				
				if ( productPage.disableQuickAddProduct(prod.PRODUCT_ID) ) {
					addDiv.hide();
				} else if ( prod.shaded || prod_skus_length == 1 ) {
					// Set up the add button if there's a shade table or only one sku
					
					addDiv.show();
	   
                            //CoreMetrics 
                            mpp_product_tag(prod.PRODUCT_ID); // CoreMetrics

					thumb.addButton = addButton({
						domNode	  : addDiv.down(".shaded_add_link"),
						progressNode : addDiv.down(".shaded_add_progress"),
						cartArgs	 : {
							sku_id	  : prod.skus[0].SKU_ID,
							shopping_id : prod.skus[0].shopping_id
						}
					});
					
					shadeDiv.observe('select:sku', function(evt) {
						var skuData = evt.memo;
						thumb.addButton.cartArgs.sku_id = skuData.SKU_ID;
						thumb.addButton.cartArgs.shopping_id = skuData.shopping_id;
					});
				} else {
					// for multiple skus, change add to quickshop
					addDiv.update(this.makeQuickshopButton(prod,thumb));
					addDiv.show();
				}
			}
		}
		//
		// fire event when the last thumb has been rendered on the page
//		console.log(prod.lastItemFlag);
		if (prod.lastItemFlag) {
			self.container_div.fire("productPage.ProductTable:loaded", self);
//			console.log("productPage.ProductTable:loaded");
		}
	},
	makeQuickshopButton: function(prod,thumb) {
		
		var qsimg = new Image();
		qsimg.src = "/images/global/buttons/qs.gif";
		qsimg.alt = "Quick Shop";

		var qidiv = new Element('div',{
			'class':'quickshop-link quick-info',
			'id': 'quickview-link-'+prod.PRODUCT_ID,
			'productid':prod.PRODUCT_ID,
			'style': 'cursor:pointer;'
		});
		
		qidiv.observe('click', function(evt) {
			thumb.initQuickshop();
			evt.preventDefault();
		});

	
		qidiv.insert(qsimg);
		
		var qldiv = new Element('div',{'class':'quick-info-link'});
		qldiv.insert(qidiv);
		
		return qldiv;
	}
	
});

////////////////////////////////////////////////////////
/////// UTILITY FUNCTIONS
////////////////////////////////////////////////////////

productPage.loadProductData = function(args) {

	var params = Object.extend({"product" : args.productIDs},
			productPage.DETAIL_VIEW_QUERY.PRODUCT_FIELDS);
	params = Object.extend (params,
			productPage.DETAIL_VIEW_QUERY.SKU_FIELDS);
	params = [params];

	var addToGlobalData = function(newProductData) {
		if (CatProdData) {
			//
			// global CatProdData object is used by the cart object
			// so we better cram some data in there
			var skus = newProductData.data.get('sku').all;
			skus.each(function(s){
				CatProdData.data.get('sku').all.push(s);
			});
			var prods = newProductData.data.get('product').all;
			prods.each(function(p){
				CatProdData.data.get('product').all.push(p);
			});
		}
	};

	var tempData = new CatProdPageData(params, false, function() {
		args.callback(tempData);
//		addToGlobalData(tempData);
		mergeIntoGlobalCatProdData(tempData);
		tempData = {};
	} );

};

/*
* This class takes a product object and returns a string based
* on the FORMATTED_PRICE field(s) of that product's SKU(s). If there
* are multiple skus for a sized product, the skus are sorted and
* a range of lowest price - highest price is returned.
* (e.g, "$1.00 - $2.00")
*/
productPage.formatPriceRange = function(prod) {
	var price_string = '';
	if (productPage.validateSkusArray(prod.skus)) {
		var prod_skus_length = prod.skus.length;
		var uniquePricesCount = prod.skus.pluck('PRODUCT_PRICE').uniq().length;
		if (uniquePricesCount > 1)  {
			var sortedSkus = prod.skus.sortBy(function(s){return s.PRODUCT_PRICE;});
			price_string = sortedSkus[0].FORMATTED_PRICE;
			price_string += " - " + sortedSkus[prod_skus_length-1].FORMATTED_PRICE;
		} else {
			price_string = prod.skus[0].FORMATTED_PRICE;
		}
		return price_string;
	} else {
		return '';
	}
};

productPage.validateString = function(str) {
	if (typeof str === 'string' && str.length > 0) {
		return true;
	}
	return false;
};

//
// convenience function that prepends & appends text for background-image CSS property
productPage.bgImgAttr = function(path) {
	return "url('" + path + "')";
};


/*
* returns a comma-separated string of Skin Type descriptions.
* skin type string paramter must be formatted 0bxxxx, where x= 0 or 1.
* legal formats include 'numerals' or 'long'. 'numerals' is default.
*/
productPage.parseSkinTypes = function(skinTypeStr, args) {

	var options = {format: 'numerals'};
	Object.extend(options, args || {});
	//
	// skin type string should be formatted 0bxxxx, where x= 0 or 1.
	// if it is invalid, return
	var re = /0b[01]{4}/;
	if (!re.match(skinTypeStr)) {
		return;
	}

	var skinTypeDisplayTexts = {
		numerals: [ '1',
			'2',
			'3',
			'4' ],
		long : [ '1 - Very Dry to Dry',
			'2 - Dry Combination',
			'3 - Oily',
			'4 - Very Oily' ]
	};
	//
	// check to make sure that the format that was passed in is valid by
	// checking it against the keys in the skinTypeDisplayTexts hash
	var validFormats = $H(skinTypeDisplayTexts).keys();
	if (!validFormats.any(function(val) { return val === options.format; } )) {
		return;
	}

	var skinTypeDisplayText = skinTypeDisplayTexts[options.format];
	skinTypeStr = skinTypeStr.substring(2,6);
	if (skinTypeStr === '0000' || skinTypeStr === '1111') {
		return "All";
	}
	for (var i=3; i>-1; i--) {
		if (skinTypeStr.charAt(i) === "0") {
			skinTypeDisplayText.splice(i,1);
		}
	}
	return skinTypeDisplayText.join(', ');

};

/*
* tests a sku obj to see if it is out of stock.
* checks the INVENTORY_STATUS field.
*/
productPage.isTos = function(sku) {
//
//# INVENTORY STATUS CODES and shoppability
//# 1 = Active (shoppable)
//# 2 = Temporarily Out Of Stock (shoppable)
//# 3 = Coming Soon (shoppable or not, depending on brand)
//# 4 = Do Not Display (not displayed, except on dev)
//# 5 = Inactive (not shoppable, remove from cart)
//# 6 = Promotional (not shoppable, but not removed from cart)
//# 7 = Sold Out (not shoppable, remove from cart)
//# 8 = Promotional Sold Out (not shoppable, remove from cart)
//# 9 = Promotional Inactive (not shoppable, remove from cart)
//
//# DISPLAY STATUS CODES
//# 0 = not displayable, corresponds to INVENTORY STATUS 5
//# 1 = displayable, corresponds to INVENTORY STATUS 1, 2, 3, 6, 7
//# 2 = displayable on dev only, corresponds to INVENTORY STATUS 4
	return (sku && sku.INVENTORY_STATUS === 2);
};

productPage.initReplenishButton = function(addBtn, viewContainer) {
	addBtn.replenish = null;
	viewContainer.observe('select:replenish', function(evt) {
		addBtn.replenish = {
			days  : evt.memo,
			skuID : addBtn.cartArgs.sku_id
		};
	});
	addBtn.onSuccess = addBtn.onSuccess.wrap(
		function(proceed) {
			if (addBtn.replenish !== null) {
				var refills = new AutoRefills();
				refills.set_pending( addBtn.replenish.skuID, addBtn.replenish.days);
			}
			proceed();
		});
};

productPage.launchSkinConsult = function() {
	templatefactory.get('skin_consultation_overlay').evaluateCallback({
		callback: function (html){
			overlay.launch({
				content  : html,
				cssStyle : {width:'798px', height:'448px'}
			});
			$('lighterwindow_close_link').observe('click',function(){
				overlay.hide();
			});
		}
	});
};

productPage.SkinConsult = {};
productPage.SkinConsult.tabNames = ["tab_1", "tab_2", "tab_3"];
productPage.SkinConsult.concerns = ["BREAKOUTS", "REDNESS", "UNEVEN SKIN TONE", "LINES", "PORES", "AGE PREVENTION", "CLARITY", "DEHYDRATION", "BASIC CARE"];
/**
* This function takes an array of Skin Consultation Quiz answers and
* returns the resulting Product & SKU data (arranged by tab) in JSON format.
* @param {Array} args.answers needs to be formatted like this:
*   [[1,0],2,3,4,1,2,3,4,0,1]
* @param {function} args.callback this function will be called
*   with the result data as a parameter.
*/
productPage.SkinConsult.getTabData = function(args) {

	var answerArray = args.answers;
	var callbackFn = args.callback;
	var fetchData = function(answerArray) {
		var params = Object.extend ( {'answers': answerArray} );
		params = Object.extend ( params, productPage.DETAIL_VIEW_QUERY.PRODUCT_FIELDS );
		params = Object.extend ( params, productPage.DETAIL_VIEW_QUERY.SKU_FIELDS );
		params = [params];

		jsonrpc.fetch(
			"skintype.results", //method
			params, //params
			{
				onSuccess: function (r) {
					//
					// parse AJAX response
					//var response = t.responseText.evalJSON()[0];
					//var id = response.id;
					//var error = response.error;
					//var data = $H(response.result);

					var responseData = r.responseText.evalJSON(true);
					if (typeof responseData[0].result === "object") {
						skinResultData = responseData[0].result;
						var cpd = new CatProdPageData();

						//cpd.fillinData ( $H(skinResultData) );
						var hskin = $H(skinResultData);
						//var hskus = hskin.get('sku');
						//var hprod = hskin.get('product');
						//cpd.addSkus(hskus);
						//cpd.addProducts(hprod);

						cpd.fillinData(hskin);

						//cpd.linkSkusToProducts();

						mergeData ( cpd, skinResultData );
						//var pids = pluckIDs({resultData: skinResultData, field: "PRODUCT_ID"});
						//loadProductData(pids, skinResultData);
					}
				},
				onFailure: function (r) {console.log(r);}
			}
		);
	};

	var pluckIDs = function(args) {
		var returnArray = [];
		productPage.SkinConsult.tabNames.each(function(tab) {
			if (Object.isArray(args.resultData[tab])) {
				args.resultData[tab].each(function(skuItem){
					if (skuItem[args.field]) {
						returnArray.push(skuItem[args.field]);
					} else {
						returnArray.push('');
					}
				});
			}
		});
		return returnArray;
	};
	//
	// Tab object
	var Tab = function(productsData, catProdDataObj, idx) {
		var products = productsData;
//		console.log(productsData);
		this.label = '';
//		this.catProdData = catProdDataObj.

		if (idx === 0) {
			this.label = 'BASIC REGIMEN';
		} else {
			//if (productsData[0].CONCERN.length > 0) {
			if (productsData[0].CONCERN !== '') {
				this.label = productPage.SkinConsult.concerns[productsData[0].CONCERN -1];
			}
		}
		this.getLabel = function() {
			return this.label;
		};
		this.getProducts = function() {
			return products;
		};
	};

	var QuizResults = function(skinResultData) {
		Object.extend(this, skinResultData);
		var tabCollection = [];
		this.addTab = function(tabObj) {
			tabCollection.push(tabObj);
		};
		this.getTabs = function() {
			return tabCollection;
		};
		this.getSkinTypeNumber = function() {
			return skinResultData.skintype;
		};
		this.getSkinTypeHeader = function() {
			var skintypeTitles = [
				'You are a Skin Type 1 - Very Dry to Dry',
				'You are a Skin Type 2 - Dry Combination',
				'You are a Skin Type 3 - Oily',
				'You are a Skin Type 4 - Very Oily' ];
			return skintypeTitles[skinResultData.skintype - 1];
		};
	};

	var mergeData = function(catProdDataObj, skinResultData) {
//		console.log(catProdDataObj);
		var quizResults = new QuizResults(skinResultData);
		productsArray = catProdDataObj.data.get("product").all;
		productPage.SkinConsult.tabNames.each(function(tab, idx) {
			if (skinResultData && Object.isArray(skinResultData[tab])) {
				//
				// add it to the internal tabs collection
				var tabObj = new Tab(skinResultData[tab], catProdDataObj, idx);
				quizResults.addTab(tabObj);

				// each product
				tabObj.getProducts().each(function(resultItem) {
					var productData = productsArray.find(function(prod) {
						return prod.PRODUCT_ID === resultItem.PRODUCT_ID;
					});
					if (productData) {
						resultItem.product = productData;
					}
					productData.sku = [];
					productData.skus = [];
					if (productPage.validateSkusArray(resultItem.product.sku)) {
						var skuData = resultItem.product.sku.find(function(s){
							return s.SKU_ID === resultItem.SKU_ID;
						});
						resultItem.sku = skuData;
						productData.sku.push(skuData);
						productData.skus.push(skuData);
					} else {
						resultItem.sku = null;
					}
				});
			}
		});
//		console.log(skinResultData);
		quizResults.catProdData = catProdDataObj;
//		console.log(catProdDataObj);
		return quizResults;
	};
	var loadProductData = function(productIDs, skinResultData) {
		productPage.loadProductData({
			productIDs: productIDs,
			callback: function(catProdDataObj) {
				var tabData = mergeData(catProdDataObj, skinResultData);
//				console.log(tabData.getTabs());
//				console.log(tabData.getSkinTypeHeader());
//				console.log(tabData.getSkinTypeNumber());
//				console.log(tabData.getTabs()[0].getLabel());
//				console.log(tabData.getTabs()[0].getProducts());

				callbackFn(tabData);
			}
		});
	};

	fetchData(answerArray);

};

productPage.validateSkusArray = function(skus) {
	return (skus && Object.isArray(skus) && skus.length > 0 && skus[0]);
};

productPage.ProductView.ShadeRange = Class.create( productPage.ProductView.Inline, {
	descriptionCallbackFn: function($super, html){
//		console.log("productPage.ProductView.ShadeRange#descriptionCallbackFn");
		$super(html);
//		this.loadShadeRange(this.shadeRangeAnswers);
		this.initShadeRange(this.shadeRangeSkuIDs);
		var attributesNode = this.getChildNode("attributes-container");
		if (attributesNode) {
			attributesNode.style.display = "none"
		}
	},
	initShadeRange: function(skuIDsArray) {
		var self = this;
		var tableContainerID = this.appendSuffix('shade-range-table-container');

		var resultMessage = foundationMessage || "MEET YOUR MATCH!<br/>Here's the foundation we recommend for your skin.";

		var viewContainer = this.getViewContainerNode();
		var ffElements = viewContainer.select(".foundation-finder");
		ffElements.each(function(ele) {
			ele.style.display="block";
		});
		viewContainer.observe("skuMenu:loaded", function() {
			self.getChildNode('shadelbl').update('All Shades:');
			self.getViewContainerNode().stopObserving("skuMenu:loaded");
		});

		var msgEle = viewContainer.select("#foundation-finder-message")[0];
		if (msgEle) {
			msgEle.update(resultMessage);
		}
		
		$$('#foundation-finder-btns .foundationFinderButton').each(function(button) {
			button.observe('click',function(event){
				// KFR: CoreMetrics taggng. This will send the element along with a flag to tell
				// checkNavLink (found in coremetrics2.js) to manually throw a link promotions tag
				var element = Event.element(event);
				console.log(element);
				checkNavLink(element.parentNode,element.parentNode.classNames());
				launchFoundationFinder();
			});
		});
		
		$$('#foundation-finder-btns .foundationSaveButton').each(function(button) {
			// If user is signed in, let them save their results.
			// Else just hide the save button.
			// (TODO - prompt user to sign in and save...)
			if ( typeof user == 'object' && user.isSignedIn() ) {
				button.observe('click',function(){
					if ( getFoundationAnswers() ) {
						var params = Object.extend ( {'answers': getFoundationAnswers()} );
						params = [params];
					
						jsonrpc.fetch(
							"foundation.save", //method
							params, //params
							{
								onSuccess: function (r) {
									var o = r.responseText.evalJSON()[0];
									var str = ( o.error == null || o.error.length == 0 ? 'Foundation Match Saved.' : 'Error saving foundation info.' );
									$$('#foundation-finder-btns .progress').each(function(p){
										p.update(str);
									});
								},
								onFailure: function (r) {
									console.log(r);
								}
							}
						);
					}
				});
			} else {
				button.hide();
			}
		});
		
		// There's a popup for the undertones link
		createLwPopupLinks();


		var skuDataArray = [];
		skuIDsArray.each(function(skuID) {
			var skuObjToAdd = self.productData.sku.find(function(skuObj) {
				return skuObj.SKU_ID === skuID;
			})
			if (skuObjToAdd) {
				skuDataArray.push(skuObjToAdd);
			}
		});
		skuDataArray.each(function(sku) {
			var getUndertoneName = function(undertoneValue) {
				switch (undertoneValue) {
					case "N" :
						return "Neutral Undertone";
						break;
					case "P" :
						return "Pink Undertone";
						break;
					case "G" :
						return "Golden Undertone";
						break;
					default:
						return "";
						break;
				}
			}
			sku.undertoneName = getUndertoneName(sku.UNDER_TONE);
		});
		var shadeTableArgs = {
			tableData : skuDataArray,
			tableContainerID: tableContainerID,
			viewContainerID : self.viewContainerID,
			includeFilters  : false,
			cellsPerRow	 : 4,
			menuContainerNode	   : null,
			filterMenuContainerNode : null
		};
		this.shadeRangeTable = new productPage.ProductView.ShadeRange.Table(shadeTableArgs);
	}

});

productPage.ProductView.ShadeRange.Table = Class.create( productPage.ShadePicker.Table, {
	initialize: function($super, args) {
//		console.log("productPage.ProductView.ShadeRange.Table#initialize");
		this.filename = "foundation_shade_table_cell"
		$super(args);
	}
});

productPage.launchColorFinder = function(categoryID) {
	if (categoryID && categoryID.length && categoryID.length > 0) {
		var cf = new productPage.ColorFinder(categoryID);
	}
};

productPage.ColorFinder = Class.create({

	initialize: function(categoryID) {
		this.categoryID = categoryID;
		var self = this;
		var params = Object.extend({
				"category" : [this.categoryID],
				"category_fields" : ["CATEGORY_ID",
						"DISPLAY_NAME",
						"product"]
				}, productPage.DETAIL_VIEW_QUERY.PRODUCT_FIELDS);
		params = Object.extend (params,
				productPage.DETAIL_VIEW_QUERY.SKU_FIELDS);
		params = [params];

		jsonrpc.fetch(
			"prodcat.byid", //method
			params, //params
			{
				onSuccess: function (r) {
					var responseData = r.responseText.evalJSON(true);
					if (typeof responseData[0].result === "object") {
						responseCategoryData = responseData[0].result;
						var responseCategoryDataHash = $H(responseCategoryData);
						var categoryDataObject = new CatProdPageData();
						categoryDataObject.fillinData(responseCategoryDataHash);
						mergeIntoGlobalCatProdData(categoryDataObject);
						var recursiveSkuDataArray = [];

						categoryDataObject.getSkus().each(function(s){
							if (s.hex_value_string) {
								var p = categoryDataObject.getProductBySku(s.SKU_ID);
								var c = categoryDataObject.getCategoryBySku(s.SKU_ID);
								s.product = p;
								s.category = c;
								recursiveSkuDataArray.push(s);
							}
						});
						var args = {};
						args.tableData = recursiveSkuDataArray;
						self.render(args);
					}
				},
				onFailure: function() {}
			});

	},

	render: function(args) {
		var self = this;
		this.containerID = 'colorfinder-container';
		templatefactory.get(productPage.TEMPLATE_DIR + 'colorfinder').evaluateCallback({
			object   : args.tableData[0].category,
			callback : function(html) {
//				console.log("ColorFinder callback");
				// create temporary hidden div to contain the table as it gets built
				var wrapperNode = new Element("div", {id:"colorfinder-wrapper"});
				wrapperNode.setStyle({display:"none"});
				$(document.body).insert(wrapperNode);
				wrapperNode.update(html);

				var filterContainer = $$('#colorfinder-filter-container .filter-options')[0];

				// initialize table
				var tableArgs = {
					tableData: args.tableData,
					tableContainerID: 'colorfinder-table-container',
					viewContainerID : "colorfinder-wrapper",
					cellsPerRow : 7,
					filterMenuContainerNode : filterContainer,
					includeFilters: true,
					sortMenuContainerNode : filterContainer,
					categoryID : self.categoryID,
					postRenderCallback: function() {
//							console.log("postRenderCallbackFn");
							wrapperNode.style.display = "block";
							// open pop-over window
							overlay.launch({
								content  : wrapperNode,
								cssStyle : {padding: '10px'}
							});
							// activate "close" link
							$('colorfinder-close-link').observe('click', function(){
								overlay.hide();
							});
							var overlayNode = $(productPage.OVERLAY_CONTAINER_ID);
							overlayNode.style.overflow = "visible";
							overlayNode.observe("cart:add:success", function () { overlay.hide(); } );
							overlayNode.observe("cart:add:fail", function () { overlay.hide(); } );
							var detailArgs = {
								shadeDetailContainerID : 'colorfinder-detail-container',
								viewContainerID : productPage.OVERLAY_CONTAINER_ID,
								skuData : args.tableData[0]
							};
//							console.log("new ColorFinder");
//							console.log(detailArgs);
							var cfd = new productPage.ShadePicker.Detail.ColorFinder(detailArgs);
						}
				};

				var cft = new productPage.ShadePicker.Table.ColorFinder(tableArgs);
			}
		});
	}
});


