/*
  searchable select
*/

function decorateSearchableSelectWidget(id, presearch) {
    var widget = $(id);
    
    var input = INPUT({"type": "text", "id": id + "_search"});
    var cancel = BUTTON({"class": "button button-delete", "title": "Reset filter."}, SPAN("reset"));
    var search = DIV({"class": "full-select-searchable-search"}, LABEL({"for": id + "_search"}, "Filter keuzes op:"), " ", input, " ", cancel);
    
    function forEachLabel(fn) {
        var choices = widget.childNodes;
        for(var i=0; i<choices.length; i++) {
            var choice = choices[i];
            if(choice.nodeType == 1) {
                var labels = choice.getElementsByTagName("label");
                for(var j=0; j<labels.length; j++) {
                    if(fn(choice, labels[j]))
                        break;
                }
            }
        }
    }
    
    function filter() {
        try {
            var re = new RegExp(input.value, "i");
            removeElementClass(input, "invalid");
        } catch(e) {
            addElementClass(input, "invalid");
            return;
        }
        forEachLabel(
            function (choice, label) {
                var text = (label.childNodes.length == 1 ? label.firstChild.nodeValue : (label.textContent || label.innerText || scrapeText(label)));
                if (text.search(re) == -1) {
                    choice.style.display = "none";
                    choice.firstChild.disabled = true; // to make sure a hidden select does not go through
                } else {
                    choice.style.display = "";
                    choice.firstChild.disabled = undefined;
                    return true;
                }
            });
    }
    
    function clear(event) {
        input.value = "";
        forEachLabel(
            function (choice, label) {
                choice.style.display = "";
                choice.firstChild.disabled = undefined;
            });
        event.preventDefault();
    }
    
    function throttled(fn, delay) {
        var timeout = null;
        return function() {
            if(timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            timeout = setTimeout(fn, delay);
        }
    }
    
    function ignore(e) {
        if(e.type() == 'keyup' || e.type() == 'keydown') {
            if(e.key().string == 'KEY_ENTER') {
                e.preventDefault();
            }
        }
    }
    
    connect(input, "onkeydown", ignore);
    connect(input, "onkeyup", throttled(filter, 100));
    connect(input, "onchange", throttled(filter, 100));
    connect(cancel, "onclick", clear);
    
    insertSiblingNodesAfter(widget, search);
    
    if(presearch) {
        input.value = presearch;
        filter();
    }
}


/*
  group select
*/

function flagElementClass(node, flag, className) {
    if (flag) {
        addElementClass(node, className);
    } else {
        removeElementClass(node, className);
    }
}

function selectGroupSelection(group, selected) {
    flagElementClass(group, selected, "group-selection-selected");
    forEach(group.childNodes,
            function (child) {
                if (child.nodeType == 1) {
                    if(hasElementClass(child, "group-selection-label"))
                        flagElementClass(child, selected, "group-selection-label-selected");
                    if(hasElementClass(child, "group-selection-contents"))
                        flagElementClass(child, selected, "group-selection-contents-visible");
                }
            });
}

function decorateNormalGroupSelectWidget(name) {
    var name = name;
    var groups = $(name + "_groups");
    
    function resetGroup(form) {
        var inputs = form[name];
        if (!isArrayLike(inputs))
            inputs = [inputs];
        forEach(inputs,
                function(input) {
                    removeElementClass(input.parentNode.parentNode, "group-selection-selected");
                });
    }
    
    forEach(groups.childNodes,
            function (group) {
                if (group.nodeType == 1 && group.tagName == "DIV" && hasElementClass(group, "group-selection-selectable")) {
                    var input = group.firstChild.firstChild;
                    connect(input, "onchange",
                            function() {
                                /*
                                if (input.type == "radio")
                                    resetGroup(input.form);
                                if (input.checked)
                                    addElementClass(group, "group-selection-selected");
                                else
                                    removeElementClass(group, "group-selection-selected");
                                */
                               selectGroupSelection(group, input.checked);
                            });
                    connect(input, "onclick",
                            function() {
                                // IE workaround
                                input.blur();
                                input.focus();
                            });
                }
            });
}

function foldGroupSelection(group, fold) {
    flagElementClass(group, !fold, "group-selection-folded");
    forEach(group.childNodes,
            function (child) {
                if (child.nodeType == 1) {
                    if(hasElementClass(child, "group-selection-contents"))
                        flagElementClass(child, !fold, "group-selection-contents-folded");
                }
            });
}

function decorateSearchableGroupSelectWidget(name) {
    var name = name;
    var options = $(name + "_options");
    var groups = $(name + "_groups");
    
    decorateSearchableSelectWidget(options);
    
    function forEachInput(fn) {
        forEach(options.getElementsByTagName("input"),
                function (input) {
                    if (input.name == name && (input.type == "checkbox" || input.type == "radio"))
                        fn(input);
                });
    }
    
    function reselect(event) {
        var input = event.src();
        var option = $(name + "_" + input.value);
        selectGroupSelection(option, input.checked);
    }
    
    forEachInput(function (input) {
            connect(input, "onchange", reselect);
            connect(input, "onclick",
                    function() {
                        // IE workaround
                        input.blur();
                        input.focus();
                    });
        });

    // Make the searchable options list foldable
    if (options && hasElementClass(options.parentNode.parentNode, "group-selection-foldable")) {
        var option = options.parentNode.parentNode;
        var header = option.firstChild;
        connect(header, "onclick", function(event) {
                    foldGroupSelection(option, hasElementClass(option, "group-selection-folded"));
                });
    }

    function toggleGroupFold (event) {
                var target = event.target();
                if (target) {
                    while (target.parentNode) {
                        if (hasElementClass(target, "group-selection-label")) {
                            var option = target.parentNode;
                            if(hasElementClass(option, "group-selection-foldable")) {
                                foldGroupSelection(option, hasElementClass(option, "group-selection-folded"));
                            }
                            break;
                        } else {
                            target = target.parentNode;
                        }
                    }
                }
            };

    // Make the (selected) options foldable
    if (groups) {
        connect(groups, "onclick", toggleGroupFold);
    }
}


/*
  date widget
*/

function buildDateWidget(id) {
    var input = $(id);
    var container = DIV({"class": "calendar"}, DIV({id: id+"_container"}));
    
    insertSiblingNodesAfter(input, DIV(null, container, BR({"style": "clear: both"})));
    
    input.calendar = new YAHOO.widget.Calendar(id+"_calendar", id+"_container", {
        show_week_header: true,
        multi_select: false,
        start_weekday: 1,
        iframe: false
    });
    
    var cfg = input.calendar.cfg;
    cfg.setProperty("NAV_ARROW_LEFT", context_path + "/img/action_return_icon.gif");
    cfg.setProperty("NAV_ARROW_RIGHT", context_path + "/img/action_goto_icon.gif");
    cfg.setProperty("WEEKDAYS_SHORT", nl_weekdays_short);
    cfg.setProperty("MONTHS_LONG", nl_months_long);
    
    function select(event, args) {
        var date = new Date(args[0][0][0], args[0][0][1]-1, args[0][0][2]);
        input.value = writeDate(date);
        removeElementClass(input, "invalid");
    }
    
    input.calendar.selectEvent.subscribe(select);
    
    function change() {
        var date = parseDate(input.value);
        if(date) {
            removeElementClass(input, "invalid");
            input.calendar.select(date, true);
            input.calendar.setMonth(date.getMonth());
            input.calendar.setYear(date.getFullYear());
            input.calendar.render();
        } else {
            addElementClass(input, "invalid");
        }
    }
    
    connect(input, "onkeyup", change);
    connect(input, "onchange", change);
    
    if(input.value)
        change();
    else
        input.calendar.render();
    
    var d = 4 + (navigator.appName == "Microsoft Internet Explorer" ? 1 : 0) + (navigator.userAgent.indexOf("Safari") != -1 ? 2 : 0);
    input.style.width = getElementDimensions(container).w - d + "px";
}


/*
  date interval widget
*/

function buildIntervalWidget(id, multiple) {
    var widget = $(id);
    var intervals = $(id + "_intervals");

    widget.multiple = multiple;

    widget.addIntervalElement = partial(addIntervalElement, intervals, id);
    widget.removeIntervalElement = partial(removeIntervalElement, intervals, id);    

    widget.getIntervals = partial(getIntervals, intervals, id);
    widget.addInterval = partial(addInterval, widget);    
    widget.removeInterval = partial(removeInterval, widget);    

    widget.getIntervalWith = partial(getIntervalWith, widget);

    widget.addIntervalDate = partial(addIntervalDate, widget);
    widget.removeIntervalDate = partial(removeIntervalDate, widget);

    widget.renderIntervalsList = partial(renderIntervalsList, widget);
    widget.updateCalendarSelection = partial(updateCalendarSelection, widget);    

    widget.showDate = partial(showDate, widget);    

    widget.insertBefore(DIV({"class": "calendar"}, DIV({id: id+"_container"})), widget.lastChild);
    widget.calendar = new YAHOO.widget.Calendar(id+"_calendar", id+"_container", {
        show_week_header: true,
        multi_select: true,
        start_weekday: 1,
        iframe: false
    });

    widget.updateCalendarSelection(true);    

    var cfg = widget.calendar.cfg;
    cfg.setProperty("NAV_ARROW_LEFT", context_path + "/img/action_return_icon.gif");
    cfg.setProperty("NAV_ARROW_RIGHT", context_path + "/img/action_goto_icon.gif");
    cfg.setProperty("WEEKDAYS_SHORT", nl_weekdays_short);
    cfg.setProperty("MONTHS_LONG", nl_months_long);

    widget.calendar.selectEvent.subscribe(function(event, args) {
        if(!widget.ignoreEvents) {
            forEach(Interval.reducedFromDates(map(function(d) {
                return new Date(d[0], d[1]-1, d[2]);
            }, args[0])), function(interval) {
                widget.addInterval(interval);
            });
            widget.calendar.render();
            widget.renderIntervalsList();
        }
    });

    widget.calendar.deselectEvent.subscribe(function(event, args) {
        if(!widget.ignoreEvents) {
            forEach(args[0], function(selected) {
                var date = new Date(selected[0], selected[1]-1, selected[2]);
                var interval = widget.getIntervals() && widget.getIntervals().length==1 && widget.getIntervals()[0];
                if (!widget.multiple && interval && interval.inLeftHalf(date) && interval.start < date && interval.end.getTime()>plusDays(interval.start, 1).getTime()) {
                  widget.removeInterval(new Interval(interval.start, date));
                  interval.start = plusDays(date, 1);
                } else {
                  widget.removeIntervalDate(date);
                }
            });
            widget.renderIntervalsList();
        }
    });

    widget.calendar.render();

    widget.list = UL({"class": "interval-list"});
    widget.intervals = DIV({"class": "intervals"},
                           multiple ? LABEL("Geselecteerde perioden:") : LABEL("Geselecteerde periode:"),
                           widget.list);

    widget.insertBefore(widget.intervals, widget.lastChild);

    if(multiple) {
        var dataInput = INPUT({type: "text", size: 26,
                               title: "Periode om toe te voegen."});
        var addButton = BUTTON({type: "button", "class": "button button-create", disabled: true,
                                title: "Voeg de opgegeven periode toe."}, "Toevoegen");

        var getDateInputInterval = function(warn) {
            try {
                var year = widget.calendar.cellDates[7][0];
                var interval = parseInterval(dataInput.value, year);
                return interval;
            } catch (e) {
                if(warn)
                    alert(e);
            }
        }

        var addDateInputInterval = function(interval) {
            if (interval != undefined) {
                widget.addInterval(interval);
                widget.renderIntervalsList();
                widget.calendar.select(interval.getDays(), true, true);
                widget.calendar.setMonth(interval.start.getMonth());
                widget.calendar.setYear(interval.start.getFullYear());
                widget.calendar.render();
                dataInput.value = "";
            }        
        }

        connect(dataInput, "onkeydown", function(e) {
            if(e.key().string == 'KEY_ENTER') {
                try {
                    addDateInputInterval(getDateInputInterval(false));
                } finally {
                    e.stopPropagation();
                    e.preventDefault();
                }
            }
        });

        connect(dataInput, "onkeyup", function(e) {
            var interval = getDateInputInterval(false);
            if (interval == undefined) {
                addButton.disabled = true;
                addElementClass(dataInput, "invalid");
            } else {
                addButton.disabled = false;     
                removeElementClass(dataInput, "invalid");
            }
            addButton.disabled = (interval == undefined);
        });

        connect(addButton, "onclick", function(e) {
            addDateInputInterval(getDateInputInterval(true));
            e.stopPropagation();
            e.preventDefault();
        });        

        var helpButton = BUTTON({type: "button", "class": "button button-help", title: "Toon hulp bij invoeren..."}, "?");
        var helpElement = DIV({"class": "help", style: "display: none"},
                              P("Perioden kunt u op de volgende manieren tekstueel invoeren:"),
                              UL({},
                                 LI("\"week 1 t/m week 4\""),
                                 LI("\"01-01 tot 01-02\""),
                                 LI("\"1 jan tot 1 feb\""),
                                 LI("\"1 januari tot 1 februari\"")),
                              P("Al deze perioden kunnen zowel 'tot' als 't/m' zijn."),
                              P("Opgegeven week-nummers vallen altijd in het huidige catalogusjaar."));

        connect(helpButton, "onclick", function(e) {
            helpElement.style.display = helpElement.style.display == "none" ? "block" : "none";
            e.stopPropagation();
            e.preventDefault();
        });

        widget.intervals.appendChild(DIV({"class": "interval-add"},
                                         dataInput, " ", addButton, " ", helpButton,
                                         helpElement));
    }                              

    widget.renderIntervalsList();
}

function addInterval(widget, interval) {
    var joinWith = [];
    forEach(widget.getIntervals(), function(existing) {
        if(!widget.multiple || interval.overlaps(existing) || interval.abuts(existing)) {
            widget.removeIntervalElement(existing);
            joinWith.push(existing);
        }
    });
    if (joinWith.length > 0) {
        if(widget.multiple) {
            joinWith.push(interval);
            widget.addIntervalElement(reduce(function(a, b) {
                return a.join(b);
            }, joinWith));
        } else {
            if(joinWith.length > 0) {
                var existing = joinWith[0];
                var start = existing.start < interval.start ? existing.start : interval.start;
                var end = existing.end > interval.end ? existing.end : interval.end;
                var replaced = new Interval(start, end);
                widget.addIntervalElement(interval.join(joinWith[0]));
                widget.calendar.select(replaced.getDays(), true, true);
            } else {
                widget.addIntervalElement(interval);
            }
        }
    } else {
        widget.addIntervalElement(interval);
    }
}

function addIntervalElement(container, name, interval) {
    var element = INPUT({type: "hidden", name: name, value: interval.toISOInterval()});
    element.interval = interval;
    container.appendChild(element);
}                            

function removeInterval(widget, interval) {
    widget.removeIntervalElement(interval);
    widget.renderIntervalsList();
    widget.calendar.deselect(interval.getDays(), true, true);
    widget.calendar.render();
}

function removeIntervalElement(container, name, interval) {
    forEach(container.childNodes, function(node) {
        if (node.tagName == "INPUT" && interval.equals(node.interval)) {
            node.interval = undefined;
            removeElement(node);
        }
    });
}

function getIntervals(container) {
    return sorted(imap(Interval.fromNode, filter(function(node) {
        return node.tagName == "INPUT";
    }, container.childNodes)));
}

function getIntervalWith(widget, date) {
    return findIf(widget.getIntervals(), function(interval) {
        return interval.contains(date);
    });
}

function addIntervalDate(widget, date) {
    widget.addInterval(Interval.fromDate(date));
}

function removeIntervalDate(widget, date) {
    var interval = widget.getIntervalWith(date);
    if(interval != undefined) {
        widget.removeIntervalElement(interval);
        if(widget.multiple) {
            forEach(interval.split(date), function(split) {
                widget.addInterval(split);
            });
        } else {
            var intervals = interval.split(date);
            if(intervals.length > 0) {
                widget.addInterval(intervals[0]);
                if (intervals.length > 1) {
                    widget.calendar.deselect(intervals[1].getDays(), true, true);
                    widget.calendar.render();
                }
            }
        }
    }
}

function renderIntervalsList(widget) {
    var calendar = widget.calendar;

    var elements = map(function(interval) {
        var item;

        var start = STRONG(shortFormat(interval.start));
        connect(start, "onclick", function(e) {
            widget.showDate(item.interval.start);
        });

        var end = interval.isOneDay() ? null : STRONG(shortFormat(minusDays(interval.end, 1)));        

        var del = BUTTON({type: "button", "class": "button button-delete", title: "Verwijder deze periode."}, SPAN("X"));
        connect(del, "onclick", function(e) {
            widget.removeInterval(item.interval);
        });

        if (end == undefined) {
            item = LI({"class": "interval"}, start, " ", del);
        } else {
            item = LI({"class": "interval"}, start, " t/m ", end, " ", del);
            connect(end, "onclick", function(e) {
                widget.showDate(item.interval.end);
            });
        }

        item.interval = interval;        
        return item;

    }, widget.getIntervals());

    if(elements.length == 0) {
        replaceChildNodes(widget.list, LI({"class": "interval empty-selection-notifier"}, "Nog geen perioden geselecteerd."));        
    } else {
        replaceChildNodes(widget.list, elements);
    }
}

function updateCalendarSelection(widget, reset) {
    var calendar = widget.calendar;
    try {
        widget.ignoreEvents = true;
        forEach(widget.getIntervals(), function(interval) {
            calendar.select(interval.getDays(), true, true);
            if(reset === true) {
                calendar.setMonth(interval.start.getMonth());
                calendar.setYear(interval.start.getFullYear());
                reset = false;
            }
        });
    } finally {
        widget.ignoreEvents = false;        
    }
}

function showDate(widget, date) {
    widget.calendar.setMonth(date.getMonth());
    widget.calendar.setYear(date.getFullYear());    
    widget.calendar.render();
}

// An interval from the start date (inclusive) up to the end date (exclusive)

function Interval(start, end) {
    if(start >= end)
        throw "Start-datum ("+shortFormat(start)+") moet voor de eind-datum ("+shortFormat(end)+") zijn.";
    this.start = new Date(start);
    this.end = new Date(end);
}

Interval.fromDate = function(date) {
    return new Interval(date, plusDays(date, 1));
}

Interval.fromDay = function(year, month, day) {
    return Interval.fromDate(new Date(year, month, day));
}

Interval.fromNode = function(node) {
    if (node.interval == undefined) {
        node.interval = Interval.fromISOInterval(node.value);
    }
    return node.interval;
}

Interval.reducedFromDates = function(dates) {
    var reduced = [];
    forEach(dates, function(date) {
        var interval = Interval.fromDate(date);
        var joined = false;
        for (var i=0; i<joined.length; i++) {
            var existing = reduced[i];
            if (existing.abuts(interval) || existing.overlaps(interval)) {
                reduced[i] = existing.join(interval);
                joined = true;
            }
        }
        if(!joined)
            reduced.push(interval);
    });
    return reduced;
}

Interval.fromISOInterval = function(string) {
    parts = string.split("/");
    return new Interval(isoTimestamp(parts[0]), isoTimestamp(parts[1]));
}

// Determines whether the interval is completely before the given interval or date

Interval.prototype.before = function(obj) {
    if(obj instanceof Date) {
        return this.end <= obj;
    } else if (obj instanceof Interval) {
        return this.before(obj.start) && this.before(obj.end);
    } else {
        throw "Unable to compare.";
    }
}

// Determines whether the interval is completely after the given interval or date

Interval.prototype.after = function(obj) {
    if(obj instanceof Date) {
        return this.start > obj;
    } else if (obj instanceof Interval) {
        return this.after(obj.start) && this.after(obj.end);
    } else {
        throw "Unable to compare.";
    }
}

Interval.prototype.encloses = function(interval) {
    return this.contains(interval.start) && this.contains(minusDays(interval.end, 1));
}

Interval.prototype.contains = function(date) {
    return date >= this.start && date < this.end;
}

Interval.prototype.overlaps = function(interval) {
    return this.contains(interval.start) || this.contains(minusDays(interval.end, 1)) || interval.encloses(this)
}

Interval.prototype.abuts = function(interval) {
    return this.start.getTime() == interval.end.getTime() || this.end.getTime() == interval.start.getTime();
}

Interval.prototype.join = function(obj) {
    if(obj instanceof Date) {
        if(this.contains(obj)) {
            return new Interval(this.start, this.end);
        } else if (this.before(obj)) {
            return new Interval(this.start, plusDays(obj, 1));
        } else {
            return new Interval(obj, this.end);
        }
    }if(obj instanceof Interval) {
        if(this.abuts(obj)) {
            if(this.end.getTime() == obj.start.getTime()) {
                return new Interval(this.start, obj.end);
            } else {
                return new Interval(obj.start, this.end);
            }
        } else {
            return new Interval(this.start < obj.start ? this.start : obj.start,
                                this.end > obj.end ? this.end : obj.end);
        }
    } else {
        throw "Unable to join.";
    }
}

// Returns the intervals that would be if this date is removed from this interval

Interval.prototype.split = function(date) {
    var intervals = [];
    if(this.contains(date)) {
        if(this.isFirst(date)) {
            if(!this.isOneDay())
                intervals.push(new Interval(plusDays(this.start, 1), this.end));
        } else if (this.isLast(date)) {
            if(!this.isOneDay())
                intervals.push(new Interval(this.start, date));
        } else {
            intervals.push(new Interval(this.start, date));
            intervals.push(new Interval(plusDays(date, 1), this.end));           
        }
    }
    return intervals;
}

Interval.prototype.isOneDay = function() {
    return this.isLast(this.start);
}

Interval.prototype.isFirst = function(date) {
    return date.getTime() == this.start.getTime();
}

Interval.prototype.isLast = function(date) {
    return date.getTime() == minusDays(this.end, 1).getTime();
}

Interval.prototype.getDays = function(f) {
    var days = [];
    var date = this.start;
    var year = date.getFullYear();
    var month = date.getMonth();
    var day = date.getDate();
    var i = 0;
    while(date < this.end) {
        days.push(date);
        date = new Date(year, month, day + (i++));
    }
    return days;
}

Interval.prototype.forEachDay = function(f) {
    var date = new Date(this.start);
    while(date < this.end) {
        f(date);
        date.setDate(date.getDate()+1);
    }
}

Interval.prototype.toISOInterval = function() {
    return toISOTimestampWithTZ(this.start, true) + "/" + toISOTimestampWithTZ(this.end, true);
}

Interval.prototype.toLocaleDateString = function() {
    return shortFormat(this.start) + " tot " + shortFormat(this.end);
}

Interval.prototype.toString = function() {
    return "[" + this.toLocaleDateString() + "]";
}

Interval.prototype.compare = function(other) {
    if(this.start.getTime() == other.start.getTime()) {
        if(this.end.getTime() == other.end.getTime()) {
            return 0;
        } else {
            return compare(this.end, other.end);
        }
    } else {
        return compare(this.start, other.start);
    }
}

Interval.prototype.equals = function(other) {
    return this.compare(other) == 0;
}

Interval.prototype.inLeftHalf = function (date) {
    return (date.getTime()-this.start.getTime())<(this.end.getTime()-date.getTime());
}

registerComparator("interval",
                   function(x) {
                       return x instanceof Interval;
                   },
                   function(a, b) {
                       return a.compare(b);
                   });

// Date utilities

function dateInCatalogusjaar(year, month, day) {
    if(typeof year == "string")
        year = new Number(year);
    if(typeof month == "string")
        month = new Number(month);
    if(typeof day == "string")
        day = new Number(day);
    if(!year && current_catalogusjaar) {
        if (month > current_catalogusjaar_van.getMonth())
            year = current_catalogusjaar_van.getFullYear();
        else
            year = current_catalogusjaar_tot.getFullYear();
    } else if (year < 1000) {
        current_date = new Date();
        year = year + (current_date.getFullYear() - (current_date.getFullYear() % 100));
    }
    return new Date(year, month, day);
}

function parseInterval(str) {
    var start;
    var end;
    
    var catalogusjaar = str.match(/^\s*(?:het|de)?\s*(?:heel|(ge)?hele)?\s*(?:(catalogus|school)?jaar)\s*$/);
    if (catalogusjaar != null) {
        return new Interval(current_catalogusjaar_van, current_catalogusjaar_tot);
    }
    
    var exclusive = str.match(/^\s*(.+?)\s*(?:\/|tot)\s*(.+?)\s*$/);
    if(exclusive != null) {
        start = parseStartDate(exclusive[1]);
        end = parseEndDate(exclusive[2], false);
    }
    
    var inclusive = str.match(/^\s*(.+?)\s*(?:t\/?m|tot\s*(?:en)?\s*met)\s*(.+?)\s*$/);
    if(inclusive != null) {
        start = parseStartDate(inclusive[1]);
        end = parseEndDate(inclusive[2], true);
    }
    
    while(end < start)
        end.setFullYear(end.getFullYear()+1);
    
    if(start != undefined && end != undefined) {
        return new Interval(start, end);

    } else if (start == undefined && end == undefined) {

        start = parseYearDate(str);
        if (start != undefined) {
            end = plusDays(start, 1);
            return new Interval(start, end);
        }

        start = parseYearWeek(str);
        if (start != undefined) {
            end = plusDays(start, 7);
            return new Interval(start, end);
        }        

        throw "Geen geldige data opgegeven.";

    } else {
        throw "Geen geldige periode opgegeven.";
    }
}

function parseStartDate(str) {
    var start = parseYearDate(str);
    if (start == undefined) {
        return parseYearWeek(str);
    } else {
        return start;
    }
}

function parseEndDate(str, inclusive) {
    var end = parseYearDate(str);
    if (end == undefined) {
        end = parseYearWeek(str);
        if (end != undefined && inclusive)
            end = plusDays(end, 7);
    } else {
        if (inclusive)
            end = plusDays(end, 1);
    }
    return end;
}

function parseYearDate(str) {
    var numericDate = str.match(/^\s*(\d{1,2})\s*[-\s]\s*(\d{1,2})(?:\s*[-\s]\s*(\d{2,4}))?\s*$/);
    if (numericDate != null) {
        var day = new Number(numericDate[1]);
        var month = new Number(numericDate[2]) - 1;
        var year = numericDate[3] || null;
        return dateInCatalogusjaar(year, month, day);
    }
    var namedDate = str.match(/^\s*\w*?\s*(\d{1,2})\s*(\w{3,})\s*(\d{2,4})?\s*$/);
    if (namedDate != null) {
        var day = new Number(namedDate[1]);
        var month = findValue(nl_months_short, namedDate[2].toLowerCase().substring(0,3));
        if(month == -1)
            month = findValue(nl_months_long, namedDate[2].toLowerCase());
        var year = namedDate[3] || null;
        if(month > -1) {
            return dateInCatalogusjaar(year, month, day);
        }
    }
}

function parseYearWeek(str) {
    var week = str.match(/week\s+(\d{1,2})/);
    if (week != null) {
        return startOfISOweekInCatalogusjaar(new Number(week[1]), current_catalogusjaar_van);
    }    
}

function startOfISOweekInCatalogusjaar(week, catalogusjaar_van) {
    var date = startOfISOweekInYear(week, catalogusjaar_van.getFullYear());
    if(date.getMonth() < catalogusjaar_van.getMonth()) {
        return startOfISOweekInYear(week, catalogusjaar_van.getFullYear()+1);
    } else {
        return date;
    }
}

function startOfISOweekInYear(week, year) {
    var first = new Date(year, 0, 4);
    var start = new Date(year, 0, 5 - (first.getDay() == 0 ? 7 : first.getDay()));
    return new Date(start.getFullYear(), start.getMonth(), start.getDate() + (week - 1) * 7);
}

function toISOTimestampWithTZ(date, midnight) {
    if(midnight) {
        return (padZero(date.getFullYear(), 4) + "-" + padZero(date.getMonth()+1, 2) + "-" + padZero(date.getDate(), 2) + "T" +
                "00:00.000" + toISOTZ(date));
    } else {
        return (padZero(date.getFullYear(), 4) + "-" + padZero(date.getMonth()+1, 2) + "-" + padZero(date.getDate(), 2) + "T" +
                padZero(date.getHours(), 2) + ":" + padZero(date.getMinutes(), 2) + ":" + padZero(date.getSeconds(), 2) + "." +
                padZero(date.getMilliseconds(), 3) + toISOTZ(date));
    }
}

function toISOTZ(date) {
    var offset = date.getTimezoneOffset();
    var minutes = offset % 60;
    var hours = (offset - minutes)/60;
    return (hours < 0 ? "+" : "-") + padZero(hours < 0 ? -hours : hours, 2) + ":" + padZero(minutes, 2);
}

function shortFormat(date) {
    return nl_weekdays_short[date.getDay()] + " " + date.getDate() + " " + nl_months_short[date.getMonth()] + " " + date.getFullYear();
}

function minusDays(date, days) {
    return new Date(date.getFullYear(), date.getMonth(), date.getDate() - days);
}

function plusDays(date, days) {
    return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
}



