/* THIS FILE REQUIRES A prodrb Hash(), see: js_eng.tmpl */

/**
 * @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 = "/templates/products/";
/**
 * Message that appears for products that are Out of Stock.
 * @fieldOf productPage
 * @constant
 */
if (prodrb) {
    productPage.TOS_MSG = prodrb.get("out_of_stock") || "Out Of Stock";
    productPage.SOLD_OUT_MSG = prodrb.get("sold_out") || "Sold Out";
    productPage.COMING_SOON_MSG = prodrb.get("coming_soon") || "Coming Soon";
} else {
    productPage.TOS_MSG = "Out Of Stock";
    productPage.SOLD_OUT_MSG = "Sold Out";
    productPage.COMING_SOON_MSG = "Coming Soon";
}
 /**
 * 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", "PROD_RGN_NAME", "PROD_RGN_SUBHEADING", "SUB_LINE", "DESCRIPTION", "SHORT_DESC", "PROD_SKIN_TYPE", "PROD_SKIN_TYPE_TEXT", "PROD_CAT_IMAGE_NAME", "PROD_CAT_DISPLAY_ORDER", "SMALL_IMAGE", "LARGE_IMAGE", "THUMBNAIL_IMAGE", "PRODUCT_USAGE", "FORMULA", "ATTRIBUTE_COVERAGE", "ATTRIBUTE_BENEFIT", "SKIN_CONCERN_LABEL", "SKIN_CONCERN_1", "SKIN_CONCERN_2", "SKIN_CONCERN_3", "skus", "shaded", "sized", "url", "AVERAGE_RATING", "RATING_RANGE", "TOTAL_REVIEW_COUNT", "ONLY_RATINGS_COUNT", "RECOMMENDED_COUNT", "RECOMMENDED_PERCENT", "RATING_IMAGE", "PROD_BASE_ID" ]
};
/**
 * 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"]
//};
productPage.DETAIL_VIEW_QUERY.SKU_FIELDS = {
    "sku_fields" : [ "SKU_ID", "SKU_BASE_ID", "PRODUCT_ID", "SHADENAME", "SHADE_DESCRIPTION", "DISPLAY_ORDER", "SKIN_TYPE", "SKIN_TYPE_TEXT", "PRODUCT_SIZE", "STRENGTH", "PRICE", "formattedPrice", "formattedTaxedPrice", "INVENTORY_STATUS", "SMOOSH_DESIGN", "SMOOSH_PATH_STRING", "REFILLABLE", "HEX_VALUE", "HEX_VALUE_STRING", "FINISH", "ATTRIBUTE_COLOR_FAMILY", "LIFE_OF_PRODUCT", "UNDERTONE", "SKIN_TONE", "SKIN_TONE_TEXT", "COLORGROUPING" ]
};
/** @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_fields" : ["PRODUCT_TABBED_DATA"]
};

productPage.CONTENT_TAB_QUERY.TAB_FIELDS = ["PROD_BASE_ID", "TAB_NUMBER", "DISPLAY_ORDER", "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({
	CAT1067 : { // Powder
		filters: [ { label: prodrb.get("Skin_Tone"), field: "SKIN_TONE" } ]
	},
	CAT1559 : { // Foundations
		filters: [ { label: prodrb.get("Skin_Tone"), field: "SKIN_TONE" } ]
	},
	CAT1598 : { // Eye Shadow
		filters: [ { label: prodrb.get("Finish"), field: "FINISH" },
				   { label: prodrb.get("Colour_Group"), field: "ATTRIBUTE_COLOR_FAMILY" } ]
	},
	CAT1597 : { // Eye Liner
		filters: [ { label: prodrb.get("Colour_Group"), field: "ATTRIBUTE_COLOR_FAMILY" } ]
	},
	CAT1605 : { // Lipstick
		filters: [ { label: prodrb.get("Finish"), field: "FINISH" },
				   { label: prodrb.get("Colour_Group"), field: "ATTRIBUTE_COLOR_FAMILY" } ]
	},
	CAT1603 : { // Lip Gloss
		filters: [ { label: prodrb.get("Finish"), field: "FINISH" },
				   { label: prodrb.get("Colour_Group"), field: "COLORGROUPING" } ]
	},
	CAT1604 : { // Lip Liner
		filters: [ { label: prodrb.get("Colour_Group"), field: "COLORGROUPING" } ]
	}
});

/**
 * 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: prodrb.get("Bestseller") , field: "DISPLAY_ORDER" },
				{ label: prodrb.get("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 children widget 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
 * as 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 is 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 and 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 enabled
     * unique IDs to be created for child elements and recursive desctruction of the Widget
     * and its children 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's 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 an instance from the class's
     * internal registry of instances. It will do the same for any Widget instances that were
     * instantiated as "children 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 from an array
     * of data in JSON format. Select menus are registered with the FilterTable, which in turn
     * assigns events to those menus.
     * @class
     * @arguments 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 capable of being parsed by the filtering
     * and 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;
        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 be the name of a top-level property of a tableData item. For example,
     * a table displaying Product items might filter on a field Product.PROD_RGN_NAME.
     * The parameter passing in this case would be "PROD_RGN_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 be the name of a top-level property of a tableData item. For example,
     * a table displaying Product items might sort on a field Product.PROD_RGN_NAME.
     * The parameter passing in this case would be "PROD_RGN_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 and sorting menus, puts the
     * requested items into an array and 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;
        var filterFunctions = function(field){
            switch(field) {
            case "PROD_SKIN_TYPE_TEXT":
                return function(valueToFind, valueToSearch){
                    if (valueToSearch.substring(2,6) === "0000") {
                        return true;
                    }
                    var pos = parseInt(valueToFind) + 1;
                    //
                    // check valueToSearch.substring(pos, 1) because we are interested in the
                    // 3rd - 6th characters of skin_type_string. valueToFind will be digits 1-4
                    if (valueToSearch.substr(pos, 1) === "1") {
                        return true;
                    }
                    return false;
                };
                break;
            default:
                return function(valueToFind, valueToSearch){
                    return valueToSearch === valueToFind;
                };
                break;
            }
        };
        //
        // iterate through the 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
            // we are going to filter
            self.filterNodes.keys().each(function(menuName){
                //
                // menuName 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, add options, and insert the select menu
     * in the DOM.
     * @param {Node} args.containerNode the node into which the sort menu will be inserted
     * @param {number} args.menuWidth width in pixels of a custom menu to be rendered
     * the select menu takes this number and assigns the node a class of that width.
     * @param {string} args.selectNodeID the ID that will be given to the the select menu node
     * @param {Array} args.sortMenuArray an array of JS Hash objects. These objects define the options that
     * are inserted into the menu - the format must be { field: *string*, label: *string* } where
     * field is the sorting property 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
              }).insert(sortMenuRecord.label));
        });
        options.containerNode.insert(slct);
        return slct;
    },
    /**
     * This method builds the HTML select menu node.
     * @requires niceform_init (contrib/niceform.js)
     * @methodOf productPage.FilterTable
     * @param {Node} args.filterMenuContainerNode the node where the filter menus will be inserted
     */
    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: filterObj.label + prodrb.get("Filter_by") + ':', 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( prodrb.get("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_TEXT" :
                    [1,2,3,4].each(function(n){
                        menuOptions.push(n);
                    });
                    break;
                default:
                    options.tableData.each(function(record){
                        var menuVal = record[fieldName];
                        if (menuVal) {
//                            console.log(record);
 //                           console.log(menuVal);
                            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, {
    /**
     * 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
     * @lends productPage.ProductView#
     * @constructs
     */
    initialize: function(args) {
        this.detailFilename = "product_detail.tmpl";
        this.descriptionFilename = "product_description.tmpl";
        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 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;
       return this
       /*
        * CL JP does not support Replenishment
       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, SKU_BASE_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,
                    sku_base_id : skus[0].SKU_BASE_ID,
                    shopping_id : skus[0].shopping_id,
                    qty         : 1,
                    increment   : true
                 }
            });
            this.addButton.setShoppable(productPage.isActive(skus[0]));
            this.viewContainerNode.observe('select:sku', function(evt) {
                var skuData = evt.memo;
                self.addButton.cartArgs.sku_id = skuData.SKU_ID;
                self.addButton.cartArgs.sku_base_id = skuData.SKU_BASE_ID;
                self.addButton.cartArgs.shopping_id = skuData.shopping_id;
                self.addButton.setShoppable(productPage.isActive(skuData));
            });
            /*
             * CL JP does not support Replenishment
            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 : prodrb.get("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.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
            });
        }
        /*
         * CL JP does not support replenishment
        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.PRICE;
                });*/
                this.productData.skus = skus.sortBy(function(s){ return s.DISPLAY_ORDER ? s.DISPLAY_ORDER : 0; });
                // remove INACTIVE skus
                // this.productData.skus = this.productData.skus.reject(function(s){
                //     return productData.isInactive(s);
                // });
                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.formattedPrice);
            self.updatePrice(productPage.inlineTaxedPrice(evt.memo, ' '));
        });
        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.tmpl",
    initialize: function(args){
        this._tabs = null;
        this.register();
        this.tabLinksContainerID = "product-tabs";
        this.contentContainerID = "tabs-content";
        this.tabsWrapperID = 'tabs_container';
//        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;
        }
        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);
            });
        }
    },

    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
                document.fire("MPP:productQV",this.productData.PRODUCT_ID);
            } 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     : prodrb.get("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 : prodrb.get("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) {
        var self = this;
        var tabParams = [{}];
        tabParams[0].product_fields = ["PRODUCT_TABBED_DATA", "PROD_BASE_ID", "PRODUCT_ID"];
        tabParams[0].products = [productID];

        jsonRpcWrapper.fetch({
            method : "prodcat",
            params : tabParams,
            onSuccess: function (jsonRpcResponse) {
                var data = jsonRpcResponse.getValue();
                var tab_items;
                if (data && data.products) {
                    // assume we've only requested tabs from one product
                    tab_items = data.products[0].PRODUCT_TABBED_DATA;
                }
                if (tab_items) {
                    self.initContentTabs(tab_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";
                        }
                    }
                }
            },
            onError: function (jsonRpcResponse) {
                console.log(jsonRpcResponse.getMessages());
            }
        });
    },
    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 = [
            prodrb.get('VIDEO'),
            prodrb.get('PRESS'),
            prodrb.get('EXPERT_TIPS'),
            prodrb.get('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;
                    if (!videoItem) {
                        templatefactory.get(productPage.TEMPLATE_DIR + 'tabbed_content_item.tmpl').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
                            });
                        }
                    }
                });
            }
        }
    },    
    initBazaarVoice: function(productData) {
		var host = location.href.parseUri().host;
		var bvhost = host.endsWith('esteeonline.com') ? 'reviews.clinique-jp.esteeonline.com' : 'reviews.clinique.co.jp';
		bvhost = (host.match(/eng\./) || host.match(/dev\./)) ? bvhost + '/bvstaging' : bvhost;
		var prodid = productData.PROD_BASE_ID || productData.PRODUCT_ID;
		var iframeSrc = "http://"+bvhost+"/3813-ja/" +
				prodid +
				"/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;
		// keep true fo now.
		window.BVisLoaded = true;
        bvIframe.observe('load', function() {
            if (!window.BVisLoaded) {
                var ele = document.getElementById("BVFrame");
                if (ele) {
                    var page = ele.src;
					var bvhost = host.endsWith('esteeonline.com') ? 'reviews.clinique-jp.esteeonline.com' : 'reviews.clinique.jp';
					ele.src='http://' + bvhost + '/logging?page=' + escape(page);
                }
            }
        });
    }
    
    
});


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

    initialize: function(args) {
        this.filename = "/templates/quickshop_price_select_single.tmpl";
        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 fieldObjArray = this.findMenuFields(this.productData.skus);
                if (fieldObjArray.length > 0) {
                    var selectFields = [];
                    for (var i=0; i<fieldObjArray.length; i++) {
                        selectFields.push(fieldObjArray[i].field);
                    }
                    this.filename = '/templates/quickshop_price_select_multi.tmpl';
                    this.productData.selectLabel = fieldObjArray[0].label;
                    this.selectFields = selectFields;
                }
            }
            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.selectFields && this.selectFields.length > 0) {
            // 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("PRICE").uniq().length > 1) {
                includePrice = true;
            }
            skus.each(function(sku, i) {
                var fieldValues = [];
                for (var i=0; i<self.selectFields.length; i++) {
                    var fieldName = self.selectFields[i];
                    var optionLabel;
                    if (fieldName === "SKIN_TYPE_TEXT") {
                        optionLabel = prodrb.get('Skin_Type') + " ";
                        optionLabel += productPage.parseSkinTypes(sku[fieldName]);                        
                    } else {
                        optionLabel = sku[fieldName];
                    }
                    fieldValues.push( optionLabel );
                }
                var txt = fieldValues.join(" - ");
                if (includePrice) {
                    //txt += ' ' + sku.formattedPrice;
                    txt += ' ' + productPage.inlineTaxedPrice(sku, ' ');
                }
                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);
        }
    },
    findMenuFields: function(skus) {
        var fields = [
            { label: prodrb.get('Concern'), field: 'STRENGTH' },
            { label: 'Skin Type', field: 'SKIN_TYPE_TEXT' },
            { label: prodrb.get('Shade'), field: 'SHADENAME' },
            { label: prodrb.get('Size'), field: 'PRODUCT_SIZE' }
        ];
        var countUnique = function(field) {
            return skus.pluck(field).uniq().length;
        }
        var fieldsForMenu = [];
        for (var i=0, len=fields.length; i<len; i++) {
            if (countUnique(fields[i].field) > 1) {
                fieldsForMenu.push(fields[i]);
            }
        }
        return fieldsForMenu;
    }
});


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

    initialize: function(args) {
        var self = this;
        this.containerID = '';
        this.skuData = {};
        this.filename = 'replenish_menu.tmpl';
        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.tmpl",
    productData: {},

    initialize: function (args) {
        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 foundationAnswers = 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.tmpl");
        console.log('productPage.ShadePicker.Table: ' + this.filename);
        console.log('foundationAnswers: ', foundationAnswers);
        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 (this.skusToDisplay.length == 1 && foundationAnswers && 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) {
            for (var i=0; i<this.skusToDisplay.length; 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 === tblObj.skusToDisplay.length) {
                            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 (foundationAnswers) {
                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");
        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(',');

        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 -6 + "px",
                height: self.swatchHeight -6 + "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, 250);
        }.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) {
        //    this.toggleClassName('over');
        //});
        //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"
        };

        if (!s.SMOOSH_PATH_STRING)
            return defaultStyle;

        var swatchVals = s.SMOOSH_PATH_STRING.split(',');
        var 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.tmpl';
        // 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,
                "sku_base_id" : s.SKU_BASE_ID,
                "shopping_id" : s.shopping_id,
                "qty"         : 1,
                "increment"   : true
             }
        });
        this.addButton.setShoppable(productPage.isActive(s));
        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) {
            shade_details += "<p>" +
                    s.SHADE_DESCRIPTION +
                    "</p>\n";
        }
        if (s.FINISH !== null) {
            shade_details += "<strong>" + prodrb.get('Finish') + ": </strong>" +
                    s.FINISH +
                    "<br />\n";
        }
        if (s.ATTRIBUTE_COLOR_FAMILY !== null) {
            shade_details += "<strong>" + prodrb.get('Colour_Group') + ": </strong>" +
                    s.ATTRIBUTE_COLOR_FAMILY +
                    "<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(',');
        if (!s.SMOOSH_PATH_STRING) return;
        var smooshVals = s.SMOOSH_PATH_STRING.split(',');
        //
        // break up smoosh path string, removing positional suffix
        var re = /^(.*_\wg)(?:_\w+)?(\.png)$/;
        var reResult = re.exec(smooshVals[0]);
        if (!reResult) { // create an array of empty strings if the reg ex doesn't match anything in the smoosh string.
            reResult = ["",""]
        }
        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.sku_base_id = s.SKU_BASE_ID;
        this.addButton.cartArgs.shopping_id = s.shopping_id;
        this.addButton.setShoppable(productPage.isActive(s));
    },
    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.PROD_RGN_NAME);
        $("product-name").update(lnk);
        $("shade_price").update(skuData.formattedPrice);
        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 );

	var params = [{
	    products : [inputProductID],
	    product_fields : ["PRODUCT_ID", "DEFAULT_CAT_ID", "PARENT_CAT_ID", "PROD_RGN_NAME", "PROD_RGN_SUBHEADING", "SUB_LINE", "DESCRIPTION", "SHORT_DESC", "PROD_SKIN_TYPE", "PROD_SKIN_TYPE_TEXT", "PROD_CAT_IMAGE_NAME", "PROD_CAT_DISPLAY_ORDER", "SMALL_IMAGE", "LARGE_IMAGE", "THUMBNAIL_IMAGE", "PRODUCT_USAGE", "FORMULA", "ATTRIBUTE_COVERAGE", "ATTRIBUTE_BENEFIT", "SKIN_CONCERN_LABEL", "SKIN_CONCERN_1", "SKIN_CONCERN_2", "SKIN_CONCERN_3", "skus", "shaded", "sized", "worksWith"],
		sku_fields : ["SKU_ID", "SKU_BASE_ID", "PRODUCT_ID", "SHADENAME", "SHADE_DESCRIPTION", "SKIN_TYPE", "SKIN_TYPE_TEXT", "PRODUCT_SIZE", "DISPLAY_ORDER", "STRENGTH", "PRICE", "formattedPrice", "formattedTaxedPrice","SMOOSH_DESIGN", "SMOOSH_PATH_STRING", "INVENTORY_STATUS", "REFILLABLE", "HEX_VALUE", "HEX_VALUE_STRING", "FINISH", "ATTRIBUTE_COLOR_FAMILY", "UNDERTONE", "SKIN_TONE", "SKIN_TONE_TEXT" ]
	}];

    jsonRpcWrapper.fetch({
        method : "prodcat",
        params : params,
        onSuccess: function (jsonRpcResponse) {
            var responseProductData = jsonRpcResponse.getValue();
            var args = {};
            args.productData = responseProductData.products[0];

            var catProdDataObject = new CatProdPageData(); // these 3 lines are needed to launch cart pop-over after adding to bag
            catProdDataObject.processData(responseProductData); // 
            mergeIntoGlobalCatProdData(catProdDataObject); // 

            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();
        },
        onError: function (jsonRpcResponse) {
            console.log(jsonRpcResponse.getMessages());
        }
    });
};


/**
 * 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, {
    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.tmpl';
        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,
            skinTypeString: productPage.getAllSkinTypes(this.productData, 'numerals'),
            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() || !self.productData.skinTypeString) {
                        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 sbid = self.productData.skus[0].SKU_BASE_ID;
                                    var addid = cart.add(
                                        { sku_id   : sid,
                                          sku_base_id : sbid,
                                          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);
        }
    }
});

/**
 * This method determines which optional attribute fields are present in a product data object.
 * It then renders a UL that contains those field values along with labels and drops that UL
 * into a container node that it receives as a parameter.
 * @param {Node} args.containerNode the node into which the UL will be inserted
 * @param {Object} productData JSON-formatted data that contains product information.
 * @memberOf productPage
 */
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: prodrb.get('Skin_Types'),
              field: productPage.getAllSkinTypes(p)
            },
            { label: prodrb.get('Formula'), field: p.FORMULA },
            { label: prodrb.get('Coverage'), field: p.ATTRIBUTE_COVERAGE },
            { label: prodrb.get('Benefits'), field: p.ATTRIBUTE_BENEFIT },
            { label: prodrb.get('Concern'), field: p.SKIN_CONCERN_LABEL }
        ];
        if (!productPage.displaySkinTypes()) {
            fields.shift();
        };
        for (var i=0, len=fields.length; i<len; i++) {
            if (fields[i].field && fields[i].field.length > 0) {
                var strEle = new Element('strong').update(fields[i].label + ': ');
                var li = new Element('li').update(strEle);
                li.insert(fields[i].field);
                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);

        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 if (productPage.isComingSoon(skuData)) {
         		this.domNode.update(productPage.COMING_SOON_MSG);
         		this.domNode.removeClassName('hidden');
     		} else if (productPage.isSoldOut(skuData)) {
         		this.domNode.update(productPage.SOLD_OUT_MSG);
                this.domNode.removeClassName('hidden');
            } else {
                this.domNode.update('');
                this.domNode.addClassName('hidden');
            }
        }
    }
});

productPage.ProductsTable = Class.create(productPage.FilterTable, {
    /**
     * @memberOf productPage
     * @lends productPage.ProductsTable#
     */
    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;
        });
        // 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;
        }
        if (prods.length > 0 && this.startIndex < prods.length) {
            for (var i=this.startIndex; i<prods.length; i++) {
                var itemIndex = i - this.startIndex;
                if (self.maxItems && itemIndex >= self.maxItems) {
                    break;
                }
                var prod = prods[i];

                var templt_args = {
                    li_class: "",
                    price_string: "",
                    skinTypeString: "",
                    url_domain: window.URL_DOMAIN };

                // 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) {
                    self.ul = new Element("ul", {'class': 'product-thumb-row'});
                    self.container_div.insert(self.ul);
                }
                prod.lastItemFlag = false;

                // last <UL> gets class="lastrow"
                if (prods.length - 1 === i || (self.maxItems && self.maxItems - 1 === i)) {
                    self.ul.addClassName("lastrow");
                    prod.lastItemFlag = true;
                }
                // sort skus by price
                if (productPage.validateSkusArray(prod.skus)) {
                    prod.skus = prod.skus.sortBy(function(sku){
                        return parseFloat(sku.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.getAllSkinTypes(prod);

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

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

                // add constructed parameters required by the template
                Object.extend(prod, templt_args);

                // build the HTML and instantiate a ProductThumb object
                templatefactory.get(productPage.TEMPLATE_DIR + templateFile).evaluateCallback({
                    object: prod,
                    callback: function(prod, ul, i, html){
                        self.initThumb(prod, ul, i, html);
                    }.curry(prod, self.ul, itemIndex)
                });
            }
        }
    },
    initThumb: function(prod, ul, li_index, html){
        var self = this;
        var prod_skus_length = productPage.validateSkusArray(prod.skus) ? prod.skus.length : 0;
        ul.insert(html);
        var li = $$("#" + this.containerID + " 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: ( this.mode == 2 )
        });

        if (this.showShadeTable) {
			var shadeWrapper = li.down('.thumb_shade_wrapper');
			var shadeDiv = li.down('.thumb_shades');
			if ( shadeWrapper && shadeDiv ) {
				shadeWrapper.show();
				shadeDiv.id = this.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 ) {
					var shadeTableArgs = {
						tableData : prod.skus,
						tableContainerID: shadeDiv.id,
						viewContainerID : this.containerID,
						includeFilters  : false,
						cellsPerRow	 : 4,
						menuContainerNode	   : null,
						filterMenuContainerNode : null
					};
	
					thumb.shadeRangeTable = new productPage.ProductView.ShadeRange.Table(shadeTableArgs);
				} else if ( Prototype.Browser.IE ) {
					// 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';
				}
	
				// Set up the add button if there's a shade table or only one sku
				if ( prod.shaded || prod_skus_length == 1 ) {
		   			var addDiv = li.down('.thumb_shade_add_button');
					addDiv.id = this.containerID + prod.PRODUCT_ID + '-add-button';
					addDiv.show();
	
					thumb.addButton = addButton({
						domNode	  : addDiv.down(".shaded_add_link"),
						progressNode : addDiv.down(".shaded_add_progress"),
						cartArgs	 : {
							sku_id	  : prod.skus[0].SKU_ID,
                            sku_base_id : prod.skus[0].SKU_BASE_ID,
							shopping_id : prod.skus[0].shopping_id,
                            qty         : 1,
                            increment   : true
						 }
					});
                    thumb.addButton.setShoppable(productPage.isActive(prod.skus[0]));
					shadeDiv.observe('select:sku', function(evt) {
						var skuData = evt.memo;
						thumb.addButton.cartArgs.sku_id = skuData.SKU_ID;
                        thumb.addButton.cartArgs.sku_base_id = skuData.SKU_BASE_ID;
						thumb.addButton.cartArgs.shopping_id = skuData.shopping_id;
                        thumb.addButton.setShoppable(productPage.isActive(skuData));
					});
				}
            }
        }

        // fire event when the last thumb has been rendered on the page
        // console.log(prod.lastItemFlag);
        if (prod.lastItemFlag) {
            self.container_div.fire("productPage.ProductsTable:loaded", self);
            console.log("productPage.ProductsTable:loaded");
        }
    }
});

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

productPage.loadProductData = function(args) {
	var params = Object.extend({"products" : 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.getSkus();
			skus.each(function(s){
				CatProdData.data.get('all_skus').push(s);
			});
			var prods = newProductData.data.getProducts();
			prods.each(function(p){
				CatProdData.data.get('all_products').push(p);
			});
		}
	};

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

};

/*
* This class takes a product object and returns a string based
* on the formattedPrice 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('PRICE').uniq().length;
		if (uniquePricesCount > 1)  {
			var sortedSkus = prod.skus.sortBy(function(s){return s.PRICE;});
			//price_string = sortedSkus[0].formattedPrice;
			//price_string += " - " + sortedSkus[prod_skus_length-1].formattedPrice;
			price_string = sortedSkus[0].formattedTaxedPrice;
			price_string += " - " + sortedSkus[prod_skus_length-1].formattedTaxedPrice;
		} else {
			//price_string = prod.skus[0].formattedPrice;
			price_string = prod.skus[0].formattedTaxedPrice;
		}
		return price_string;
	} else {
		return '';
	}
};

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

productPage.inlineTaxedPrice = function(sku, separator){
    if (sku) {
        var preTaxLabel  = '&#26412;&#20307;';
        var postTaxLabel = '&#31246;&#36796;';
        var openBracket = '( ';
        var closeBracket = ')';
        if (!separator) separator = ' ';
        var price_string = '';
        price_string = postTaxLabel + sku.formattedTaxedPrice + separator + openBracket + preTaxLabel + sku.formattedPrice + closeBracket;
        return price_string;
    } else {
        return '';
    }
}

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

/*
* This method iterates through the SKU in a product,
* building a string that contains the skin types from each SKU.
*/
productPage.getAllSkinTypes = function(productData, format) {
	if (!productData.skus || !Object.isArray(productData.skus)) {
		return;
	}
	var skinTypes = new Array("0","0","0","0");
	for (var j=0, len=productData.skus.length; j<len; j++) {
		var skuData = productData.skus[j];
        if (skuData.SKIN_TYPE_TEXT){
		    for (var i=0; i<4; i++) {
			    var type = skuData.SKIN_TYPE_TEXT.charAt(i+2);
 			    if (type == "1") {
				    skinTypes[i] = "1";
 		  	    }
 		    }
        }
	}
	skinTypeStr = "0b" + skinTypes.join();
	skinTypeStr = skinTypeStr.replace(/,/g, '');
	var formatStr = 'fulllength'
	if (format) {
	    formatStr = format;
	}
	return productPage.parseSkinTypes(skinTypeStr, {format: formatStr});
};
/*
* 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 'fulllength'. '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' ],
		fulllength : [ 
                        prodrb.get('one_Very_Dry_to_Dry'),
			prodrb.get('two_Dry_Combination'),
			prodrb.get('three_Oily'),
			prodrb.get('four_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 prodrb.get("All");
    // }
	for (var i=3; i>-1; i--) {
		if (skinTypeStr.charAt(i) === "0") {
			skinTypeDisplayText.splice(i,1);
		}
	}
	
	return skinTypeDisplayText.join(', ');

};

//
//# 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
/*
* tests a sku obj to see if it is out of stock.
* checks the INVENTORY_STATUS field.
*/
productPage.isTos = function(sku) {
    return (sku && sku.INVENTORY_STATUS === 2);
};
productPage.isComingSoon = function(sku) {
    return (sku && sku.INVENTORY_STATUS === 3);
};
productPage.isSoldOut = function(sku) {
    return (sku && sku.INVENTORY_STATUS === 7);
};
productPage.isActive = function(sku) {
    return (sku && sku.INVENTORY_STATUS === 1);
};
productPage.isInactive = function(sku) {
    return (sku && sku.INVENTORY_STATUS === 5);
};

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

productPage.launchSkinConsult = function() {
	templatefactory.get('/templates/skin_consultation_overlay.tmpl').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"];
/**
* 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];

        jsonRpcWrapper.fetch({
            method: "skintype.results",
            params: params,
			onError: function (jsonRpcResponse) {
			    console.log(jsonRpcResponse.getMessages());
			},
			onSuccess: function (jsonRpcResponse) {
				var responseData = jsonRpcResponse.getData();
				if (typeof responseData === "object") {
					skinResultData = responseData;
					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);
				}
			}
        }); // end jsonRpcWrapper.fetch

	};

	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 = prodrb.get('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 ' + prodrb.get('one_Very_Dry_to_Dry'),
				'You are a Skin Type ' + prodrb.get('two_Dry_Combination'),
				'You are a Skin Type ' + prodrb.get('three_Oily'),
				'You are a Skin Type ' + prodrb.get('four_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);
};

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 = foundationFinderRB ? foundationFinderRB.get("MEET_YOUR_MATCH") : "MEET YOUR MATCH";

		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( prodrb.get('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
				event.preventDefault();
                // var element = Event.element(event);
                // checkNavLink(element.parentNode,element.parentNode.classNames());
				launchFoundationFinder();
			});
		});
		
			console.log('bing');
		$$('#foundation-finder-btns .foundationSaveButton').each(function(button) {
			if ( foundationAnswers ) {
				if ( typeof user == 'object' && user.isSignedIn() ) {
					// auto save quiz
					console.log('auto saving quiz results');
					button.hide();
					var params = [{
						'type': 'foundation_finder',
						'data': Object.toJSON(foundationAnswers)
						}];
					jsonRpcWrapper.fetch({
						method : "quiz.save",
						params : params,
						onSuccess: function (jsonRpcResponse) {
							var sobj = jsonRpcResponse.getValue();
							console.log('response from quiz.cache:', sobj);
						//	var str = 'Foundation Match Saved.';
						//	$$('#foundation-finder-btns .progress').each(function(p){p.update(str);});
						},
						onError: function (jsonRpcResponse) {
							console.log(jsonRpcResponse.getMessages());
						//	var str = 'Error saving foundation info.';
						//	$$('#foundation-finder-btns .progress').each(function(p){p.update(str);});
							}
						});
					}
				else{
				console.log('got foundation finder results, show buttons');
					button.observe('click',function(saveClickEvent){
						var params = [{
								'type': 'foundation_finder',
								'data': Object.toJSON(foundationAnswers)
								}];
						jsonRpcWrapper.fetch({
							method : "quiz.cache",
							params : params,
							onSuccess: function (jsonRpcResponse) {
								var sobj = jsonRpcResponse.getValue();
								console.log('response from quiz.cache:', sobj);
							//	var str = '';
							//	$$('#foundation-finder-btns .progress').each(function(p){p.update(str);});
								var host = location.href.parseUri().host;
								var target = '';
								if ( typeof user == 'object' && user.recognized_user ) {
									// send to sign in page before saving
									target = 'https://'+host+'/account/signin.tmpl';
									}
								else{
									// send to register page before saving
									target = 'https://'+host+'/account/registration.tmpl';
									}
								new HiddenForm({
										action: target
										}).elements({
											RETURN_URL: '/diagnostics/foundation_finder/index.tmpl?FF=1'
											}).submit();
									},
							onError: function (jsonRpcResponse) {
								console.log(jsonRpcResponse.getMessages());
							//	var str = 'Error saving foundation info.';
							//	$$('#foundation-finder-btns .progress').each(function(p){p.update(str);});
								}
							});
						});
			//		saveClickEvent.preventDefault();
					}
				}
			else{
				console.log('no foundation finder results');
				button.hide();
				}
			});
		createLwPopupLinks(); // There's a popup for the undertones link
		var skuDataArray = [];
		skuIDsArray.each(function(skuID) {
			var skuObjToAdd = self.productData.skus.find(function(skuObj) {
				return skuObj.SKU_ID === skuID;
			})
			if (skuObjToAdd) {
				skuDataArray.push(skuObjToAdd);
			}
		});		    
    	var rb = new Resource({
    		CONFIG_LIST: ["language"],
    		LIMIT: ['Neutral_Undertone', 'Pink_Undertone', 'Golden_Undertone', 'Neutral_Undertone_Value', 'Pink_Undertone_Value', 'Golden_Undertone_Value'],
    		CALLBACK: function (rb) {
    		console.log('checking undertones');
    		    var neutralLabel = rb.keys.get('Neutral_Undertone');
    		    var pinkLabel = rb.keys.get('Pink_Undertone');
    		    var goldenLabel = rb.keys.get('Golden_Undertone');
    		    var neutralValue = rb.keys.get('Neutral_Undertone_Value');
    		    var pinkValue = rb.keys.get('Pink_Undertone_Value');
    		    var goldenValue = rb.keys.get('Golden_Undertone_Value');
    			var getUndertoneName = function(undertoneValue) {    			    
    				switch (undertoneValue) {
    					case neutralValue :
    						return neutralLabel;
    						break;
    					case pinkValue :
    						return pinkLabel;
    						break;
    					case goldenValue :
    						return goldenLabel;
    						break;
    					default:
    						return "";
    						break;
    				}
    			};
    			skuDataArray.each(function(sku) {
              //      sku.undertoneName = getUndertoneName(sku.UNDERTONE);
                    sku.undertoneName = sku.UNDERTONE;
    		console.log('undertone:'+sku.UNDERTONE);
    			});
        		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.tmpl"
		$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.tmpl').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);
			}
		});
	}
});
