function Calendar(target, type)
{
    this.target = target;
    this.activeCalendar = null;
    this.activeTime = null;

    switch (type)
    {
        case "datetime":
            this.type = this.DATE | this.TIME;
            break;

        case "time":
            this.type = this.TIME;
            break;

        default:
            this.type = this.DATE;
            break;
    }

    this.init();
}

Calendar.prototype.DATE = 1;
Calendar.prototype.TIME = 2;

Calendar.prototype.style = { 'calendar': {
        'container': {
            "position": "absolute",
            "padding": "2px",
            "border": "1px solid #bbb",
            "background-color": "#ddd",
            "font-family": "Arial",
            "width": "200px",
            "height": "176px",
            "text-align": "center",
            "z-index": "100"
        },
        'options': {
            "background-color": "#ddd",
            "font-family": "Arial",
            "font-size": "9pt",
            "padding": "2px 4px 2px 4px",
            "text-align": "center",
            "height": "18px",
            "line-height": "18px"
        },
        'arrows': {
            "font-weight": "bold",
            "cursor": "pointer"
        },
        'monthyear': {
            "font-family": "sans-serif",
            "font-size": "10px",
            "font-weight": "bold",
            "padding": "0 4px 0 4px",
            "display": "inline",
            "cursor": "pointer"
        },
        'calendar': {
            "font-family": "Arial",
            "font-size": "9pt",
            "border-collapse": "collapse",
            "margin": "auto"
        },
        'day': {
            "width": "28px",
            "height": "28px",
            "line-height": "28px",
            "text-align": "center",
            "font-size": "9px",
            "font-weight": "bold",
            "padding": "0",
            "margin": "0"
        },
        'invalid': {
            "background-color": "#f8f8f8",
            "border": "1px solid #bbb",
            "cursor": "pointer",
            "color": "#aaa"
        },
        'valid': {
            "background-color": "#fff",
            "border": "1px solid #bbb",
            "cursor": "pointer",
            "color": "#000"
        }
    },
    'time': {
        'preview': {
            "font-family": "monospace",
            "font-weight": "bold",
            "color": "#000",
            "height": "22px",
            "line-height": "22px",
            "text-align": "center",
            "font-size": "10pt",
            "background-color": "#f8f8f8",
            "border": "1px solid #bbb",
            "margin-bottom": "2px"
        },
        'seperator': {
            "font-weight": "bold",
            "font-size": "12pt"
        },
        'options': {
            "font-size": "8pt"
        }
    }
};

Calendar.prototype.init = function () {
    // set everything up!
    var size = 0;
    if((this.type & this.DATE) == this.DATE)
    {
        size += 12;
    }

    if((this.type & this.TIME) == this.TIME)
    {
        size += 8;
    }

    this.target.size = size;
    this.target.style.backgroundImage = "url(/static/i/calendar.png)";
    this.target.style.backgroundPosition = "center right";
    this.target.style.backgroundRepeat = "no-repeat";
    //this.target.style.paddingRight = "18px";
    this.target.maxLength = size;

    var self = this;

    this.target.onclick = function () {
        self.onClick();
    }
};

Calendar.prototype.activate = function () {
    var self = this;

    var topOffset = 0;

    // read the current date/time value
    try
    {
        var parseValue = this.target.value;

        var dateParts = [""];
        var timeParts = [""];

        if((this.type & this.DATE) == this.DATE)
        {
            // get the date out of here
            var dateIndex = 0;
            for(var i = 0; i < parseValue.length; i++)
            {
                var c = parseValue.substr(i, 1);

                if(c == " ")
                {
                    break;
                }
                else if(c == "/")
                {
                    dateIndex++;
                    dateParts.push("");
                }
                else
                {
                    dateParts[dateIndex] += c;
                }
            }

            // skip white space
            while(parseValue.substr(i, 1) == " ") i++;
        }

        if((this.type & this.TIME) == this.TIME)
        {
            // time?
            var timeIndex = 0;
            for(; i < parseValue.length; i++)
            {
                var c = parseValue.substr(i, 1);

                if(c == " ")
                {
                    break;
                }
                else if(c == ":" || c == "A" || c == "P")
                {
                    timeIndex++;
                    timeParts.push("");

                    if(c == "A" || c == "P")
                    {
                        timeParts[timeIndex] = c;
                    }
                }
                else
                {
                    timeParts[timeIndex] += c;
                }
            }
        }

        switch (dateParts.length)
        {
            case 3: // 1/1/2008
                this.displayDate = new Date(dateParts[2], dateParts[0] - 1, dateParts[1]);
                break;
            case 2: // 1/1
                this.displayDate = new Date((new Date()).getFullYear(), dateParts[0] - 1, dateParts[1]);
                break;
            default:
                this.displayDate = new Date();
                break;
        }

        switch (timeParts.length)
        {
            case 3: // 4:21AM
                if(timeParts[2] == "AM")
                {
                    if(timeParts[0] >= 12)
                    {
                        timeParts[0] -= 12;
                    }

                    this.displayTime = new Date((new Date()).getFullYear(), (new Date()).getMonth(), (new Date()).getDate(), timeParts[0], timeParts[1], 0, 0);
                }
                else
                {
                    this.displayTime = new Date((new Date()).getFullYear(), (new Date()).getMonth(), (new Date()).getDate(), parseInt(timeParts[0]) + 12, timeParts[1], 0, 0);
                }
                break;

            case 2: // 16:21
                this.displayTime = new Date((new Date()).getFullYear(), (new Date()).getMonth(), (new Date()).getDate(), timeParts[0], timeParts[1], 0, 0);
                break;

            default:
                this.displayTime = new Date();
                break;
        }
    }
    catch (e)
    {
        this.displayDate = new Date();
        this.displayTime = new Date();
    }

    if(isNaN(this.displayDate))
    {
        this.displayDate = new Date();
    }

    if(isNaN(this.displayTime))
    {
        this.displayTime = new Date();
    }

    this.selectedValue = this.displayDate;
    this.selectedTime = this.displayTime;

    topOffset += this.target.offsetHeight;

    var size = 0;
    if((this.type & this.TIME) == this.TIME)
    {
        size += 50;

        if((this.type & this.DATE) != this.DATE)
        {
            // a few pixel paddings for ie6
            size += 4;
        }
    }
    if((this.type & this.DATE) == this.DATE)
    {
        size += 200;
    }

    // create the calendar and show the selected date
    var calendar = document.createElement("DIV");
    this.applyStyles(calendar, this.style.calendar.container);
    calendar.style.height = size + "px";
    calendar.style.left = function (e) { return (e)?(((e.offsetLeft)?e.offsetLeft:0) + arguments.callee(e.offsetParent)):0; }(this.target) + "px";
    calendar.style.top = (function (e) { return (e)?(((e.offsetTop)?e.offsetTop:0) + arguments.callee(e.offsetParent)):0; }(this.target) + topOffset) + "px";

    this.activeCalendar = {
        main: calendar,
        calendar: null,
        time: null,
        activeSelect: null
    };

    if((this.type & this.TIME) == this.TIME)
    {
        // add the time box too!
        var timeDisplay = document.createElement("DIV");
        this.applyStyles(timeDisplay, this.style.time.preview);

        if(this.type == this.TIME)
        {
            timeDisplay.style.cursor = "pointer";
            timeDisplay.onclick = function () {
                self.unactivate();
            };
        }
        else
        {
            timeDisplay.style.cursor = "default";
        }

        calendar.appendChild(timeDisplay, true);

        function populate(parent, min, max)
        {
            for(var i = min; i <= max; i++)
            {
                var opt = document.createElement("OPTION");
                opt.innerHTML = i;
                opt.value = i;
                parent.appendChild(opt, true);
            }
        }

        // hour selector
        var hours = document.createElement("SELECT");
        this.applyStyles(hours, this.style.time.options);
        populate(hours, 1, 12);
        hours.onchange = function () {
            self.updateTime();
            self.updateTarget();
        };

        calendar.appendChild(hours, true);

        var sep = document.createElement("SPAN");
        this.applyStyles(sep, this.style.time.seperator);
        sep.innerHTML = ":";
        calendar.appendChild(sep, true);

        var minutesTens = document.createElement("SELECT");
        this.applyStyles(minutesTens, this.style.time.options);
        populate(minutesTens, 0, 5);
        minutesTens.onchange = function () {
            self.updateTime();
            self.updateTarget();
        };

        calendar.appendChild(minutesTens, true);

        var minutes = document.createElement("SELECT");
        this.applyStyles(minutes, this.style.time.options);
        populate(minutes, 0, 9);
        minutes.onchange = function () {
            self.updateTime();
            self.updateTarget();
        };

        calendar.appendChild(minutes, true);

        var ampm = document.createElement("SELECT");
        ampm.onchange = function () {
            self.updateTime();
            self.updateTarget();
        };

        this.applyStyles(ampm, this.style.time.options);
        ampm.appendChild(function (value) { var e = document.createElement("OPTION"); e.innerHTML = e.value = value; return e; }("AM"), true);
        ampm.appendChild(function (value) { var e = document.createElement("OPTION"); e.innerHTML = e.value = value; return e; }("PM"), true);

        calendar.appendChild(ampm, true);

        this.activeCalendar.time = {
            display: timeDisplay,
            hours: hours,
            minutes1: minutesTens,
            minutes2: minutes,
            ampm: ampm
        };

        this.showTime(this.displayTime.getHours(), this.displayTime.getMinutes());
    }

    if((this.type & this.DATE) == this.DATE)
    {
        var styles = this.style.calendar;

        // create the calendar and show the selected date
        var options = document.createElement("DIV");
        this.applyStyles(options, styles.options);

        if((this.type & this.TIME) == this.TIME)
        {
            options.style.borderTop = "1px solid #bbb";
            options.style.marginTop = "4px";
        }

        var prevMonth = document.createElement("SPAN");
        this.applyStyles(prevMonth, styles.arrows);
        prevMonth.innerHTML = "&lt;";
        prevMonth.onclick = function () {
            self.previousMonth();
        }
        options.appendChild(prevMonth, true);

        var currentMonth = document.createElement("DIV");
        this.applyStyles(currentMonth, styles.monthyear);
        currentMonth.innerHTML = "";
        currentMonth.style.width = "75px";
        currentMonth.onclick = function () {
            self.selectMonth();
        };
        options.appendChild(currentMonth, true);

        var nextMonth = document.createElement("SPAN");
        nextMonth.innerHTML = "&gt;";
        this.applyStyles(nextMonth, styles.arrows);
        nextMonth.onclick = function () {
            self.nextMonth();
        }
        options.appendChild(nextMonth, true);

        var prevYear = document.createElement("SPAN");
        this.applyStyles(prevYear, styles.arrows);
        prevYear.innerHTML = "&lt;";
        prevYear.style.marginLeft = "20px";
        prevYear.onclick = function () { self.previousYear(); };
        options.appendChild(prevYear, true);

        var currentYear = document.createElement("DIV");
        currentYear.innerHTML = "";
        this.applyStyles(currentYear, styles.monthyear);
        currentYear.style.width = "35px";
        currentYear.onclick = function () {
            // give them a selection of years!
            self.selectYear();
        };
        options.appendChild(currentYear, true);

        var nextYear = document.createElement("SPAN");
        this.applyStyles(nextYear, styles.arrows);
        nextYear.innerHTML = "&gt;";
        nextYear.onclick = function () { self.nextYear(); };
        options.appendChild(nextYear, true);

        calendar.appendChild(options, true);

        // create the numbers
        var table = document.createElement("TABLE");
        table.cellPadding = 0;
        table.cellSpacing = 0;
        this.applyStyles(table, styles.calendar);

        var tbody = document.createElement("TBODY");

        var calendarData = [];

        for(var week = 0; week < 6; week++)
        {
            calendarData[week] = [];

            var row = document.createElement("TR");
            for(var day = 0; day < 7; day++)
            {
                var cell = document.createElement("TD");
                this.applyStyles(cell, styles.day);

                row.appendChild(cell, true);
                calendarData[week][day] = cell;
            }

            tbody.appendChild(row, true);
        }

        table.appendChild(tbody, true);
        calendar.appendChild(table, true);

        document.body.appendChild(calendar, true);

        this.activeCalendar.calendar = {
            main: calendar,
            month: currentMonth,
            year: currentYear,
            days: calendarData
        };

        this.showMonth(this.displayDate.getMonth() + 1, this.displayDate.getFullYear());
    }

    document.body.appendChild(calendar, true);

    setTimeout( function () {
        var self2 = self;
        // the final click handler
        if('addEventListener' in document.body)
        {
            self.mozHandler = function (event) { self2.handleBodyClick(event); };
            document.body.addEventListener('click', self.mozHandler, false);
        }
        else
        {
            self.ieHandler = function () { self2.handleBodyClick(window.event); };
            document.body.attachEvent('onclick', self.ieHandler);
        }
    }, 100);
};

Calendar.prototype.handleBodyClick = function (event) {
    if(this.activeCalendar == null)
    {
        // we shouldn't be here anyways
        return;
    }

    var node = null;

    if('target' in event)
    {
        node = event.target;
    }
    else
    {
        node = event.srcElement;
    }

    while(node)
    {
        if(node == this.activeCalendar.main || node == this.target || node == this.activeCalendar.activeSelect)
        {
            if(this.activeCalendar.activeSelect != null && node != this.activeCalendar.activeSelect)
            {
                document.body.removeChild(this.activeCalendar.activeSelect);
                this.activeCalendar.activeSelect = null;
            }

            return;
        }

        node = node.parentNode;
    }

    this.unactivate();
};

Calendar.prototype.selectMonth = function () {
    var titles = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

    var sel = document.createElement("SELECT");
    sel.style.position = "absolute";

    for(var i = 0; i < titles.length; i++)
    {
        var opt = document.createElement("OPTION");
        opt.innerHTML = titles[i];
        opt.value = i;
        sel.appendChild(opt, true);
    }

    sel.value = this.displayDate.getMonth();

    var month = this.activeCalendar.calendar.month;

    var selHeight = 22;
    var selWidth = month.offsetWidth + 30;
    sel.style.left = function (e) { return (e)?(((e.offsetLeft)?e.offsetLeft:0) + arguments.callee(e.offsetParent)):0; }(month) + (month.offsetWidth / 2 - selWidth / 2) + "px";
    sel.style.top = function (e) { return (e)?(((e.offsetTop)?e.offsetTop:0) + arguments.callee(e.offsetParent)):0; }(month) + (month.offsetHeight / 2 - selHeight / 2) + "px";
    sel.style.width = selWidth + "px";
    sel.style.height = selHeight + "px";

    var self = this;

    sel.onchange = function () {
        self.displayDate = new Date(self.displayDate.getFullYear(), sel.value, self.displayDate.getDate());
        self.showMonth(self.displayDate.getMonth() + 1, self.displayDate.getFullYear());

        setTimeout(function() {
            document.body.removeChild(sel, true);
            self.activeCalendar.activeSelect = null;
        }, 100);
    };

    setTimeout(function () {
        self.activeCalendar.activeSelect = sel;
        document.body.appendChild(sel, true);
    }, 100);
};

Calendar.prototype.selectYear = function () {
    var sel = document.createElement("SELECT");
    sel.style.position = "absolute";

    for(var year = (new Date()).getFullYear() + 5; year >= this.displayDate.getFullYear() - 100; year--)
    {
        var opt = document.createElement("OPTION");
        opt.innerHTML = year;
        opt.value = year;
        sel.appendChild(opt, true);
    }

    sel.value = this.displayDate.getFullYear();

    var year = this.activeCalendar.calendar.year;

    var selHeight = 22;
    var selWidth = year.offsetWidth + 30;
    sel.style.left = function (e) { return (e)?(((e.offsetLeft)?e.offsetLeft:0) + arguments.callee(e.offsetParent)):0; }(year) + (year.offsetWidth / 2 - selWidth / 2) + "px";
    sel.style.top = function (e) { return (e)?(((e.offsetTop)?e.offsetTop:0) + arguments.callee(e.offsetParent)):0; }(year) + (year.offsetHeight / 2 - selHeight / 2) + "px";
    sel.style.width = selWidth + "px";
    sel.style.height = selHeight + "px";

    var self = this;

    sel.onchange = function () {
        self.displayDate = new Date(sel.value, self.displayDate.getMonth(), self.displayDate.getDate());
        self.showMonth(self.displayDate.getMonth() + 1, self.displayDate.getFullYear());

        setTimeout(function() {
            document.body.removeChild(sel, true);
            self.activeCalendar.activeSelect = null;
        }, 100);
    };

    setTimeout(function () {
        self.activeCalendar.activeSelect = sel;
        document.body.appendChild(sel, true);
    }, 100);
};

Calendar.prototype.applyStyles = function (element, styles) {
    /*
    styles is expected to be an object, with the key being a CSS style name
    */
    for(var key in styles)
    {
        // convert the key name into a javscript stype
        var styleName = "";
        var shouldUpper = false;
        for(var i = 0; i < key.length; i++)
        {
            var c = key.substr(i, 1);

            if(c == "-")
            {
                shouldUpper = true;
            }
            else
            {
                if(shouldUpper)
                {
                    shouldUpper = false;
                    c = c.toUpperCase();
                }

                styleName += c;
            }

        }

        element.style[styleName] = styles[key];
    }
};

Calendar.prototype.unactivate = function () {
    if(this.activeCalendar == null)
    {
        return;
    }

    if(this.activeCalendar.activeSelect != null)
    {
        document.body.removeChild(this.activeCalendar.activeSelect, true);
        this.activeCalendar.activeSelect = null;
    }

    document.body.removeChild(this.activeCalendar.main);
    this.activeCalendar = null;

    // clean up after ourselves
    if('removeEventListener' in document.body)
    {
        document.body.removeEventListener('click', this.mozHandler, false);
    }
    else
    {
        document.body.detachEvent('onclick', this.ieHandler);
    }
};

Calendar.prototype.showMonth = function (month, year) {
    var titles = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

    // populate the right data
    if(this.activeCalendar == null)
    {
        return;
    }

    var calendar = this.activeCalendar.calendar;

    calendar.month.innerHTML = titles[month - 1];
    calendar.year.innerHTML = year;

    // when is the first day?
    var firstDay = (new Date(year, month - 1, 1)).getDay();
    var lastDay = (new Date(year, month, -1)).getDate();

    var self = this;

    for(var week = 0; week < 6; week++)
    {
        for(var day = 0; day < 7; day++)
        {
            var thisDay = week * 7 + day - firstDay;

            if(thisDay < 0 || thisDay > lastDay)
            {
                var when = new Date(year, month - 1, thisDay + 1);

                this.applyStyles(calendar.days[week][day], this.style.calendar.invalid);
                calendar.days[week][day].innerHTML = when.getDate();
                calendar.days[week][day].onclick = function (today) {
                    return function () {
                        self.displayDate = today;
                        self.showMonth(self.displayDate.getMonth() + 1, self.displayDate.getFullYear());
                    };
                }(new Date(when.getFullYear(), when.getMonth(), 1));
                calendar.days[week][day].onmouseover = null;
                calendar.days[week][day].onmouseout = null;
            }
            else
            {
                this.applyStyles(calendar.days[week][day], this.style.calendar.valid);
                calendar.days[week][day].innerHTML = thisDay + 1;
                calendar.days[week][day].onclick = function (today) {
                    return function () {
                        self.selectDate(today);
                    };
                }(new Date(year, month - 1, thisDay + 1));
                calendar.days[week][day].onmouseover = function (elm) {
                    return function () {
                        elm.style.backgroundColor = "#eee";
                    };
                }(calendar.days[week][day]);

                calendar.days[week][day].onmouseout = function (elm) {
                    return function () {
                        elm.style.backgroundColor = "#fff";
                    };
                }(calendar.days[week][day]);

            }
        }
    }
};

Calendar.prototype.previousMonth = function () {
    this.displayDate = new Date(this.displayDate.getFullYear(), this.displayDate.getMonth() - 1, 1);
    this.showMonth(this.displayDate.getMonth() + 1, this.displayDate.getFullYear());
};

Calendar.prototype.nextMonth = function () {
    this.displayDate = new Date(this.displayDate.getFullYear(), this.displayDate.getMonth() + 1, 1);
    this.showMonth(this.displayDate.getMonth() + 1, this.displayDate.getFullYear());
};

Calendar.prototype.previousYear = function () {
    this.displayDate = new Date(this.displayDate.getFullYear() - 1, this.displayDate.getMonth(), 1);
    this.showMonth(this.displayDate.getMonth() + 1, this.displayDate.getFullYear());
};

Calendar.prototype.nextYear = function () {
    this.displayDate = new Date(this.displayDate.getFullYear() + 1, this.displayDate.getMonth(), 1);
    this.showMonth(this.displayDate.getMonth() + 1, this.displayDate.getFullYear());
};

Calendar.prototype.onClick = function () {
    if(this.activeCalendar == null)
    {
        this.activate();
    }
};

Calendar.prototype.selectDate = function (date) {
    this.selectedValue = date;

    this.updateTarget();
    this.unactivate();
};

Calendar.prototype.updateTarget = function () {
    var str = "";

    if((this.type & this.DATE) == this.DATE)
    {
        str += (this.selectedValue.getMonth() + 1) + "/" + this.selectedValue.getDate() + "/" + this.selectedValue.getFullYear();
    }

    if((this.type & this.TIME) == this.TIME)
    {
        if(str != "")
        {
            str += " ";
        }

        var hours = this.selectedTime.getHours();
        var minutes = this.selectedTime.getMinutes();
        var suffix = "AM";

        if(hours > 12)
        {
            hours -= 12;
            suffix = "PM";
        }
        else if (hours == 12)
        {
            suffix = "PM";
        }

        if(minutes < 10)
        {
            minutes = "0" + minutes;
        }

        str += ((hours == 0)?("12"):hours) + ":" + minutes + suffix;
    }

    this.target.value = str;
};

Calendar.prototype.showTime = function (hours, minutes) {
    // select the right values from the select boxes
    if(this.activeCalendar == null)
    {
        return;
    }

    var isPM = false;

    if(hours >= 12)
    {
        hours -= 12;
        isPM = true;
    }

    var time = this.activeCalendar.time;

    time.hours.value = (hours == 0)?12:hours;
    time.minutes1.value = Math.floor(minutes / 10);
    time.minutes2.value = minutes % 10;
    time.ampm.selectedIndex = isPM?1:0;

    this.updateTime();
};

Calendar.prototype.updateTime = function () {
    var time = this.activeCalendar.time;

    // update the time preview
    var str = time.hours.value + ":" + time.minutes1.value + time.minutes2.value + time.ampm.value;
    time.display.innerHTML = str;

    var hours = parseInt(time.hours.value);
    var minutes = parseInt(time.minutes1.value) * 10 + parseInt(time.minutes2.value);
    var isPm = time.ampm.value == "PM";

    if(isPm && hours != 12)
    {
        hours += 12;
    }
    else if(!isPm && hours == 12)
    {
        hours -= 12;
    }

    this.selectedTime = new Date((new Date()).getFullYear(), (new Date()).getMonth(), (new Date()).getDate(), hours, minutes, 0, 0);
};

function setupCalendars()
{
    var inputs = document.getElementsByTagName("INPUT");

    for(var i = 0; i < inputs.length; i++)
    {
        var type = inputs[i].getAttribute("_type");

        if(type)
        {
            if(type == "calendar" || type == "date")
            {
                new Calendar(inputs[i], "date");
            }
            else if (type == "time")
            {
                new Calendar(inputs[i], "time");
            }
            else if (type == "datetime")
            {
                new Calendar(inputs[i], "datetime");
            }
        }
    }
}

if("addEventListener" in window)
{
    window.addEventListener("load", function () { setupCalendars(); }, false);
}
else
{
    window.attachEvent("onload", setupCalendars);
}
