var FACETATOR_VERSION="1.0.0";
$(document).ready(function() {
    jQuery.each(facet.definitions, function(i, def) {
        if (def.values) {
            var newValues = [];
            jQuery.each(def.values, function(j, val) {
                var valDef;
                if (typeof(val) == "string") {
                    valDef = { display: val };
                    // { display: val, sort: val, restriction: val }
                } else {
                    valDef = val;
                }
                if (! valDef.sort) {
                    valDef.sort = valDef.display;
                }
                if (! valDef.restriction) {
                    valDef.restriction = valDef.display;
                }
                newValues.push(valDef);
            });
            def.values = newValues;
        }
    });

    //------ check boxes to show everything or just a summary

    var altTr = $("#facets tr.strictSummary");
    var altTd = $("#facets tr.strictSummary td");
    altTr.empty();
    for(var i=0; i < facet.definitions.length; i++) {
    	var clone = altTd.clone();
        altTr.append(clone);
    }
    $(".strictSummary label").each(function(i, elem) {
        if (facet.definitions[i].strictSummaryLabel) {
            $(elem).append(facet.definitions[i].strictSummaryLabel);
        }
    });
    $(".strictSummary input").bind("click", function(event) {
        refresh();
        // don't return false or the check box won't change
    });

    //------

    var headTr = $("#facets tr.head");
    var headTd = $("#facets tr.head td");
    headTr.empty();
    for(var i=0; i < facet.definitions.length; i++) {
    	var clone = headTd.clone();
        headTr.append(clone);
        clone.find(".headTitle").append(facet.definitions[i].label);
    }

    $(".headClear").each(function(i, ele) {
        $(ele).bind("click", function(event) {
            jQuery.each(getSelectBox(facet.definitions[i].key).options,
                function(o, opt) {
                    opt.selected = false;
                });
            refresh();
            return false; // prevent href from being used
        });
    });

    //------

    var boxTr = $("#facets tr.box");
    var boxTd = $("#facets tr.box td");
    boxTr.empty();
    for(var i=0; i < facet.definitions.length; i++) {
        boxTr.append(boxTd.clone());
    }

    // assign individual IDs and change handlers
    $("#facets td select").each(function(e, elem) {
        $(elem).attr("id", facet.definitions[e].key);
        $(elem).bind("change", function(event) {
            refresh();
        });
    });

    //------

    // with keyup, refresh gets the new content
    $("#filter").bind("keyup", function(event) {
        if(event.keyCode == 13) {
            refresh();
        } else {
            toggleClearFilter(); // otherwise performed by refresh()
        }
    });

    //------

    $.ajaxSetup({
        error: function(xhr, error, exception) {
            alert("[Facetator] A data transmition error happened: "+error)
        }
    });
    loadEntries();
});
function loadEntries() {
    var url;
    if (window.location.protocol == "file:") {
        url = facet.localFileName;
    } else {
        url = facet.remoteFileName;
    }
    $.getJSON(url, function(data, status) {
        if (status != "success") {
            alert(status);
        }
        globalEntries = data.items;
        refreshWithRestrictions(parseQueryString());
    });
}

function parseQueryString() {
    var str = window.location.search;
    if (str) {
        if (str.indexOf("?") == 0) {
            str = str.substring(1);
        }
        return parseRestrictionString(str);
    } else {
        return {};
    }
}
function parseRestrictionString(str) {
    var restr = {};
    var parts = str.split("&");
    for(var i=0; i<parts.length; i++) {
        var part = parts[i];
        var eqIndex = part.indexOf("=");
        if (eqIndex >= 0) {
            var key = unescape(part.substring(0, eqIndex));
            var val = unescape(part.substring(eqIndex+1));
            if (! restr[key]) {
                restr[key] = [];
            }
            restr[key].push(val);
        }
    }
    return restr;
}
function printQueryString() {
    var restrs = collectRestrictions();
    var base = window.location.href;
    base = stripSuffixFrom("?", base);
    base = stripSuffixFrom("#", base);
    var str = "";
    jQuery.each(restrs, function(key, values) {
        jQuery.each(values, function(i, value) {
            if (str.length > 0) {
                str += "&";
            }
            str += escape(key)+"="+escape(value);
        });
    });
    window.prompt("URL for the current results", base+"?"+str);
}
function stripSuffixFrom(suffixBegin, str) {
    var index = str.indexOf(suffixBegin);
    if (index >= 0) {
        return str.substring(0, index);
    } else {
        return str;
    }
}
function toggle(elem) {
	if (elem.getAttribute("state") == "expanded") {
		elem.setAttribute("state", "collapsed");
	} else {
		elem.setAttribute("state", "expanded");
	}
    return false; // stop event
}
function reset() {
    jQuery.each(facet.definitions, function(i, def) {
        clearFacetBox(def.key);
    });
    $("#filter").val("");
    refresh();
}

function toggleClearFilter() {
    var visible = ($("#filter").val().length > 0);
    $("#clearFilter")[0].style.visibility = (visible ? "visible" : "hidden");
}
function toggleStrictSummary(key, visible) {
    $(".strictSummary td")[indexOfKey(key)].style.visibility = (visible ? "visible" : "hidden");
}
function isStrictSummary(key) {
    return !$(".strictSummary input")[indexOfKey(key)].checked;
}
function indexOfKey(key) {
    for(var i=0; i<facet.definitions.length; i++) {
        if (facet.definitions[i].key == key) {
            return i;
        }
    }
    return -1;
}
function clearFilter() {
    $("#filter").val("");
    refresh();
}

function refresh(pageNo) {
    refreshWithRestrictions(collectRestrictions(), pageNo);
}
function refreshWithRestrictions(restr, pageNo) {
    toggleClearFilter();
    var filter = $("#filter").val();
    var entries = collectOptions(filter, restr, globalEntries);
    $("#content").empty();
    entries.sort(function(e1,e2) {
    	for(var i=0; i<facet.sortKeys.length; i++) {
            var key = facet.sortKeys[i].substr(1);
            var asc = (facet.sortKeys[i].indexOf("+") == 0 ? +1 : -1);
            var v1 = e1[key];
            var v2 = e2[key];
            if (v1 < v2) {
                    return -1 * asc;
            } else if (v1 > v2) {
                    return +1 * asc;
            }
            // values are equal => try next sort key
    	}
    	return 0;
    });
    // new page starts at (facet.pageSize * x) + 1
    var pageCount = parseInt((entries.length-1) / facet.pageSize)+1;
    if (!pageNo) pageNo = 0;
    if (pageNo >= pageCount) pageNo = pageCount-1;
    if (pageNo < 0) pageNo = 0;
    for(var i=(pageNo*facet.pageSize); i<((pageNo+1)*facet.pageSize); i++) {
        var entry = entries[i];
        if (!entry) continue;
        var clone = $("#template").clone();
        clone.removeAttr("id");
        fillInClone(clone, i, entry);
        clone.appendTo("#content");
    }
    $("#entryCount").text("Results: "+entries.length+".");

    var cssClass = (pageNo == 0 ? "hidden" : "");
    $("#entryCount").append("&nbsp;&nbsp;<a class='"+cssClass+"' href='javascript:gotoPage("+(pageNo-1)+")'>Previous</a> ");
    for(i=0; i<pageCount; i++) {
        cssClass = (pageNo == i ? "pageNo selected" : "pageNo");
        $("#entryCount").append("<a class='"+cssClass+"' href='javascript:gotoPage("+i+")'>"+(i+1)+"</a>");
    }
    cssClass = (pageNo == (pageCount-1) ? "hidden" : "");
    $("#entryCount").append(" <a class='"+cssClass+"' href='javascript:gotoPage("+(pageNo+1)+")'>Next</a>");
}
function gotoPage(pageNo) {
    refresh(pageNo);
}
function fillInClone(clone, index, entry) {
    clone.find("span:contains($)").each(function(i, ele) {
        var key = $(ele).text().substring(1);
        var value;
        if (key == "__index__") {
            value = ""+(index+1);
        } else {
            value = getEntryValue(entry, key, $(ele).attr("facetLinks"));
        }
        if (value) {
            $(ele).replaceWith(value);
        } else {
            $(ele).replaceWith("");
        }
        
    });
    fillInAttributes(clone, entry, "_", "href");
    clone.find("[ifExists]").each(function(i, ele) {
        var key = ele.getAttribute("ifExists");
        if (!entry[key]) {
            $(ele).replaceWith("");
        }
    });
    clone.find("[ifNotExists]").each(function(i, ele) {
        var key = ele.getAttribute("ifNotExists");
        if (entry[key]) {
            $(ele).replaceWith("");
        }
    });
    clone.find("a.toggle")
    .each(function(key, anchor) {
        anchor.setAttribute("onclick", "return toggle(this);");
        anchor.setAttribute("href", "#");
    });
}

// Attributes such as href are specified as _href="$foo" in the template,
// because some browsers put the base URL in front of $foo.
// Thus, the attrKeyPrefix is "_" in these cases and "" otherwise.
function fillInAttributes(clone, entry, attrKeyPrefix, attrKey) {
    var oldKey = attrKeyPrefix+attrKey;
    clone.find("["+oldKey+"^=$]").each(function(i, ele) {
        var entryKey = ele.getAttribute(oldKey).substring(1);
        var value = getEntryValue(entry, entryKey);
        ele.removeAttribute(oldKey)
        if (value) {
            ele.setAttribute(attrKey, value);
        }
    });
}

function getEntryValue(entry, key, printLinks) {
    var label = entry[key+"_entry"];
    var value = entry[key]; // might be undefined
    if (! value) {
        return null;
    }
    if (! label) {
        label = value;
    }
    if (jQuery.isArray(value)) {
        var str = "";
        for(var i=0; i<value.length; i++) {
            if (i > 0) {
                str += ", ";
            }
            str += createLink(key, value[i], label[i], printLinks);
        }
        return str;
    } else {
        return createLink(key, value, label, printLinks);
    }
}
function createLink(key, value, label, printLinks) {
    if (printLinks) {
        return "<a href='javascript:go(&quot;"+escape(key)+"="+escape(value)+"&quot;)'>"+label+"</a>"
    } else {
        return label;
    }
}
function go(str) {
    refreshWithRestrictions(parseRestrictionString(str));
}

function restoreSelectedOptions(select, restr) {
    var opts = select.options;
    for(var i=0; i < opts.length; i++) {
        opts[i].selected = (restr && (jQuery.inArray(opts[i].value, restr) >= 0));
    }
}
function collectRestrictions() {
    var restr = {};
    jQuery.each(facet.definitions, function(i, facetDef) {
        var haveSelections = false;
        $(getSelectBox(facetDef.key)).find("option:selected")
        .each(function(x, selected) {
            if (!restr[facetDef.key]) {
                restr[facetDef.key] = [];
            }
            restr[facetDef.key][x] = selected.value;
            haveSelections = true;
        });
        if (facetDef.strictSummaryLabel) {
            toggleStrictSummary(facetDef.key, haveSelections);
        }
    });
    return restr;
}

//--------------- collecting options

function collectOptions(filter, restrictionMap, entries) {
    var outEntries = [];
    var keyToValueMap = {};
    jQuery.each(facet.definitions, function(f, facetDef) {
        keyToValueMap[facetDef.key] = {};
    });
    jQuery.each(facet.definitions, function(f, facetDef) {
       facetDef.strictSummary = isStrictSummary(facetDef.key);
    });
    jQuery.each(entries, function(e, entry) {
        if (!matchesFilter(entry, filter)) return; // continue
        var matchMap = {};
        var matchAll = true;
        jQuery.each(facet.definitions, function(f, facetDef) {
            var entryValue = entry[facetDef.key];
            matchMap[facetDef.key] = xmatches(restrictionMap[facetDef.key], entryValue);
            if (! matchMap[facetDef.key]) {
                matchAll = false;
            }
        });
        if (matchAll) {
            outEntries.push(entry);
        }
        jQuery.each(facet.definitions, function(f, facetDef) {
            var value = entry[facetDef.key];
            if (!value) return; // continue
            var sort = entry[facetDef.key+"_sort"];
            if (matchesExcept(matchMap, (facetDef.strictSummary ? facetDef.key : null))) {
                var valueMap = keyToValueMap[facetDef.key];
                if (jQuery.isArray(value)) {
                    jQuery.each(value, function(i, val) {
                        var sortVal = (sort ? sort[i] : null);
                        collectOptionsHelper(valueMap, val, sortVal);
                    });
                } else {
                    collectOptionsHelper(valueMap, value, sort);
                }
            }
        });
    });
    jQuery.each(facet.definitions, function(f, facetDef) {
        var select = getSelectBox(facetDef.key);
        valueMapToOptions(facetDef, select.options, keyToValueMap[facetDef.key]);
        restoreSelectedOptions(select, restrictionMap[facetDef.key]);
    });
    return outEntries;
}
function collectOptionsHelper(valueMap, display, sort) {
    var valueMapEntry = valueMap[display];
    if (! valueMapEntry) {
        if (!sort) {
            sort = display;
        }
        valueMap[display] = {
            display: display,
            sort: sort,
            restriction: display, // currently: restr = text
            count: 1
        };
    } else {
        valueMapEntry.count++;
    }
}
function valueMapToOptions(facetDef, options, valueMap) {
    var optionList = [];
    if (facetDef.values) {
        jQuery.each(facetDef.values, function(i, vdef) {
            var optDef = {
                display: vdef.display,
                sort: vdef.sort,
                restriction: vdef.restriction,
                count: 0
            };
            jQuery.each(valueMap, function(display, def) {
                if (matchesRestriction(display, vdef.restriction)) {
                    delete valueMap[display];
                    optDef.count += def.count;
                }
            });
            if (optDef.count > 0) {
                optionList.push(optDef);
            }
        });
    }

    if (! facetDef.dontAddValuesFromEntries) {
        // add remaining options
        jQuery.each(valueMap, function(key, val) {
            optionList.push(val);
        });
        var factor = (facetDef.sortDesc ? -1 : +1);
        optionList.sort(function(o1, o2) {
            return ((o1.sort > o2.sort) - (o1.sort < o2.sort)) * factor;
        });
    }
    options.length = 0;
    jQuery.each(optionList, function(i, val) {
        // options is DOM, does not have push() method
        options[options.length] = new Option(val.display+" ("+val.count+")", val.restriction);
    });
}

//---------------

function getSelectBox(facetKey) {
    return $("select")[indexOfKey(facetKey)];
}
function clearFacetBox(key) {
    // quick and dirty way of clearing the selection
    $(getSelectBox(key)).empty();
}

//--------------- matching

function greaterOrEqual(str, restr) {
    return str > restr || str.toLowerCase().indexOf(restr) == 0;
}

function lessOrEqual(str, restr) {
    return str < restr || str.toLowerCase().indexOf(restr) == 0;
}

function matchesRestrictionConjunction(entryValue, restrArray) {
    for(var i=0; i<restrArray.length; i++) {
        if (matchesRestriction(entryValue, restrArray[i])) {
            return true;
        }
    }
    return false;
}
function matchesRestriction(entryValue, restriction) {
    if (jQuery.isArray(entryValue)) {
        for(var i=0; i<entryValue.length; i++) {
            if (matchesOne(entryValue[i], restriction)) {
                return true;
            }
        }
        return false;
    } else {
        return matchesOne(entryValue, restriction);
    }
}

function matchesOne(value, restriction) {
    var v = value.toLowerCase();
    var r = restriction.toLowerCase();
    var pipeIndex = r.indexOf("|");
    if (pipeIndex == 0) { // example: "|1989"
        return lessOrEqual(v, r.substr(pipeIndex+1));
    } else if (pipeIndex == restriction.length-1) { // example: "1989|"
        return greaterOrEqual(v, r.substr(0, pipeIndex));
    } else if (pipeIndex >= 0) { // example: 1990|1999
        var lower = r.substr(0, pipeIndex);
        var upper = r.substr(pipeIndex+1);
        return greaterOrEqual(v, lower) && lessOrEqual(v, upper);
    } else {
        return value.toLowerCase() == restriction.toLowerCase();
    }
}
function xmatches(restrConjunction, value) {
    if (!restrConjunction || restrConjunction.length == 0) return true;
    // there is a restriction => will never match an undefined value
    if (! value) return false;

    // the current facet value must be among the restrictions
    if (jQuery.isArray(value)) {
        for(var i=0; i<value.length; i++) {
            if (matchesRestrictionConjunction(value[i], restrConjunction)) {
                return true;
            }
        }
        return false;
    } else {
        return matchesRestrictionConjunction(value, restrConjunction);
    }
}
function matchesFilter(entry, filter) {
    if (filter.length == 0) return true;
    filter = filter.toLowerCase();
    var matches = false;
    jQuery.each(entry, function(key, value) {
        if (!value) value = "";
        if (jQuery.isArray(value)) {
            for(var i=0; i<value.length; i++) {
                if (value[i].toLowerCase().indexOf(filter) >= 0) {
                    matches = true;
                    return false; // break
                }
            }
        } else {
            if (value.toLowerCase().indexOf(filter) >= 0) {
                matches = true;
                return false; // break
            }
        }
    });
    return matches;
}
function matchesExcept(matches, key) {
    var matchesAll = true;
    jQuery.each(matches, function(k, bool) {
        if (!bool && k!=key) {
            matchesAll = false;
            return false; // break
        }
    });
    return matchesAll;
}
