/* -- SimpleCalendar -- requires jQuery
 *
 * Desc: This javascript object creates a clean styleable calendar for choosing a date.
 * Author: Joseph Westwood
 * 
 * Quickstart: 
 * 	var cal = new SimpleCalendar();
 *	$('body').append(cal.getElement());
 *	cal.dayClicked = function(day, el){
 *           alert("Date: "+day+"/"+this.getMonth()+"/"+this.getYear());
 *	};
 * 
 * methods:
 * - dayClicked( int, Element )
 * - daysInMonth( int )
 * - getElement()
 * - getDate()
 * - getMaxDate()
 * - getMinDate()
 * - getMonth()
 * - getYear()
 * - refresh()
 * - refreshBody()
 * - refreshHeader()
 * - setDate( Date )
 * - setMaxDate( Date )
 * - setMinDate( Date )
 * - setMonth( int )
 * - setYear( int )
 */
var SimpleCalendar = function( date, minDate, maxDate ){
	
	if( date == null ) date = new Date();
	this.date = date;
	
	if( minDate == null ) minDate = new Date(date.getFullYear()-1, date.getMonth(), date.getDate());
	this.minDate = minDate;
	
	if( maxDate == null ) maxDate = new Date(date.getFullYear()+1, date.getMonth(), date.getDate());
	this.maxDate = maxDate;
	
	// create containers
	this.wrapper = $( "<div class='scal'></div>");
	this.header = $( "<div class='scal-header'></div>" );
	this.dayheader = $( "<div></div>");
	for( i=0; i<7; i++ )
		this.dayheader.append( $("<span class='scal-day-header'>"+this.dayLetters[i]+"</span>") );
		
	this.body = $( "<div class='scal-body'></div>" );
	
	// generate title + days
	this.refresh();
	
	// nest containers
	this.wrapper.append(this.header);
	this.wrapper.append(this.dayheader);
	this.wrapper.append(this.body);
	
	return this;
}

// the prototype is copied into every new SimpleCalendar object
// it contains data and functions which each independant calendar
// needs to function.
SimpleCalendar.prototype = {
	
	// first letter of each day of the week
	dayLetters: {'0':"M", '1':"T", '2':"W", '3':"T", '4':"F", '5':"S", '6':"S"},
	
	// a table of month lengths ( k = month, v = number of days )
	days: {'0':31, '1':28, '2':31, '3':30, '4':31, '5':30, '6':31, '7':31, '8':30, '9':31, '10':30, '11':31},
	
	// a table of month names ( k = month, v = name )
	months: {'0':"January", '1':"February", '2':"March", '3':"April", '4':"May", '5':"June", '6':"July", '7':"August", '8':"September", '9':"October", '10':"November", '11':"December"},
	
	// returns the value of 'wrapper' field, which is the root DOM Element for this calendar
	getElement: function(){ return this.wrapper; },
	// returns the Date object used by this Calendar
	getDate: function(){ return this.date; },
	// returns the minimum date selectable by this calendar
	getMinDate: function(){ return this.minDate; },
	// returns the maximum date selectable by this calendar
	getMaxDate: function(){ return this.maxDate; },
	// returns the month being displayed by this calendar (0-11)
	getMonth: function(){ return this.date.getMonth(); },
	// returns the year being displayed by this calendar (4 digit representation)
	getYear: function(){ return this.date.getFullYear(); },
	
	// ensures the current date is within the min/max range
	// refreshes if the date required changing
	clampDate: function(){
		if( this.date.getTime() < this.minDate.getTime() )
		{
			this.date = new Date(this.minDate);
			this.refresh();
		}
		else if( this.date.getTime() > this.maxDate.getTime() )
		{
			this.date = new Date(this.maxDate);
			this.refresh();
		}
	},
	
	// changes to the given date, calendar refreshes to the month+year
	setDate: function(d){
		this.date = new Date(d);
		this.clampDate();
		this.refresh();
	},
	// changes the minimum date selectable by this calendar to the specified date
	// months before this date will not be navigatable to and days before this date will not be selectable
	setMinDate: function( date ){
		this.minDate = new Date(date);
		this.clampDate();
	},
	// changes the maximum date selectable by this calendar to the specified date
	// months after this date will not be navigatable to and days after this date will not be selectable
	setMaxDate: function( date ){
		this.maxDate = new Date(date);
		this.clampDate();
	},
	// changes to the given month and refreshes the calendar layout
	setMonth: function(month){
		if( month == null ) month = 0;
		
		this.date.setDate( new Date(this.date.getFullYear(), month % 12, this.date.getDate()) );
		this.clampDate();
		this.refresh();
	},
	// changes to the given year and refreshes the calendar layout
	setYear: function(year){
		if( year == null ) year = (new Date()).getFullYear();
		
		this.date.setDate( new Date(year, this.date.getMonth(), this.date.getDate()) );
		this.clampDate();
		this.refresh();
	},
	
	// returns the number of days in the given month (jan=0)
	daysInMonth: function(month){ 
		month=month%12;
		if( month == 1 && new Date(this.date.getYear(),1,29).getDate() == 29 )
			return 29;
		else
			return this.days[month];	
	},
	
	// refreshes the calendar to layout its current date
	refresh: function(){
		this.refreshHeader();
		this.refreshBody();
	},
	
	// refreshes only the header of the calender
	refreshHeader: function(){
		this.header.empty();
		var span = $("<span class='scal-title'> "+this.months[this.date.getMonth()]+" "+this.date.getFullYear()+" </span>");
		
		var dateY = this.date.getFullYear();
		var dateM = this.date.getMonth();
		var maxY = this.maxDate.getFullYear();
		var maxM = this.maxDate.getMonth();
		var minY = this.minDate.getFullYear();
		var minM = this.minDate.getMonth();
		
		// should display previous month button
		if( dateY > minY || (dateY == minY && dateM > minM) )
		{
			var prev = $("<a href='#' class='scal-prev'>&lt;</a>");
			prev.click( (function(e){ 
				e.preventDefault();
				if( this.date.getMonth() == 0 )
					this.date.setFullYear( this.date.getFullYear()-1 );
					
				this.date.setMonth( (this.date.getMonth() + 11)%12 );
				this.clampDate();
				this.refresh();
				return false;
			}).bind(this) );
			this.header.append(prev);
		}
		else
			this.header.append($("<span class='scal-prev'></span>"));
		
		// insert title
		this.header.append(span);
		
		// should display next month button
		if( dateY < maxY || (dateY == maxY && dateM < maxM) )
		{
			var next = $("<a href='#' class='scal-next'>&gt;</a>");
			next.click( (function(e){ 
				e.preventDefault();
				if( this.date.getMonth() == 11 )
					this.date.setFullYear( this.date.getFullYear()+1 );
					
				this.date.setMonth( (this.date.getMonth() + 1 )%12 );
				this.clampDate();
				this.refresh();
				return false;
			}).bind(this) );
			this.header.append(next);	
		}
		else
			this.header.append($("<span class='scal-next'></span>"));
			
	},
	
	// refreshes only the body of the calendar
	refreshBody: function(){
		this.body.empty();
		this.anchors = {};
		
		// day of week for the first of the month, monday = 0
		var day = (new Date(this.date.getFullYear(), this.date.getMonth(), 1)).getDay();
		day = (day + 6) % 7; 
		
		var month = this.date.getMonth();
		var monthLength = this.daysInMonth(month);
		var prevMonthLength = this.daysInMonth( (month+11)%12 );
		var rows = Math.ceil( (day + monthLength) / 7 );
		
		for( r=0; r<rows; r++ )
		{
			var __calendar = this; // required reference for the anchor click events
			var week = $("<div class='scal-week'></div>");
			for( i=r*7+1; i<r*7+8; i++ )
			{
				if( i <= day )  // leading days
				{
					week.append("<div class='scal-day not-in-month'>"+(prevMonthLength - (day-i))+"</div>");
				}
				else if( i > day + monthLength ) // tailing days
				{
					week.append("<div class='scal-day not-in-month'>"+(i - (day+monthLength))+"</div>");
				}
				else
				{
					// get a date of the day being created to test the range
					var curDate = new Date(this.date.getFullYear(), month, (i-day));
					// if out of min/max bounds
					if( curDate.getTime() < this.minDate.getTime() || curDate.getTime() > this.maxDate.getTime() )
					{
						week.append("<div class='scal-day out-of-range'>"+(i-day)+"</div>");
					}
					else // create an anchor that calls 'dayClicked()'
					{
						var div = $("<div class='scal-day'></div>");
						var anchor = $("<a rel='"+(i-day)+"' href=''>"+(i-day)+"</a>");
						anchor.click( function(e){
							e.preventDefault();
							__calendar.dayClicked( $(this).attr("rel"), $(this) );
						} );
						
						div.append(anchor);
						week.append(div);
					}
				}
			}
			
			this.body.append(week);
		}
	},
	
	// called when a day in the calendar is clicked, the day of the month is passed
	dayClicked: function(day, el){
		// For Override
	}

}

