﻿/*global jQuery, window, console, document */
/*jslint unparam: true, white: true, browser: true, type: true, unparam: true */

(function ($, undefined) {
    "use strict";
    var csn = window.CSN || (window.CSN = {}),
        currentXhr = {},
        pageTitle = document.title,
        countMatch = /\s*\(\d*\)\s*$/g,
        encode = window.encodeURIComponent || escape,
        decode = window.decodeURIComponent || unescape,
        ithaca = csn.Ithaca = {

            /* Utilities */
            getBaseUrl: function (url) {
                return url.replace(/[?#].*/, "");
            },
            getQueryString: function (url) {
                return (url || window.location.href).match(/\?([^#]+)#?/) ? RegExp.$1 : "";
            },
            redirect: function (query, url) {
                if (!url) {
                    window.location.search = query.toString();
                } else {
                    window.location.href = ithaca.getBaseUrl(url) + "?" + query.toString();
                    //alert(ithaca.getBaseUrl(url) + "?" + query.toString());
                }
            },
            sort: function (a, b) {
                return (a.seoOrder || 999) - (b.seoOrder || 999);
            },
            isEmpty: function (o) {
                return !(o && o.length) || (/^\s+$/.test(String(o)));
            },
            parseEnhancementOptions: function (source) {
                // regexp tests options where:$1 is the key, $2 & $3 match values quoted or not.
                var options = {}, match, value, optionCheck = /(\w+)(?:(?:='([^']+)')|(?:=([^,]+)))?(?=,)/g;
                source += ",";
                while ((match = optionCheck.exec(source))) {
                    options[match[1]] = (!isNaN(match[3]) ? Number(match[3]) : match[3]) || match[2] || true;
                }
                return options;
            },
            replaceValues: function (source, mask) {
                if ($.isArray(source) && $.isArray(mask)) {
                    return $.map(source, function (value) {
                        return $.inArray(value, mask) === -1 ? value : null;
                    });
                } else {
                    return source;
                }
            },
            bindSchema: function (schema, control, source) {
                $.each(schema, function (k, v) {
                    var attr = source.attr(v);
                    if (attr && attr.length) {
                        control[k] = $.trim(attr);
                    }
                });
            },
            getRangeValues: function (source, query) {
                var match = query.match(new RegExp(source + "\\:([^|]+)"));
                return match !== null ? match[1].split(",") : [];
            },
            executeAjax: function (control, beforeSendHandler) {
                var container = control.container, resetAjaxTarget = function (control) {
                    if (control !== null) {
                        control.reset();
                        resetAjaxTarget(control.ajaxTarget);
                    }
                };

                return function () {
                    var queryBuilder, ajaxTarget, i, j, ajaxUrl, ajaxOptions;

                    if (!control.isAjaxTriggerOnly && control.isDefault()) {
                        resetAjaxTarget(control.ajaxTarget);
                        return;
                    }
                    ajaxTarget = control.ajaxTarget || control;
                    beforeSendHandler = ajaxTarget.ajaxBeforeSend || beforeSendHandler || null;

                    if (currentXhr[ajaxTarget.id]) {
                        currentXhr[ajaxTarget.id].abort();
                    }

                    queryBuilder = ithaca.QueryBuilder(container.resourceUrl, true);
                    queryBuilder.seo.enabled = false;
                    queryBuilder.remove("eapi");

                    if (ajaxTarget.inAjaxChain) {
                        for (i = 0, j = container.ajaxChain.length; i < j; i++) {
                            container.ajaxChain[i].updateQuery(queryBuilder);

                            if (container.ajaxChain[i].children === ajaxTarget.id) {
                                // reset any children of current target
                                resetAjaxTarget(ajaxTarget.ajaxTarget);
                                break;
                            }
                        }

                        queryBuilder.update("dim", ajaxTarget.source);
                        queryBuilder.update("plugin", ajaxTarget.plugin);
                    } else {
                        ajaxTarget.updateQuery(queryBuilder);
                    }

                    ajaxUrl = ithaca.getBaseUrl(container.resourceUrl) + "?" + queryBuilder.toString();
                    ajaxOptions = {
                        url: ajaxUrl,
                        dataType: "json",
                        success: function (data) {
                            delete currentXhr[ajaxTarget.id];
                            ajaxTarget.handleAjax(data);
                        }
                    };
                    if ($.isFunction(beforeSendHandler)) {
                        ajaxOptions.beforeSend = function () {
                            beforeSendHandler(ajaxTarget);
                        };
                    }
                    currentXhr[ajaxTarget.id] = $.ajax(ajaxOptions);
                };
            },

            /* Classes & Objects */
            QueryBuilder: function (url, noDecode) {
                var list = { "eapi": "2" },
                    queryString = ithaca.getQueryString(url),
                    params = queryString.split("&"),
					enableSeo = true;

                $.each(params, function () {
                    var keyValue = this.split("="),
                        key = keyValue[0],
                        value = !noDecode ? decode(keyValue[1]) : keyValue[1];
                    if (key !== "eapi" && value.length) {
                        list[key] = value;
                    }
                });

                return {
                    seo: { enabled: (enableSeo) },
                    update: function (key, value, separator) {
                        separator = separator || " ";
                        if (key !== "eapi" && !ithaca.isEmpty(value)) {
                            if (!list.hasOwnProperty(key)) {
                                list[key] = value;
                            } else if (list[key].indexOf(value) === -1) {
                                list[key] += separator + value;
                            }
                        }
                    },
                    once: function (key, value) {
                        if (key !== "eapi") {
                            list[key] = value;
                        }
                    },
                    remove: function (key) {
                        delete list[key];
                    },
                    value: function (key) {
                        return list[key] || "";
                    },
                    toString: function () {
                        var result = [];
                        for (var key in list) {
                            if (key && list.hasOwnProperty(key)) {
                                result.push(key + "=" + $.trim(list[key]));
                            }
                        }
                        return result.join("&");
                    }
                };
            },

            HistoryBuilder: null
        },


    /* Plug-in state management classes */
        Container = function (element, type) {
            this.controls = [];
            this.ajaxChain = [];
            this.element = element;
            this.searchButton = element.find(".search-button");
            this.id = element.attr("id");
            this.type = type;
            this.isLoading = false;
            this.history = null;

            ithaca.bindSchema({
                resourceUrl: "data-resource-url",
                redirectUrl: "data-redirect-url",
                overrideRedirect: "data-override-redirect",
                useLabelAsPlaceholder: "data-use-label-as-placeholder",
                useEnhancements: "data-use-enhancements",
                javascriptRequired: "data-javascript-required"
            }, this, element);
        },

        Control = function (element, container) {
            var options;

            // Reference to Search Engine or Refinement or other container
            this.container = container;

            // DOM element representing the control (not it's components)
            this.element = element;

            /* Control may have a button specific to it */
            this.button = element.find(".link-button");

            // Components are the actual UI elements that make up the Control
            this.components = { length: 0 };

            this.ajaxTarget = null;
            this.ajaxParent = null;
            this.inAjaxChain = false;
            this.isAjaxTriggerOnly = false;
            this.id = "";

            ithaca.bindSchema({ type: "data-control",
                enhancement: "data-enhancement-name",
                isSeo: "data-is-seo",
                seoOrder: "data-seo-order"
            }, this, element);

            options = element.attr("data-enhancement-options");
            this.enhancementOptions = options ? ithaca.parseEnhancementOptions(options) : {};
        },

        ithacaPlugins = {};

    // Module definition for the HistoryBuilder singleton
    (function () {
        var historyStore = {}, id, tab,
            checkIsEmpty = function (history) {
                var count = 0, p;
                for (p in history) {
                    count += (history.hasOwnProperty(p) ? 1 : 0);
                }
                return count === 0;
            };

        ithaca.HistoryBuilder = {
            load: function (tab) {
                var hashBang = decode(window.location.hash.substring(3)), isEmpty;
                if (!ithaca.isEmpty(hashBang) && hashBang.charAt(0) === "{") {
                    try {
                        historyStore[tab] = JSON.parse(hashBang)[tab] || {};
                    } catch (e) {
                        // Error parsing JSON: set to default
                        historyStore[tab] = {};
                        window.location.hash = "";
                    }
                } else {
                    historyStore[tab] = {};
                }
                isEmpty = checkIsEmpty(historyStore[tab]);
                document.title = pageTitle;

                return {
                    seo: { enabled: false },
                    update: function (key, value) {
                        key = key === "N" ? id : key;
                        historyStore[tab][key] = $.trim(value);
                        isEmpty = false;
                        window.location.hash = "!/" + JSON.stringify(historyStore);
                        document.title = pageTitle;
                    },
                    // For Builder API consistency
                    once: function (key, value) {
                        this.update(key, value);
                        window.location.hash = "!/" + JSON.stringify(historyStore);
                        document.title = pageTitle;
                    },
                    value: function (key) {
                        return $.trim(historyStore[tab].hasOwnProperty(key) ? historyStore[tab][key] : "") || "";
                    },
                    remove: function (key) {
                        delete historyStore[tab][key];
                        isEmpty = checkIsEmpty(historyStore[tab]);
                        window.location.hash = "!/" + JSON.stringify(historyStore);
                        document.title = pageTitle;
                    },
                    setId: function (newId) {
                        id = newId;
                    },
                    isEmpty: function () {
                        return isEmpty;
                    },
                    toString: function () {
                        var o = {};
                        o[tab] = historyStore[tab] || {};
                        return "!/" + JSON.stringify(o);
                    }
                };
            }
        };
    } ());

    Control.ajax = {
        "drop-down": function (data) {
            var control = this, defaultText, newItems = [], optGroup = null, parentDropDown,
                newOptGroup = function (item) {
                    return $("<optgroup></optgroup>").attr("label", (!item.Value ? item.Text : ""));
                };

            if (control.defaultText) {
                defaultText = control.getDefaultText();
                if (control.enhancement) {
                    newItems.push(new Option(defaultText, ""));
                } else {
                    control.components[0].options[0].text = defaultText;
                }
            }

            if (data && data.length) {
                $.each(data, function () {
                    var item = $('<option value="' + this.Value + '">' + this.Text + '</option>')[0];

                    if (control.showCount) {
                        item.setAttribute("data-count", this.Attributes.Count);
                    }

                    if (this.Attributes.Source) {
                        item.setAttribute("data-source", this.Attributes.Source);
                    }

                    if (/optgroup$/.test(this.Attributes["class"])) {
                        if (optGroup) {
                            newItems.push(optGroup[0]);
                        }
                        optGroup = newOptGroup(this);
                        if (this.Value) {
                            // Selectable label!!
                            item.className = "group-head";
                            newItems.push(item);
                        } else {
                            optGroup.append(item);
                        }
                    } else if (optGroup) {
                        optGroup.append(item);
                    } else {
                        newItems.push(item);
                    }
                });
            }

            if (optGroup) {
                newItems.push(optGroup[0]);
            }
            control.components.loadItems($(newItems));
            control.components.enable();

            if (control.showCount) {
                control.components.showCount();
            }

            control.components.val(control.getCurrentValue());

            // Force control to trigger change for native component
            if (control.components.jquery) {
                if (control.components[0].selectedIndex === -1) {
                    control.components[0].selectedIndex = 0;
                }
                control.components.change();
            }
        },
        "navigation": function (data) {
            var itemCount = data ? data.length : 0,
                item, html = ['<ul class="more">'],
                lessList = this.components.children("ul:first").addClass("less"), moreList,
                toggleList = function () {
                    moreList.toggle();
                    lessList.toggle();
                };

            for (var i = 0; i < itemCount; i++) {
                item = data[i];

                html.push('<li class="refine-item">');
                html.push('<a href="' + item.Attributes.RefineUrl + '">' + item.Text + '</a><span class="count">(' + item.Attributes.Count + ')</span>');
                html.push('</li>');
            }

            html.push('<li class="refine-item"><a href="#">(less...)</a></li></ul>');

            // Change "more" link
            lessList.hide().children("li:last").children("a").unbind("click").click(toggleList);
            lessList.children(".loading").remove();

            // Add new HTML and attach event to "less" link
            moreList = $(html.join("")).appendTo(this.components);
            moreList.children("li:last").children("a").click(toggleList);
        }
    }

    /* These evaluators are used for QueryStrings and History. The argument "builder" refers to the appropriate builder */
    Control.evaluators = {
        "range": function (builder) {
            var minValue, maxValue,
                newRange = "", foundMatch = false,
                tolerance, temp, query, queryValue, currentRange, rangeIsDefault;

            minValue = this.components.min.val();
            maxValue = this.components.max.val();
            rangeIsDefault = (minValue === this.minDefaultValue && maxValue === this.maxDefaultValue);

            if (parseInt(minValue) > parseInt(maxValue)) {
                temp = minValue;
                minValue = maxValue;
                maxValue = temp;
            }

            if (this.components.tolerance.length) {
                tolerance = this.components.tolerance[0].checked ? this.components.tolerance[0].value : "0";
            }

            query = this.template.split("=");
            queryValue = query[1].replace(/\{0\}/g, (minValue + "," + maxValue));
            if (tolerance === "0") {
                queryValue = queryValue.replace(/~[\d\.]+$/, "");
            }

            currentRange = builder.value(query[0]);

            if (!ithaca.isEmpty(currentRange)) {
                currentRange = currentRange.replace(new RegExp(this.source + "\\:[^|]+\\|*"), "").replace(/\|*$/, "");

                if (!rangeIsDefault) {
                    queryValue = (currentRange.length ? (currentRange + "|") : "") + queryValue;
                } else {
                    queryValue = currentRange;
                }
            }

            if (ithaca.isEmpty(queryValue)) {
                builder.remove(query[0]);
            } else {
                builder.once(query[0], queryValue);
            }
        },
        "multi-list": function (builder) {
            var checkboxes, values = builder.value("N").replace(/\+/g, " ").split(" "),
                isAll = false, isSeo = (this.isSeo && builder.seo.enabled);

            if (this.enhancement) {
                checkboxes = this.components.checkboxes;
                isAll = this.components.val().split(",").length === checkboxes.length;
            } else {
                checkboxes = this.components;
            }

            checkboxes.each(function () {
                var index = $.inArray(this.value, values);
                if (index !== -1) {
                    values.splice(index, 1);
                }
                if (this.checked || isAll) {
                    values.push(this.value);
                    if (isSeo && $(this).attr('data-custom-template')) {
                        builder.update("seo-url", $.trim($(this).attr('data-custom-template')), '/');
                    }
                }
            });
            if (!isSeo) {
                builder.once("N", values.join(" "));
            }
        },
        "drop-down": function (builder) {
            var selectedIndex = this.enhancement ? this.components.selectedIndex : this.components[0].selectedIndex,
				isDefault = selectedIndex === (this.defaultText ? 0 : -1),
				isSeo = (this.isSeo && builder.seo.enabled);

            //also check the source is different to the parent (e.g model group and region)
            if (!isDefault && isSeo && !this.components.isGroupData(this.source)) {
                builder.update("seo-url", this.components.text().replace(countMatch, "").replace(/\./g, "_").replace(/\//g, "__").replace(/\+/g, "___").replace(/-/g, "____"), "/");
                //builder.update("c", this.source, ",");

            } else {
                builder.update("N", this.components.val());
            }
        },
        "postcode": function (builder) {
            var template, value;

            value = this.components.postcode.val();
            if (!ithaca.isEmpty(value)) {
                template = this.postcodeTemplate.replace("{0}", value).split("=");
                builder.once(template[0], template[1]);

                value = this.components.distance.val();
                template = this.distanceTemplate.replace("{0}", value).split("=");
                builder.once(template[0], template[1]);
            }
        },
        "keyword": function (builder) {
            var value = this.components.val(), template = this.template.replace(/\{0\}/g, value).split("&"), i, j, temp;

            for (i = 0, j = template.length; i < j; i++) {
                temp = template[i].split("=");
                if ((value === this.watermark) || ithaca.isEmpty(value)) {
                    builder.remove(temp[0]);
                } else {
                    builder.once(temp[0], temp[1]);
                }
            }
        },
        "custom-list": function (builder) {
            var template = this.template.split("="), key = template[0], value = this.components.val(), isSeo = (this.isSeo && builder.seo.enabled);
            if (this.components.selectedIndex > 0) {
                //also check the source is different to the parent (e.g model group and region)
                if (isSeo && !this.components.isGroupData(this.source)) {
                    builder.update("seo-url", this.components.text().replace(countMatch, "").replace(".", "_").replace("/", "__").replace("+", "___").replace("-", "____"), "/");
                    //builder.update("c", this.source, ",");
                } else {
                    builder.update(key, value);
                }
            }
        },
        "navigation": function (builder) {
            var lnk = this.components.find("a.expand-link"), src = lnk.attr("data-source"), refurl = encode(lnk.attr("data-refurl"));
            builder.once("dim", src);
            builder.once("refurl", refurl);
        }
    };

    /* These loaders assign values from History */
    Control.loaders = {
        "drop-down": function () {
            var value, i, j, option, history = this.container.history;

            this.components = this.element.children("select");
            ithaca.bindSchema({
                defaultText: "data-def-value",
                required: "data-required",
                source: "data-source",
                showCount: "data-showCount",
                children: "data-children",
                isAjaxTriggerOnly: "data-ajax-trigger-only",
                plugin: "data-plugin"
            }, this, this.components);

            this.defaultValue = "";
            this.id = this.components.attr("id");

            if ($.browser.tablet) {
                this.enhancement = "";
            }

            if (this.enhancement) {
                this.applyEnhancement();
                this.components = this.components.data(this.enhancement);
            }
            if (this.showCount) {
                this.components.showCount();
            }

            this.setAjaxChain();

            if (history) {
                if (!history.isEmpty() && (this.components.itemCount() > (this.defaultText ? 1 : 0))) {
                    this.components.val(history.value(this.source));
                }
                this.components.bind("change", this.updateHistory());
            }

            if (this.children) {
                this.components.bind("change", ithaca.executeAjax(this));
            }
        },
        "range": function () {
            var values = [], self = this, label, labelText, components = this.element.find(":input"), history = this.container.history;
            this.components = {
                min: components.filter(".Min"),
                max: components.filter(".Max"),
                tolerance: components.filter(":checkbox")
            };
            ithaca.bindSchema({ minDefaultValue: "data-def-value" }, this, this.components.min);
            ithaca.bindSchema({ source: "data-source", template: "data-template", maxDefaultValue: "data-def-value" }, this, this.components.max);

            if ($.browser.tablet) {
                this.enhancement = "";
                this.element.children("label").each(function () {
                    var label = $(this), text = label.text();
                    if (!text.match(/\:$/)) {
                        label.text(text + ":");
                    }
                });
            } else if (this.enhancement) {
                if (!this.enhancementOptions.hideFromTo) {
                    label = this.element.children("label.min");
                    labelText = label.text();
                    if (labelText.toLowerCase().indexOf("from") === -1) {
                        label.text(labelText.replace(/\:\s*$/, "") + " From:");
                        label = this.element.children("label.max");
                        label.text(labelText + " To:");
                    }
                }
                this.applyEnhancement($([this.components.min[0], this.components.max[0]]));
                this.components.min = this.components.min.data(this.enhancement);
                this.components.max = this.components.max.data(this.enhancement);
            }

            this.isToleranceStateChanged = false;
            this.components.tooltip = this.element.find(".tooltip");

            if (this.components.tooltip.length) {
                csn.loadScript(["$jq/qtip"], function () {
                    var elementsFixed = false,
                        beforeShowHandler = function () {
                            var wrapper = this.elements.wrapper;
                            if (!(elementsFixed || wrapper.children(".arrow").length)) {
                                wrapper.prepend("<span class='arrow'>&nbsp;</span>");
                                wrapper.find(".qtip-contentWrapper")[0].style.border = "";
                                wrapper.find(".qtip-contentWrapper")[0].style.background = "";
                                wrapper.find(".qtip-content")[0].style.color = "";
                                elementsFixed = true;
                            }
                        };
                    self.components.tooltip.qtip({
                        content: self.components.tooltip.attr("data-content"),
                        position: { corner: { tooltip: "leftMiddle", target: "rightMiddle"} },
                        api: { "beforeShow": beforeShowHandler },
                        style: { classes: { "content": "range-tooltip-content"} }
                    }).removeAttr("data-tooltip");
                });
            }

            if (history) {
                values = ithaca.getRangeValues(this.source, history.value("Range"));
            } else {
                values = ithaca.getRangeValues(this.source, csn.queryString("Range"));
            }

            if (values.length === 2) {
                values[1] = values[1].replace(/~[\d\.]+$/, "");
                this.components.min.val(values[0]);
                this.components.max.val(values[1]);
            }

            if (history) {
                this.components.min.bind("change", this.updateHistory());
                this.components.max.bind("change", this.updateHistory());
            }

            if (this.components.tolerance.length) {
                if ($.cookie("userSearch")) {
                    csn.subCookie("userSearch", this.source + "_Tolerance", String(this.components.tolerance[0].checked));
                }

                this.components.tolerance.bind((window.addEventListener ? "change" : "click"), function () {
                    csn.subCookie("userSearch", self.source + "_Tolerance", String(this.checked));
                    if (!self.isToleranceStateChanged) {
                        self.isToleranceStateChanged = true;
                    }
                });
            }
        },
        "multi-list": function () {
            var self = this, checkboxes, values, list = this.element.find("ul:first"), history = this.container.history;

            ithaca.bindSchema({ children: "data-children", source: "data-source", defaultValue: "data-def-value" }, this, list);
            this.id = list.attr("id") || (this.source + "List");

            if ($.browser.tablet) {
                this.enhancementOptions.useLabelAsPlaceholder = false;
            }

            if (this.enhancement) {
                this.applyEnhancement(list);
                this.components = list.data(this.enhancement);
                if (this.defaultValue) {
                    this.components.val(this.defaultValue);
                }
                checkboxes = this.components.checkboxes;
            } else {
                this.components = checkboxes = list.find(":input");
            }

            this.setAjaxChain();

            if (history) {
                if (!history.isEmpty()) {
                    values = history.value(this.source);
                    if (values) {
                        if (this.enhancement) {
                            this.components.val(values);
                        } else {
                            checkboxes.each(function () { this.checked = false; });
                            $.each(values.split(","), function () {
                                var element = checkboxes.filter(":input[value='" + this + "']");
                                element[0].checked = true;
                            });
                        }
                    }
                }

                if (window.addEventListener) {
                    this.components.bind("change", this.updateHistory());
                } else {
                    // IE5-8
                    this.components.bind("click", this.updateHistory());
                }
            }

            if (this.children) {
                this.components.bind("change", ithaca.executeAjax(self));
            }
        },
        "postcode": function () {
            var self = this, currentValue = [], history = this.container.history,
                refineButton = this.element.find(".link-button");

            this.components = {
                postcode: this.element.children("input[type=text]"),
                distance: this.element.children("select")
            };

            // TODO: Can this be more efficient?
            ithaca.bindSchema({ removeNValues: "data-remove-n", currentValue: "data-currentvalue" }, this, this.element);
            ithaca.bindSchema({ postcodeTemplate: "data-template" }, this, this.components.postcode);
            ithaca.bindSchema({ distanceTemplate: "data-template" }, this, this.components.distance);

            if (this.watermark) {
                this.components.postcode.watermark(this.watermark, { useNative: false });
            }

            if (!$.browser.tablet && this.enhancement) {
                $([this.components.postcode.siblings(".span-as-title")[0], this.components.postcode[0]]).wrapAll('<span class="postcode-wrapper"></span>');
                this.applyEnhancement(this.components.distance);
                this.components.distance = this.components.distance.data(this.enhancement);
            }

            if (history) {
                if (!history.isEmpty()) {
                    currentValue = [history.value("postcode"), history.value("distance")];
                    this.components.postcode.val(currentValue[0]);
                    this.components.distance.val(currentValue[1]);
                }
                this.components.postcode.bind("blur", this.updateHistory());
                this.components.distance.bind("change", this.updateHistory());
            } else if (!ithaca.isEmpty(this.currentValue)) {
                currentValue = JSON.parse(this.currentValue);
            }

            if (currentValue.length === 2) {
                this.components.postcode.val(currentValue[0]);
                this.components.distance.val(currentValue[1]);
            }

            this.components.postcode.keypress(function (ev) {
                if (ev.which === 13 && refineButton.length) {
                    ev.preventDefault();
                    refineButton.trigger("click");
                }
            });

            this.validate = function () {
                var postcode = this.components.postcode.val();
                // non-valid postcode returns error message
                return /^\s*\d{4}\s*$/.test(postcode) || (!this.required && ithaca.isEmpty(postcode)) ? "" : "Please enter a valid postcode.";
            }
        },
        "keyword": function () {
            var keyword = this.components = this.element.children("input"), history = this.container.history,
                searchButton, self = this,
                setResetButton = function () {
                    var reset = self.element.children(".reset"),
                        query, key, url;
                    if (reset.length) {
                        key = self.template.split("=")[0];
                        query = ithaca.QueryBuilder();
                        query.remove(key);
                        query.remove("keyword");
                        url = ithaca.getBaseUrl(window.location.href) + "?" + query.toString();
                        reset.attr("href", url);
                    }
                };

            ithaca.bindSchema({ watermark: "data-watermark-text",
                template: "data-template",
                sourceUrl: "data-source-url",
                delay: "data-delay",
                minLength: "data-min-length"
            }, this, keyword);

            if (history) {
                if (!history.isEmpty()) {
                    keyword.val(history.value("keyword"));
                }
                keyword.bind("blur", this.updateHistory());
            }

            if (this.container.type === "search-engine") {
                searchButton = this.container.searchButton;
            } else {
                searchButton = this.element.find(".link-button");
                setResetButton();
            }

            if (this.watermark) {
                keyword.watermark(this.watermark, { useNative: false });
            } else if (this.enhancement) {
                this.applyEnhancement();
            }

            keyword.keypress(function (ev) {
                var error;
                if (ev.which === 13) {
                    error = self.validate(true);
                    if (error.length) {
                        alert(error);
                        keyword.focus();
                    } else {
                        searchButton.trigger("click");
                    }
                    ev.preventDefault();
                }
            });

            this.validate = function (required) {
                var keywords = this.components.val();
                return ithaca.isEmpty(keywords) && this.required ? "Please specify a keyword or keywords to search for." : "";
            }
        },
        "text": function (history) {
            var value, history = this.container.history;
            this.components = this.element.find(":input");

            if (history && !history.isEmpty()) {
                value = history.value(this.id);
                this.components.val(value);
                this.components.bind("blur", this.updateHistory());
            }
        },
        "custom-list": function () {
            var widgetId = this.element.attr("id").replace(/Row$/, ""), history = this.container.history;

            ithaca.bindSchema({ defaultText: "data-def-value",
                required: "data-required",
                source: "data-source",
                showCount: "data-showCount",
                children: "data-children",
                isAjaxTriggerOnly: "data-ajax-trigger-only",
                plugin: "data-plugin",
                template: "data-template"
            }, this, this.element);

            this.defaultValue = "";
            this.components = $("#" + widgetId);
            this.applyEnhancement();
            this.components = this.components.data(this.enhancement);

            if (this.showCount) {
                this.components.showCount();
            }

            this.id = this.element.find(".csn-select").attr("id");
            this.setAjaxChain();

            if (history) {
                if (!history.isEmpty() && (this.components.itemCount() > (this.defaultText ? 1 : 0))) {
                    this.components.val(history.value(this.source));
                }
                this.components.bind("change", this.updateHistory());
            }

            if (this.children) {
                this.components.bind("change", ithaca.executeAjax(this));
            }
        },
        "navigation": function () {
            var container = this.container, checkbox = this.element.find(":checkbox");

            if (checkbox.length) {
                checkbox.bind(window.addEventListener ? "change" : "click", function () { var url = this.getAttribute("data-url"); window.location.href = url; });
                this.components = this.element.find(".jq-collapsible");
                this.components.each(function () {
                    var control = new Control($(this), container);
                    control.type = "navigation";
                    control.init();
                    $(this).data("ithaca", control);
                    container.controls.push(control);
                });
            } else {
                this.components = this.element.find(".body");
                this.components.find("a.expand-link").click(ithaca.executeAjax(this));
                this.isDefault = function () {
                    return false;
                }
            }
        }
    };

    $.extend(Control.prototype, {
        init: function () {
            if (Control.loaders.hasOwnProperty(this.type)) {
                Control.loaders[this.type].apply(this);
            }
        },
        getCurrentValue: function () {
            var history = this.container.history;
            return (history && history.value(this.source)) || this.components.val();
        },
        getDefaultText: function () {
            var value, parentDropDown, text;
            if (this.defaultText) {
                if (this.defaultText.indexOf("{0}") === -1) {
                    value = this.defaultText;
                } else if (this.type === "drop-down" && this.components.jquery) {
                    parentDropDown = this.ajaxParent.components[0];
                    if (parentDropDown.tagName.toUpperCase() === "SELECT") {
                        text = (parentDropDown.selectedIndex >= (this.ajaxParent.defaultText ? 1 : 0)) ? parentDropDown.options[parentDropDown.selectedIndex].text : "";
                        value = this.defaultText.replace("{0}", text);
                    }
                }
            }
            return value;
        },
        applyEnhancement: function (components) {
            components = components || this.components;

            this.enhancementOptions.disabled = this.disabled;

            if (this.defaultText) {
                this.enhancementOptions.defaultText = this.defaultText;
                this.enhancementOptions.defaultValue = this.defaultValue;
            }

            components[this.enhancement](this.enhancementOptions);
        },
        updateQuery: function (query) {
            if (Control.evaluators.hasOwnProperty(this.type)) {
                Control.evaluators[this.type].apply(this, [query]);
            }
        },
        updateHistory: function () {
            var self = this, history = this.container.history;
            return function () {
                history.setId(self.source);
                if (Control.evaluators.hasOwnProperty(self.type)) {
                    Control.evaluators[self.type].apply(self, [history]);
                }
            }
        },
        bindAction: function (handler) {
            this.element.children(".link-button,:button").eq(0).removeAttr("href").click(handler);
        },
        setAjaxChain: function () {
            var i, j, ajaxChain = this.container.ajaxChain;

            for (i = 0, j = ajaxChain.length; i < j; i++) {
                if (ajaxChain[i].children === this.id) {
                    ajaxChain[i].ajaxTarget = this;
                    this.ajaxParent = ajaxChain[i];
                    ajaxChain.splice(i + 1, 0, this);
                    this.inAjaxChain = true;

                    if (this.components.itemCount() <= (this.defaultText ? 1 : 0)) {
                        this.components.disable();
                    }
                    return;
                }
                if (ajaxChain[i].id === this.children) {
                    ajaxChain[i].ajaxParent = this;
                    this.ajaxTarget = ajaxChain[i];
                    ajaxChain.splice(i, 0, this);
                    this.inAjaxChain = true;
                    return;
                }
            }
            if (this.children) {
                ajaxChain.push(this);
                this.inAjaxChain = true;
            }
        },
        ajaxBeforeSend: function (ajaxTarget) {
            ajaxTarget.components.clearItems("Loading...");
        },
        handleAjax: function (data) {
            var func = Control.ajax.hasOwnProperty(this.type) ? Control.ajax[this.type] : null;
            if (func) {
                func.apply(this, [data]);
            }
        },
        isDefault: function () {
            var val = this.getCurrentValue(), isEmpty = ithaca.isEmpty(val);
            return (isEmpty || (!isEmpty && val === this.defaultValue));
        },
        validate: function () {
            if (this.required) {
                return this.isDefault() ? "Please enter or select a value for '" + this.label.text() + "'.\n" : "";
            }
            return "";
        },
        reset: function () {
            // Reset current control, ready for an ajax update.
            var container = this.container;

            this.components.disable();
            this.components.clearItems();

            if (container.isLoading && this === container.ajaxChain[container.ajaxChain.length - 1]) {
                container.isLoading = false;
            }

            if (!container.isLoading && !this.ajaxParent.isAjaxTriggerOnly && this.ajaxParent.isDefault()) {
                container.history.remove(this.source);
            }
        }
    });

    $.extend(ithacaPlugins, {
        "search-engine": function () {
            var container = new Container(this, "search-engine"),
                containerId = this.attr("id"), history;

            container.history = history = ithaca.HistoryBuilder.load(containerId);

            this.find("div[data-control]").each(function () {
                var control = new Control($(this), container);
                control.init();
                $(this).data("ithaca", control);
                container.controls.push(control);
            });

            Control.ajax["custom-list"] = Control.ajax["drop-down"];

            if (!history.isEmpty() && container.ajaxChain.length) {
                container.isLoading = true;
                ithaca.executeAjax(container.ajaxChain[0])();
            }

            container.searchButton.bind("click", container, function (event) {
                var errorMessage = "", i, count, container = event.data,
                    verticalSiloCheck = decode(container.resourceUrl).match(/[&?]vertical=([^&]+)|[&?]silo=([^&]+)|[&?]base=([^&]+)|[&?]N=([^&]+)|[&?]match=([^&]+)/ig),
                    query = ithaca.QueryBuilder(container.redirectUrl),
                    seoUrl, redirectUrl;

                if (verticalSiloCheck) {
                    $.each(verticalSiloCheck, function () {
                        var param = this.split("=");
                        query.update(param[0].substr(1), param[1]);
                    });
                }
                container.controls.sort(ithaca.sort);
                for (i = 0, count = container.controls.length; i < count; i++) {
                    errorMessage = container.controls[i].validate();
                    if (!errorMessage.length) {
                        container.controls[i].updateQuery(query);
                    } else {
                        alert(errorMessage);
                        event.preventDefault();
                        return false;
                    }
                }

                redirectUrl = container.redirectUrl;
                seoUrl = query.value('seo-url');
                query.remove("seo-url");
                if (seoUrl.length > 0) {
                    var a = document.createElement('a');
                    a.href = container.redirectUrl;
                    redirectUrl = a.pathname + seoUrl + a.search;
                    if (redirectUrl && redirectUrl[0] != '/')
                        redirectUrl = "/" + redirectUrl;
                }
                ithaca.redirect(query, redirectUrl);
            });

            // IE6 won't redirect in script if the <a> has an href attribute
            if ($.browser.msie && parseInt($.browser.version) <= 6) {
                container.searchButton.removeAttr("href").css("cursor", "pointer");
            }

            this.find(".adv-search").click(function (ev) {
                // update HREF before redirect with history
                var advId = this.getAttribute("data-advanced-id");

                if (!history.isEmpty()) {
                    this.href += "#" + history.toString().replace(containerId, advId);
                }
            });

            return this.data("ithaca", container);
        },
        "refinement": function () {
            var container = new Container(this, "refinement");

            this.children("ul").each(function () {
                var refinement = $(this),
                    specialized = refinement.attr("data-control"),
                    control;

                if (ithaca.isEmpty(specialized)) {
                    control = new Control(refinement, container);
                    control.type = "navigation";
                    control.init();
                } else {
                    control = new Control(refinement.children(".body").children("div"), container);
                    control.init();
                    control.bindAction(function () {
                        var url = (self.redirectUrl || window.location.href),
                        query = ithaca.QueryBuilder(url), error = control.validate();

                        if (!error) {
                            control.updateQuery(query);
                            ithaca.redirect(query, url);
                        } else {
                            alert(error);
                        }
                    });
                    if (control.type === "keyword") {
                        control.required = true;
                    }
                }

                refinement.data("ithaca", control);
                container.controls.push(control);
            });

            return this.data("ithaca", container);
        }
    });

    $.fn.ithaca = function () {
        var controlType = arguments[0];

        if ($.browser.tablet) {
            $("body").addClass("tablet");
        }

        if (typeof controlType === "string" && ithacaPlugins[controlType]) {
            controlType = controlType.toLowerCase();
            return ithacaPlugins[controlType].apply(this, Array.prototype.slice.call(arguments, 1));
        } else {
            throw "Please specify an Ithaca control to initialize.";
        }
    };

    // jQuery enhancements
    (function () {
        $.extend($.fn, {
            reset: function () {
                if (this.is("select")) {
                    this[0].selectedIndex = 0;
                }
            },
            disable: function () {
                if (this[0].disabled !== undefined) {
                    this[0].disabled = true;
                } else {
                    this.addClass("disabled");
                }
                return this;
            },
            enable: function () {
                if (this[0].disabled !== undefined) {
                    this[0].disabled = false;
                } else {
                    this.removeClass("disabled");
                }
                return this;
            },
            itemCount: function () {
                var element = this.eq(0);
                if (element.is("select")) {
                    return this[0].options.length;
                } else if (element.is("ul,ol")) {
                    return this.children("li").length;
                }
                return 0;
            },
            showCount: function () {
                var element = this[0], opt, i, j, count;
                if (element.tagName.toUpperCase() === "SELECT") {
                    for (i = 0, j = element.options.length; i < j; i++) {
                        opt = element.options[i];
                        count = opt.getAttribute("data-count");
                        if (count) {
                            opt.text += " (" + count + ")";
                        }
                    }
                }
                return this;
            },
            clearItems: function (message) {
                var element = this[0], i;
                if (element.tagName.toUpperCase() === "SELECT") {
                    for (i = element.childNodes.length - 1; i > 0; i--) {
                        element.removeChild(element.childNodes[i]);
                    }
                    if (message) {
                        element.options[0].text = message;
                    }
                    element.selectedIndex = 0;
                }
                return this;
            },
            loadItems: function (options) {
                var element = this[0], i, j;
                for (i = 0, j = options.length; i < j; i++) {
                    element.appendChild(options[i]);
                }
                return this;
            }
        });
        $.browser.tablet = navigator.userAgent.match(/mobile/i) !== null && navigator.userAgent.match(/iPad|Android/i) !== null;
    } ());

    // update the title if it has been replaced by url hash
    $(window).load(function () {
        if (document.title.substring(0, 1) === "#") {
            document.title = pageTitle;
        }
    });

} (jQuery));
