package SimpleCalendar;

use strict;
use warnings;
use Time::Local;

# --------------------------------------------------------------------------
# obj new(int month, int year, string weekStart)
# Description: This will create a new SimpleCalendar object with the
# sepcified parameters. Note that month must be from 1 to 12, year must
# be a four digit number with no leading zeros and weekStart is the
# day which is the start of the week. ( sunday .. saturday )
# --------------------------------------------------------------------------
sub new {
	my $proto    = shift;
	my $month    = shift;
	my $year     = shift;
	my $weekStart= shift;
	my $class	 = ref($proto) || $proto;
	my %dayIDs	 = ();
	my %self	 = ();
	
	# Map names to IDs in the sunday-based week
	%dayIDs = (
		'sunday'	=> 0,
		'monday'	=> 1,
		'tuesday'	=> 2,
		'wednesday'	=> 3,
		'thursday'	=> 4,
		'friday'	=> 5,
		'saturday'	=> 6,
	);
	
	# Remove all leading zeros from the year
	$year =~ s/^0+//;
	
	# Lower-case weekStart for easier usage
	$weekStart = lc($weekStart);
	
	# Make sure month, year, and weekStart are valid
	return if 		( ($month < 1) || ($month > 12) );
	return unless 	( $year =~ /\d{4}/ );
	return unless   ( grep { $_ eq $weekStart } keys %dayIDs );
	
	# Everything's good ... proceed
	$self{'id'}		= $dayIDs{$weekStart};
	$self{'month'}	= $month - 1;
	$self{'year'}	= $year;
	
	# Object on the move!
	return bless \%self, $class;
}

# --------------------------------------------------------------------------
# int _GenerateTimeStamp()
# Description: This method will generate UNIX timestamp for the month
# and year provided. Note that the timestamp is always generated
# for the 1st day in the month.
# --------------------------------------------------------------------------
sub _GenerateTimeStamp {
	my $self = shift;
	
	return timelocal(1,0,0,1,$self->{'month'},$self->{'year'});
}

# --------------------------------------------------------------------------
# int _GetYearType()
# Description: This method will return 1 if the year provided is
# a leap year, and 0 otherwise.
# --------------------------------------------------------------------------
sub _GetYearType {
	my $self = shift;
	
	if ( $self->{'year'} % 4 == 0) {
		unless(($self->{'year'} % 100 == 0) && ($self->{'year'} % 400 == 0)) {
			return 1;
		}
	}
	
	return undef;
}

# --------------------------------------------------------------------------
# int _GetDayOfWeek(timestamp)
# Description: This method will retrive the day of week from the provided
# timestamp. It will also convert the resultant number into the week
# system defined in $self->{'id'}.
# --------------------------------------------------------------------------
sub _GetDayOfWeek {
	my $self = shift;
	my $timestamp = shift;
	my $dayOfWeek = undef;
	
	# Get day of week in the sunday-based system
	($dayOfWeek) = (localtime($timestamp))[6];
	
	# Convert that into our system
	return ($dayOfWeek + ( 7 - $self->{'id'})) % 7;
}

# --------------------------------------------------------------------------
# arrayref BuildCalendar()
# Description: This method will return a data structure for the calendar
# of the month and year defined in the object.
# --------------------------------------------------------------------------
sub BuildCalendar {
	my $self = shift;
	my @calendar  = ([],[],[],[],[]);
	my $firstDay = undef;
	my @daysOfMonth = qw(31 28 31 30 31 30 31 31 30 31 30 31);
	my $row = 0;
	my $dayOfWeek = undef;
	my $rem = 0;
	
	# If it is a leap year, add one to feb
	$daysOfMonth[1] = 29 if ( $self->_GetYearType() );
	
	# Get the first day of the week
	$firstDay = $self->_GetDayOfWeek($self->_GenerateTimeStamp());
	
	# Get day of week pointer
	$dayOfWeek = $firstDay;
	
	# Start loading data into the structure
	for ( my $i = 1; $i <= $daysOfMonth[$self->{'month'}]; $i++ ) {
		# move to next row
		if ( $dayOfWeek == 7 ) {
			$row += 1;
			$dayOfWeek = 0;
		}
		
		# add data
		$calendar[$row][$dayOfWeek] = "$i";
		
		# increase day of week
		$dayOfWeek++;
	}
	# how many positions to fill
	$rem = 6 - $dayOfWeek + 1;
	
	if ( $rem > 0 ) {
		for ( my $i = 0; $i < $rem; $i++ ) {
			$calendar[$row][$dayOfWeek] = undef;
			$dayOfWeek++;
		}
	}
	
	# if the last week wasn't filled, then remove it
	if ( @{$calendar[4]} == 0 ) {
		pop(@calendar);
	}
	
	# done
	return \@calendar;
}

# --------------------------------------------------------------------------
# int AccessMonth()
# --------------------------------------------------------------------------
sub AccessMonth {
	my $self = shift;
	return $self->{'month'};
}

# --------------------------------------------------------------------------
# int AccessYear()
# --------------------------------------------------------------------------
sub AccessYear {
	my $self = shift;
	return $self->{'year'};
}

# --------------------------------------------------------------------------
# int AccessStartOfWeek()
# --------------------------------------------------------------------------
sub AccessStartOfWeek {
	my $self = shift;
	return $self->{'id'};
}

=head1 NAME

SimpleCalendar - A Simple Interface For Monthly Calendars

=head1 SYNOPSIS

	use SimpleCalendar;
	use Data::Dumper;
	
	my $obj = new SimpleCalendar(6, 2004, 'monday');
	my $cal = $obj->BuildCalendar();

=head1 DESCRIPTION

This module provides a fairly simple interface for creating monthly
calendars. It makes use of C<Time::Local> module which is usually
bundled with Perl by default.

=head2 Methods

=over 12

=item C<new>

Returns a new SimpleCalendar object. It takes three arguments: The month 
( which ranges from 1 to 12 ), the year ( which must be a 4 digit number with
no leading zeros ) and the start of the week ( from sunday to saturday ).

=item C<BuildCalendar>

Returns an array reference which contains the calendar. Each element of this
array represents a week and each element of a week is the day. Days that
are before the first day of the month are assigned C<undef> values as well as days
that are after the last day of the month. For example, the structure
that will be returned from the previous example is:

	$cal = [[undef,'1','2','3','4','5','6'],['7','8','9','10','11','12','13'],['14'
	,'15','16','17','18','19','20'],['21','22','23','24','25','26','27'],['28','29',
	'30',undef,undef,undef,undef]];

=back

=head1 INSTALLATION

Just download the file to whatever location you like and C<use> it in
your scripts.

=head1 BUGS

Not known.

=head1 AUTHOR

Mohammad Mahmoud Khajah - <mmkhajah@syr.edu>

=head1 SEE ALSO

L<Time::Local>

=cut

1;
