// Calendar.js - simple calendar widget
//
// To create a calendar widget for the current date:
//   MakeCalendarCurrent( element, callback );
// To create a calendar widget for some other date:
//   MakeCalendarDMY( element, day, month, year, callback );
//
// Element is the HTML element where you want the calendar built.
// Day is 1-31, month is 1-12, and year is four digits.  Callback
// is a routine that gets called when the user clicks on a date.
// It gets passed the day, month, and year that was selected.
//
// The Calendar widget exposes the following CSS classes, in case you want
// to change the default colors or something:
//   calendar: the outermost element of the widget
//   calendarCorner: the upper left and upper right corner areas
//   calendarHover: when the mouse moves over an active item
//   calendarToday: special styling for the current day
//
//
// The current version of this code is always available at:
// http://www.acme.com/javascript/
//
//
// Copyright \xa9 2007 by Jef Poskanzer <jef@mail.acme.com>.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// For commentary on this license please see http://www.acme.com/license.html


function MakeCalendarCurrent( element, callback )
    {
    var d = new Date();
    return MakeCalendarDMY( element, d.getDate(), d.getMonth() + 1, d.getFullYear(), callback );
    }


// day: 1-31  month: 1-12  year: 4 digits
function MakeCalendarDMY( element, day, month, year, callback )
    {
    MakeCalendarDMYInternal( element, day, month, year, day, month, year, callback );
    }


function MakeCalendarDMYInternal( element, day, month, year, initialDay, initialMonth, initialYear, callback )
    {
    // Make sure the element starts empty.
    ClearElement( element );

    // Outer table and headers.
    var tableOuter = AppendElement( element, 'table', { border: 0, cellSpacing: 0, cellPadding: 4, className: 'calendar' } );
    var tbodyOuter = AppendElement( tableOuter, 'tbody', { } );
    var trOuter_1 = AppendElement( tbodyOuter, 'tr', { } );
    // Upper left corner.
    var tdOuter_1_1 = AppendElement( trOuter_1, 'td', { className: 'calendarCorner', style: { width: '33.333%' } } );
    var tableUL = AppendElement( tdOuter_1_1, 'table', { border: 0, cellSpacing: 0, cellPadding: 1, width: '100%' } );
    var tbodyUL = AppendElement( tableUL, 'tbody', { } );
    var trUL_1 = AppendElement( tbodyUL, 'tr', { } );
    var tdUL_1_1 = AppendElement( trUL_1, 'td', { style: { textAlign: 'left' } } );
    var prevMonth = month - 1;
    if ( prevMonth == 0 ) prevMonth = 12;
    tdUL_1_1.innerHTML = '&lt; ' + MonthName( prevMonth );
    if ( NotTheFuture( day, prevMonth, year, initialDay, initialMonth, initialYear ) )
	{
	tdUL_1_1.onmouseover = MakeCaller( CalendarHoverOn, tdUL_1_1 );
	tdUL_1_1.onmouseout = MakeCaller( CalendarHoverOff, tdUL_1_1 );
	tdUL_1_1.onclick = MakeCaller( MakeCalendarDMYInternal, element, 0, prevMonth, year, initialDay, initialMonth, initialYear, callback );
	}
    var trUL_2 = AppendElement( tbodyUL, 'tr', { } );
    var tdUL_2_1 = AppendElement( trUL_2, 'td', { style: { textAlign: 'left' } } );
    var prevYear = year - 1;
    tdUL_2_1.innerHTML = '&lt;' + prevYear;
    if ( NotTheFuture( day, month, prevYear, initialDay, initialMonth, initialYear ) )
	{
	tdUL_2_1.onmouseover = MakeCaller( CalendarHoverOn, tdUL_2_1 );
	tdUL_2_1.onmouseout = MakeCaller( CalendarHoverOff, tdUL_2_1 );
	tdUL_2_1.onclick = MakeCaller( MakeCalendarDMYInternal, element, 0, month, prevYear, initialDay, initialMonth, initialYear, callback );
	}
    // Middle header.
    var tdOuter_1_2 = AppendElement( trOuter_1, 'td', { style: { width: '33.333%', textAlign: 'center' } } );
    tdOuter_1_2.innerHTML = MonthName( month )+ '<br />' + year;
    // Upper right corner.
    var tdOuter_1_3 = AppendElement( trOuter_1, 'td', { className: 'calendarCorner', style: { width: '33.333%' } } );
    var tableUR = AppendElement( tdOuter_1_3, 'table', { border: 0, cellSpacing: 0, cellPadding: 1, width: '100%' } );
    var tbodyUR = AppendElement( tableUR, 'tbody', { } );
    var trUR_1 = AppendElement( tbodyUR, 'tr', { } );
    var tdUR_1_1 = AppendElement( trUR_1, 'td', { style: { textAlign: 'right' } } );
    var nextMonth = month + 1;
    if ( nextMonth == 13 ) nextMonth = 1;
    tdUR_1_1.innerHTML = MonthName( nextMonth ) + ' &gt;';
    if ( NotTheFuture( day, nextMonth, year, initialDay, initialMonth, initialYear ) )
	{
	tdUR_1_1.onmouseover = MakeCaller( CalendarHoverOn, tdUR_1_1 );
	tdUR_1_1.onmouseout = MakeCaller( CalendarHoverOff, tdUR_1_1 );
	tdUR_1_1.onclick = MakeCaller( MakeCalendarDMYInternal, element, 0, nextMonth, year, initialDay, initialMonth, initialYear, callback );
	}
    var trUR_2 = AppendElement( tbodyUR, 'tr', { } );
    var tdUR_2_1 = AppendElement( trUR_2, 'td', { style: { textAlign: 'right' } } );
    var nextYear = year + 1;
    tdUR_2_1.innerHTML = nextYear + '&gt;';
    if ( NotTheFuture( day, month, nextYear, initialDay, initialMonth, initialYear ) )
	{
	tdUR_2_1.onmouseover = MakeCaller( CalendarHoverOn, tdUR_2_1 );
	tdUR_2_1.onmouseout = MakeCaller( CalendarHoverOff, tdUR_2_1 );
	tdUR_2_1.onclick = MakeCaller( MakeCalendarDMYInternal, element, 0, month, nextYear, initialDay, initialMonth, initialYear, callback );
	}
    // Inner table for the days.
    var trOuter_2 = AppendElement( tbodyOuter, 'tr', { } );
    var tdOuter_2_1 = AppendElement( trOuter_2, 'td', { colSpan: 3 } );
    var tableLower = AppendElement( tdOuter_2_1, 'table', { border: 0, cellSpacing: 0, cellPadding: 1, style: { textAlign: 'center' } } );
    var tbodyLower = AppendElement( tableLower, 'tbody', { } );
    var trLower_1 = AppendElement( tbodyLower, 'tr', { } );
    // Day of the week header.
    for ( var dow = 0; dow < 7; ++dow )
	{
	var td_dow = AppendElement( trLower_1, 'td', { } );
	td_dow.innerHTML = DowName( dow );
	}

    // And here are the actual days.
    var firstDow = DayOfWeek( 1, month, year );
    var daysInMonth = DaysPerMonth( month, year );
    var tr = null;
    var column;
    var td;
    for ( var aDay = 0; aDay <= daysInMonth; )
	{
	if ( tr == null || column == 7 )
	    {
	    tr = AppendElement( tbodyLower, 'tr', { } );
	    column = 0;
	    }
	if ( aDay == 0 )
	    {
	    if ( column < firstDow )
		{
		td = AppendElement( tr, 'td', { } );
		++column;
		continue;
		}
	    aDay = 1;
	    }
	td = AppendElement( tr, 'td', { className: 'calendarDay' } );
	td.innerHTML = aDay;
	if ( aDay == initialDay && month == initialMonth && year == initialYear )
	    td.className = 'calendarToday';
	if ( NotTheFuture( aDay, month, year, initialDay, initialMonth, initialYear ) )
	    {
	    td.onmouseover = MakeCaller( CalendarHoverOn, td );
	    td.onmouseout = MakeCaller( CalendarHoverOff, td );
	    td.onclick = MakeCaller( CalendarSelect, td, callback, aDay, month, year )
	    }
	td.calendar = element;
	++column;
	++aDay
	}
    }


function NotTheFuture( day, month, year, initialDay, initialMonth, initialYear )
    {
    if ( year < initialYear )
	return true;
    if ( year > initialYear )
	return false;
    if ( month < initialMonth )
	return true;
    if ( month > initialMonth )
	return false;
    if ( day <= initialDay )
	return true;
    return false;
    }


function CalendarHoverOn( element )
    {
    element.oldId = element.id;
    element.id = 'calendarHover';
    }


function CalendarHoverOff( element )
    {
    element.id = element.oldId;
    }


function CalendarSelect( element, callback, day, month, year )
    {
    var prevSelected = element.calendar.selected;
    if ( prevSelected != null )
	prevSelected.className = prevSelected.oldClass;

    element.oldClass = element.className;
    if ( element.className == 'calendarToday' )
	element.className = 'calendarTodaySelected';
    else
	element.className = 'calendarSelected';
    element.calendar.selected = element;

    callback( day, month, year );
    }


// Jan=1 Dec=12
function MonthName( month )
    {
    var monthName = [
      'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
      'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
    return monthName[month-1];
    }


// Sun=0 Sat=6
function DowName( dow )
    {
    var dowName = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
    return dowName[dow];
    }


// day: 1-31  month: 1-12  year: 4 digits
// return: Sun=0 Sat=6
function DayOfWeek( day, month, year )
    {
    var d = new Date();
    d.setDate( day );
    d.setMonth( month - 1 );
    d.setYear( year );
    d.setHours( 12 );
    d.setMinutes( 0 );
    d.setSeconds( 0 );
    return d.getDay();
    }


var daysPerMonth = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
function DaysPerMonth( month, year )
    {
    if ( month == 2 && IsLeapYear( year ) )
	return 29;
    return daysPerMonth[month - 1];
    }


function IsLeapYear( year )
    {
    if ( year % 4 != 0 )
	return false;
    if ( year % 100 != 0 )
	return true;
    if ( year % 400 != 0 )
	return false;
    return true;
    }
