// ---------------------------------------------------------------------
// Produces an inline calendar for selecting a date.
// 
// Requires: dom.js
// ---------------------------------------------------------------------

/*@cc_on @*/     // Who doesn't love IE?

// ---------------------------------------------------------------------
// Convenience functions for setting up a popup calendar.

function dnCalendarCreatePopups (classname, imgurl, side) {
    if (!side) side = "right";
    
    var button = document.createElement("a");
    button.href = "javascript:// Open or close pop-up calendar.";
    var img = button.appendChild(document.createElement("img"));
    img.src = imgurl;
    img.align = "top";
    img.style.border = "none";
    img.className ? img.className += " dnCalendarPopupButton" :
                    img.className  =  "dnCalendarPopupButton";
    
    // Initialize the calendar and assign event handlers.
    var inputs = dnGetElementsByClassName(classname, "input");
    for (var i = 0; i < inputs.length; i++) {
        if (!inputs[i].id) inputs[i].id = "dnPopupCalendarInputId" + i;
        var inputid = inputs[i].id;
        
        var id = inputid + ":dncal";
        var date = dnCalendarParseDate($(inputid).value);
        if (!date) date = new Date();
        var cal = new dnCalendar(id, dnCalendarPopupCallback, date, "none");
        $(id).style.display = "none";
        
        var buttonDup = button.cloneNode(true);
        buttonDup.firstChild.id = inputid + ":dncalbutton";
        buttonDup.id = inputid + ":dncalbutton";
        var nbsp = document.createTextNode("\u00A0");
        
        if (side == "left") {
            inputs[i].parentNode.insertBefore(buttonDup, inputs[i]);
            inputs[i].parentNode.insertBefore(nbsp, inputs[i]);
        }
        else {
            inputs[i].parentNode.insertBefore(buttonDup, inputs[i].nextSibling);
            inputs[i].parentNode.insertBefore(nbsp, buttonDup);
        }
        
        dnAddEvent(buttonDup, "click", dnCalendarTogglePopup);
        dnAddEvent(inputs[i], "keyup", dnCalendarPopupChange);
    }
    
    // Add event handlers to other elements that accept keyboard focus so 
    // that calendards can be dismissed when they gain focus.
    inputs = document.getElementsByTagName("input");
    var textareas = document.getElementsByTagName("textarea");
    var buttons = document.getElementsByTagName("button");
    var focusable = dnConcatNodeLists(inputs, textareas, buttons);
    for (var j = focusable.length - 1; j >= 0; j--) {
        if (focusable[j].type == "hidden") continue;
        dnAddEvent(focusable[j], "focus", dnCalendarPopupBlur);
    };
    
    // Blur the popups when clicking elsewhere in the document.
    dnAddEvent(document, "click", dnCalendarPopupBlur);
};

function dnCalendarPopupChange(e) {
    var input = e.target ? e.target : e.srcElement;
    var cal = $(input.id + ":dncal");
    if (cal.style.display == "none") return;
    
    var date = dnCalendarParseDate(input.value);
    if (!date) date = new Date();
    dnCalendar.registry[cal.id].setDate(date);
};

function dnCalendarTogglePopup (e) {
    var target = e.target ? e.target : e.srcElement;
    var input = $(target.id.replace(/:dncalbutton$/, ""));
    var cal = $(target.id.replace(/button$/, ""));
    if (cal.style.display == "block")
        dnCalendarPopupBlur(e);
    else {
        dnCalendarPopupBlur(e);
        dnCalendarPopupFocus(e);
    }
    
    return false;
};

function dnCalendarPopupFocus (e) {
    var target = e.target ? e.target : e.srcElement;
    var input = $(target.id.replace(/:dncalbutton$/, ""));
    var cal = $(target.id.replace(/button$/, ""));
    
    cal.style.visibility = "hidden";
    cal.style.display = "block";
    dnCalendarPopupChange({target: input});
    dnCalendarPopupPosition(cal.id);
    cal.style.visibility = "visible";
};

function dnCalendarPopupBlur (e) {
    var target = e.target ? e.target : e.srcElement;
    if (this == document && target.id.match(/:dncalbutton$/)) return;
    
    for (var calid in dnCalendar.registry) {
        var cal = dnCalendar.registry[calid];
        if ($(calid).style.display == "none") continue;
        if (target != $(cal.popupInputId) && !dnContains($(calid), target))
            $(calid).style.display = "none";
        
        // Fix for IE6's windowed elements not observing z-index ordering.
        /*@if (@_jscript_version <= 5.6)
        var iframeSheild = $(calid + ":iframeSheild");
        if (iframeSheild) document.body.removeChild(iframeSheild);
        @end @*/
    }
};

function dnCalendarPopupPosition (id) {    
    var cal = $(id);
    var calWidth = cal.offsetWidth;
    var calHeight = cal.offsetHeight;
    var padding = 10;
    
    var button = $(id + "button");
    var buttonPos = dnFindPos(button);
    var input = $(id.replace(/:dncal/, ""));
    var inputPos = dnFindPos(input);
    var inputHeight = input.offsetHeight;
    
    var windowWidth = window.innerWidth ? window.innerWidth :
                      document.documentElement.clientWidth;
    var windowHeight = window.innerHeight ? window.innerHeight :
                       document.documentElement.clientHeight;
    
    // Since the button can be on the right side of the input, we use leftx
    // and rightx as the abstraction of the point furthest left of the 
    // input/button combo, and rightx to be the rightmost point.
    var leftx, rightx;
    if (buttonPos[0] > inputPos[0]) {  // Button on right side.
        leftx = inputPos[0];
        rightx = buttonPos[0] + button.offsetWidth;
    }
    else {  // Button on left side.
        leftx = buttonPos[0];
        rightx = inputPos[0] + input.offsetWidth;
    }
    
    var pos = [0,0];
    // Place calendar on the right side.
    if (rightx + padding + calWidth < windowWidth)
        pos[0] = rightx + padding;
    // Place calendar on the left side.
    else if (leftx - padding - calWidth > 0)
        pos[0] = leftx - padding - calWidth;
    // Place calendar completely below input, either aligned leftx or rightx.
    else {
        if (rightx - calWidth > 0)
            pos[0] = rightx - calWidth;
        else
            pos[0] = leftx;
        pos[1] = inputPos[1] + inputHeight + padding;
    }
    
    // Put calendar slightly above input, if y coord isn't yet set.
    if (pos[1] == 0) pos[1] = inputPos[1] - padding;
    
    cal.style.left = pos[0] + "px";
    cal.style.top = pos[1] + "px";
    
    // Fix for IE6's windowed elements not observing z-index ordering.
    /*@if (@_jscript_version <= 5.6)
    var iframeSheild = $(id + ":iframeSheild");
    if (!iframeSheild) {
        iframeSheild = document.createElement("iframe");
        iframeSheild.id = id + ":iframeSheild";
        iframeSheild.style.position = "absolute";
        iframeSheild.style.zIndex = parseInt(dnGetStyle(cal, "zIndex"));
        iframeSheild.style.filter = "progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)";
        document.body.insertBefore(iframeSheild, cal);
    }
    iframeSheild.style.width = cal.offsetWidth + "px";
    iframeSheild.style.height = cal.offsetHeight + "px";
    iframeSheild.style.left = cal.style.left;
    iframeSheild.style.top = cal.style.top;
    @end @*/
};

function dnCalendarPopupCallback (date, id) {
    var cal = dnCalendar.registry[id];
    var input = $(id.replace(/:dncal$/, ""));
    
    var month = date.getMonth() + 1;
    if (month < 10) month = "0" + month;
    
    var day = date.getDate();
    if (day < 10) day = "0" + day;
    
    var preserve = input.value.replace(/^\s*\d+[\/\-]\d+[\/\-]\d+/, "");
    input.value = month + "-" + day + "-" + date.getFullYear() + preserve;
    input.focus();
    $(id).style.display = "none";
    
    // Fix for IE6's windowed elements not observing z-index ordering.
    /*@if (@_jscript_version <= 5.6)
    var iframeSheild = $(id + ":iframeSheild");
    if (iframeSheild) document.body.removeChild(iframeSheild);
    @end @*/
};

function dnCalendarParseDate (s) {
    var d = /(\d+)[\/\-](\d+)[\/\-](\d+)/.exec(s);
    if (!d || d.length != 4) return null;
    if (d[3] < 1900 || d[3] > 2100) return null;
    if (d[1] < 1 || d[1] > 12) return null;
    
    var lastDate = dnCalendar.getLastDayInMonth(d[1] - 1, d[3]);
    if (d[2] < 1 || d[2] > lastDate) return null;
    
    var date = new Date();
    date.setFullYear(d[3], d[1] - 1, d[2]);
    return date;
};

// ---------------------------------------------------------------------

function dnCalendar (id, callback, date, displayStyle) {
    this.id = id;
    this.callback = callback;
    this.date = date ? date : new Date();
    this.displayMonth = this.date.getMonth();
    this.displayYear  = this.date.getFullYear();
    
    this.weekStartDay = 0;
    
    // Create and insert the calendar div into the document.
    var calDiv = document.createElement("div");
    calDiv.id = id;
    calDiv.className = "dnCalendar";
    calDiv.style.display = displayStyle ? displayStyle : "block";
        
    // Create and insert the top navigation for the calendar.
    var calNavTable = calDiv.appendChild(document.createElement("table"));
    calNavTable.id = id + ":navigation";
    calNavTable.className = "dnCalendarNavigation";
    calNavTable.style.width = "100%";
    calNavTable.style.margin = "0";
    var calNavTbody = calNavTable.appendChild(document.createElement("tbody"));
    var calNav = calNavTbody.appendChild(document.createElement("tr"));
    var calNavLeft = calNav.appendChild(document.createElement("td"));
    var calNavMiddle = calNav.appendChild(document.createElement("td"));
    var calNavRight = calNav.appendChild(document.createElement("td"));
    
    // Set up one month forward and back navigation.
    calNavLeft.appendChild(document.createTextNode("\u00ab"));
    calNavLeft.className = "dnCalendarClickable dnCalendarNavButton";
    calNavRight.appendChild(document.createTextNode("\u00bb"));
    calNavRight.className = "dnCalendarClickable dnCalendarNavButton";
    
    var me = this;
    dnAddEvent(calNavLeft, "click", function () { me.goToPreviousMonth(); });
    dnAddEvent(calNavRight, "click", function () { me.goToNextMonth(); });
    
    // Set up month and year navigation.
    var monthSel = calNavMiddle.appendChild(document.createElement("select"));
    monthSel.id = id + ":monthSelect";
    monthSel.dnCalendarId = id;
    monthSel.className = "dnCalendarSelect";
    monthSel.onchange = dnCalendar.monthChangeCallback;
    for (var i = 0; i < 12; i++) {
        var option = monthSel.appendChild(document.createElement("option"));
        option.appendChild(document.createTextNode(dnCalendar.longMonthNames[i]));
        option.value = i;
    }
    
    calNavMiddle.appendChild(document.createTextNode(" "));
    
    var yearSel = calNavMiddle.appendChild(document.createElement("select"));
    yearSel.id = id + ":yearSelect";
    yearSel.dnCalendarId = id;
    yearSel.className = "dnCalendarSelect";
    yearSel.onchange = dnCalendar.yearChangeCallback;
        
    // Set up the table for the calendar display.
    var calTable = calDiv.appendChild(document.createElement("table"));
    calTable.id = id + ":table";
    calTable.className = "dnCalendarTable";
    calTable.style.width = "100%";
    calTable.style.height = "100%";
    calTable.style.margin = "0";
    
    // Column groups for styling.
    for (var i=0; i < 7; i++) {
        var className = "dnCalendarCol ";
        var day = (7 + i + this.weekStartDay) % 7;
        if (day == 0 || day == 6)
            className += "dnCalendarWeekendCol";
        else
            className += "dnCalendarWeekdayCol";
        
        var col = calTable.appendChild(document.createElement("col"));
        col.className = className;
    }
    
    var calThead = calTable.appendChild(document.createElement("thead"));
    var calHeadRow = calThead.appendChild(document.createElement("tr"));
    calHeadRow.style.height = "auto";
    for (var i=this.weekStartDay; i < this.weekStartDay+7; i++) {
        var th = calHeadRow.appendChild(document.createElement("th"));
        th.appendChild(document.createTextNode(dnCalendar.tinyDayNames[i%7]));
    }
    
    var calTbody = calTable.appendChild(document.createElement("tbody"));
    calTbody.id = id + ":tableBody";
    
    if (!dnCalendar.registry) dnCalendar.registry = {};
    dnCalendar.registry[id] = this;
    
    document.body.appendChild(calDiv);
    if (dnGetStyle(calDiv, "display") != "none") this.redraw();
};

// ---------------------------------------------------------------------

dnCalendar.prototype.redraw = function () {
    // Set the month SELECT
    $(this.id+":monthSelect").value = this.displayMonth;
    
    // Set the year SELECT.
    var yearSel = $(this.id+":yearSelect");
    while (yearSel.firstChild) { yearSel.removeChild(yearSel.firstChild) }
    for (var i = this.displayYear - 5; i <= this.displayYear + 5; i++) {
        var opt = yearSel.appendChild(document.createElement("option"));
        opt.appendChild(document.createTextNode(i));
        opt.value = i;
    }
    yearSel.value = this.displayYear;
    
    // Remove existing rows from the table.
    var tbody = $(this.id+":tableBody");
    var rows = tbody.getElementsByTagName("tr");
    for (var i = rows.length - 1; i >= 0; i--) {
        tbody.removeChild(rows[i]);
    }
        
    // tdCount keeps track of how many td's we've added to the current row.
    var tdCount = 0;
    var firstRow = tbody.appendChild(document.createElement("tr"));
    
    // Find the first day that the table begins at. More often than not
    // this is in the previous month.
    var tmpDate = new Date();
    tmpDate.setDate(1);
    tmpDate.setFullYear(this.displayYear);
    tmpDate.setMonth(this.displayMonth);
    var startDay = tmpDate.getDay();
    
    // The first row likely contains dates from the previous month.
    if (startDay != this.weekStartDay) {
        var prevMo = this.displayMonth ? this.displayMonth - 1 : 11;
        var prevYear = prevMo != 11 ? this.displayYear : this.displayYear - 1;
        var prevMoEndDate = dnCalendar.getLastDayInMonth(prevMo, prevYear);
        
        var date = prevMoEndDate - ((startDay - this.weekStartDay + 7) % 7) + 1;
        while (date <= prevMoEndDate) {
            this._addtd(firstRow, date, prevMo, prevYear, "dnCalendarDateGray");
            ++date;
            ++tdCount;
        }
    }
    
    // Create TDs for all the dates in the displayMonth.
    var date = 1;
    var moEndDate = dnCalendar.getLastDayInMonth(this.displayMonth,this.displayYear);
    var row = firstRow;
    while (date <= moEndDate) {
        if (tdCount % 7 == 0)
            row = tbody.appendChild(document.createElement("tr"));
        this._addtd(row, date, this.displayMonth, this.displayYear);
        ++date;
        ++tdCount;
    }
    
    // Fill out the last row with dates from the next month.
    date = 1;
    while (tdCount % 7 != 0) {
        var nextMo = this.displayMonth != 11 ? this.displayMonth + 1 : 0;
        var nextYear = nextMo != 0 ? this.displayYear : this.displayYear + 1;
        
        this._addtd(row, date, nextMo, nextYear, "dnCalendarDateGray");
        ++date;
        ++tdCount;
    }
};

// ---------------------------------------------------------------------
// Function to add a date TD to a TR.

dnCalendar.prototype._addtd = function (row, date, month, year, extraClass) {
    var dateObj = new Date();
    dateObj.setFullYear(year, month, date);
    
    var callback = this.callback;
    var id = this.id;
    var handleClick = function () { callback(dateObj, id); };
    
    var td = row.appendChild(document.createElement("td"));
    td.appendChild(document.createTextNode(date));
    td.onclick = handleClick;
    
    td.className = "dnCalendarDate dnCalendarClickable";
    if (extraClass) td.className += " " + extraClass;
    if (date == this.date.getDate() && month == this.date.getMonth()
                                    && year == this.date.getFullYear())
        td.className += " dnCalendarDateSelected";

    return td;
};

// ---------------------------------------------------------------------

dnCalendar.prototype.setDate = function (date) {
    this.date = date;
    this.displayMonth = date.getMonth();
    this.displayYear = date.getFullYear();
    this.redraw();
};

// ---------------------------------------------------------------------

dnCalendar.prototype.setWeekStartDay = function (day) {
    this.weekStartDay = day;
    this.redraw();
};

// ---------------------------------------------------------------------

dnCalendar.monthChangeCallback = function () {
    var cal = dnCalendar.registry[this.dnCalendarId];
    cal.displayMonth = parseInt(this.value);
    cal.redraw();
};

dnCalendar.yearChangeCallback = function () {
    var cal = dnCalendar.registry[this.dnCalendarId];
    cal.displayYear = parseInt(this.value);
    cal.redraw();
};

// ---------------------------------------------------------------------

dnCalendar.prototype.goToPreviousMonth = function () {
    if (this.displayMonth == 0) --this.displayYear;
    this.displayMonth = (this.displayMonth - 1 + 12) % 12;
    this.redraw();
};

dnCalendar.prototype.goToNextMonth = function () {
    if (this.displayMonth == 11) ++this.displayYear;
    this.displayMonth = (this.displayMonth + 1) % 12;
    this.redraw();
};

// ---------------------------------------------------------------------

dnCalendar.getLastDayInMonth = function (month, year) {
    if (month != 1) return dnCalendar.lastDayInMonth[month];
    if (!(year % 400) || (!(year % 4) && (year % 100))) return 29;
    return 28;
};

// ---------------------------------------------------------------------
// Data

dnCalendar.lastDayInMonth = [
    31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
];

dnCalendar.shortMonthNames = [
    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
    "Sep", "Oct", "Nov", "Dec"
];

dnCalendar.longMonthNames = [
    "January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December"
];

dnCalendar.tinyDayNames = [
    "S", "M", "T", "W", "T", "F", "S"
];

dnCalendar.shortDayNames = [
    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
];

dnCalendar.longDayNames = [
    "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
];

// ---------------------------------------------------------------------
// Adds the default stylesheet.

function dnCalendarDefaultStyle () {
    dnAddCss("          \
div.dnCalendar {        \
    position: absolute; \
    width: 16em;        \
    height: 10em;       \
}                       \
                        \
td.dnCalendarClickable {         \
    cursor: pointer;             \
    -moz-user-select: none;      \
    -khtml-user-elect: element;  \
    user-select: element;        \
}                                \
                                 \
td.dnCalendarDate {              \
    text-align: center;          \
    color: #006184;              \
    font-weight: bold;           \
    text-decoration: underline;  \
}                                \
                                 \
table.dnCalendarNavigation select {  \
    margin: 2px;                     \
    font-size: 10px;                 \
}                                    \
                                     \
table.dnCalendarNavigation td { \
    text-align: center;         \
}                               \
                                \
table.dnCalendarNavigation {    \
    background-color: #80B1C2;  \
    background-image: url(/images/backgrounds/search_info.gif);  \
    border-collapse: collapse;  \
    border-spacing: 2px;        \
    border: solid 1px black;    \
    border-bottom: none;        \
}                               \
                                \
td.dnCalendarDateGray {         \
    color: #80B1C2;             \
}                               \
                                \
col.dnCalendarWeekendCol {      \
    background-color: #CBCBFF;  \
}                               \
                                \
td.dnCalendarNavButton {        \
    color: #fff;          \
    font-weight: bold;          \
    font-size: large;           \
    text-align: center;         \
}                               \
                                \
table.dnCalendarTable {         \
    border-collapse: collapse;  \
}                               \
                                \
table.dnCalendarTable th, table.dnCalendarTable td { \
    border: solid 1px black; \
    background-color: white; \
}                            \
                             \
table.dnCalendarTable th { \
   background-color: #80B1C2; \
}                            \
                                                          \
div.dnCalendar td.dnCalendarDateSelected {  \
    background-color: #006184;  \
    color:#fff;                 \
}");
};

// ---------------------------------------------------------------------

