//----------------------------------------------------------------------------------------------------
//
//	Calendar
//
//	Inherits From:		Control
//
//	Declared In:		Calendar.h
//
//	Disclaimer
//
//		You may freely copy, distribute and reuse this software and its
//		associated documentation. I disclaim any warranty of any kind, 
//		expressed or implied, as to its fitness for any particular use.
//
//----------------------------------------------------------------------------------------------------
#import "Calendar.h"


static const char*  DAYS [7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", 	"Friday", "Saturday" };

static const char*  MONTHS [12] = { "January", "February", "March", "April", "May", "June", "July", 	"August", "September", "October", "November", "December" };

static int	LAST_DAY_OF_MONTH [13] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	
char	 dateString [25];



@implementation Calendar

//----------------------------------------------------------------------------------------------------
//	Private Methods
//----------------------------------------------------------------------------------------------------
- _initMonthTextField
{
	//  Create the monthTextField instance and add it as a subview.
	
	Font*	font = [Font newFont:"Helvetica-Bold" size:12 style:0 matrix:NX_FLIPPEDMATRIX];
	NXRect	frameRect = {5.0, 114.0, 94.0, 15.0};

	monthTextField = [[TextField allocFromZone:[self zone]] initFrame: &frameRect];
	[monthTextField setEditable: NO];
	[monthTextField setBezeled: NO];
	[monthTextField setBackgroundColor: NX_COLORLTGRAY];
	[monthTextField setFont: font];
	[self addSubview: monthTextField];
	return self;
}


- _initYearTextField
{
	//  Create the yearTextField instance and add it as a subview.
	
	Font*	font = [Font newFont:"Helvetica-Bold" size:12 style:0 matrix:NX_FLIPPEDMATRIX];
	NXRect	frameRect = {133.0, 114.0, 33.0, 15.0};

	yearTextField = [[TextField allocFromZone:[self zone]] initFrame: &frameRect];
	[yearTextField setEditable: NO];
	[yearTextField setBezeled: NO];
	[yearTextField setAlignment: NX_RIGHTALIGNED];
	[yearTextField setBackgroundColor: NX_COLORLTGRAY];
	[yearTextField setFont: font];
	[self addSubview: yearTextField];
	return self;
}


- _initWeekdayTextField
{
	//  Create the weekdayTextField instance and add it as a subview.
	
	Font*	font = [Font newFont:"Helvetica-Bold" size:10 style:0 matrix:NX_FLIPPEDMATRIX];
	NXRect	frameRect = {7.0, 96.0, 157.0, 13.0};

	weekdayTextField = [[TextField allocFromZone:[self zone]] initFrame: &frameRect];
	[weekdayTextField setEditable: NO];
	[weekdayTextField setBezeled: NO];
	[weekdayTextField setAlignment: NX_CENTERED];
	[weekdayTextField setBackgroundColor: NX_COLORDKGRAY];
	[weekdayTextField setTextColor: NX_COLORWHITE];
	[weekdayTextField setFont: font];
	[weekdayTextField setStringValue: " S    M     T     W     T     F     S"];
	[self addSubview: weekdayTextField];
	return self;
}


- _initDayMatrix
{
	//  Create the dayMatrix ButtonCell Matrix and add it as a subview.
	
	id		buttonCell;
	int		cellTag = 0;
	int		row;
	int		column;
	Font*	font = [Font newFont:"Helvetica" size:10 style:0 matrix:NX_FLIPPEDMATRIX];
	NXRect 	matrixRect = {7.0, 3.0, 161.0, 90.0};
	NXRect  	cellRect = {0.0, 0.0, 23.0, 15.0};
	NXSize  	cellSize = {0.0, 0.0};

	buttonCell = [[ButtonCell allocFromZone:[self zone]] initTextCell: ""];
	[buttonCell setHighlightsBy: NX_CHANGEGRAY];
	[buttonCell setType: NX_ONOFF];
	[buttonCell setEditable: NO];
	[buttonCell setBordered: NO];
	[buttonCell setFont: font];

	dayMatrix = [[Matrix  allocFromZone:[self zone]] initFrame:&matrixRect 		mode:NX_RADIOMODE prototype:buttonCell numRows:6 numCols:7];
		
	for (row = 0; row < 6; row++)
		for (column = 0; column < 7; column++)
			[[dayMatrix cellAt:row :column] setTag: cellTag++];

	[dayMatrix setIntercell:&cellSize];
	[dayMatrix setCellSize:&cellRect.size];
	[dayMatrix sizeToCells];
	[dayMatrix setAutodisplay:YES];
	[[dayMatrix setTarget: self] setAction: @selector (_daySelected:)];
	[dayMatrix setEmptySelectionEnabled: YES];
	[self addSubview: dayMatrix];
	return self;
}


- _initWithSystemDate
{
    	struct  timeval greenwich;
	struct  tm*	today;
		
    	gettimeofday (&greenwich, NULL);
    	today = (localtime (&(greenwich.tv_sec)));
	day = today->tm_mday;
	month = 1 + today->tm_mon;
	year = 1900 + today->tm_year;
	return self;
}


- (int) _lastDayOfMonth
{
	if (month == 0) [self _initWithSystemDate];
	if (month == 2) LAST_DAY_OF_MONTH[1] = (year % 4 ? 28 : 29);
	return LAST_DAY_OF_MONTH [month - 1];
}


- (int) _firstWeekdayOfMonth
{
	int		theMonth = month;
	int		theYear = year;
	int		weekDay;
	int 		offset;
	long 	julian;

	if (theMonth <= 2) { theYear--; theMonth += 12; }

	offset = 0;
	if ((theYear * 10000.0) + (theMonth * 100.0) + 1 >= 15821015.0)
		offset = (2 - (theYear / 100)) + ((theYear / 100) / 4);

	julian = (365.25 * theYear);
	julian += (long) (30.6001 * (theMonth +1));
	julian += (long) 1;
	julian += 1720994L;
	julian += (long) offset;

	weekDay = (int) ((julian + 2) % 7);
	return (weekDay < 0 || weekDay > 6 ? 0 : weekDay);
}


- _highlightCurrentDay
{
	//  Called by _updateCalendar and day: methods to select current day.
	
	int	firstWeekday;
	id	selectedCell;

	[dayMatrix selectCellAt: -1 :-1];
	if (day == 0) return self;
	
	firstWeekday = [self _firstWeekdayOfMonth];
	selectedCell = [dayMatrix findCellWithTag: day + firstWeekday - 1];

	if ([selectedCell title]) 
		if (strcmp ([selectedCell title], "")) 
			[dayMatrix selectCellWithTag: day + firstWeekday - 1];
		
	return self;
}


- _sendActionToTarget
{
	if ([self target] && [self isEnabled])
		if ([[self target] respondsTo: [self action]])
			[[self target] perform:[self action] with:self];
			
	return self;
}


- _daySelected: sender
{
	//  Private action method for dayMatrix.  Send user's action to target.
	
	day = ([sender selectedCell] ? atoi ([[sender selectedCell] title]) : 0);
	[self _sendActionToTarget];
	return self;
}


- _updateCalendar
{
	//  Handles display update of subviews.
	
	int		date;
	int		cellTag;
	int		firstWeekday = [self _firstWeekdayOfMonth];
	int		lastDayOfMonth = [self _lastDayOfMonth];
	char		dateString[3];
	
	[[dayMatrix window] disableDisplay];
	
	for (cellTag = 0; cellTag < firstWeekday; cellTag++)
		[[[dayMatrix findCellWithTag: cellTag] setTitle: ""] setEnabled: NO];
		
	for (cellTag = lastDayOfMonth; cellTag < [dayMatrix cellCount]; cellTag++)
		[[[dayMatrix findCellWithTag: cellTag] setTitle: ""] setEnabled: NO];
		
	for (cellTag = firstWeekday, date = 1; date <= lastDayOfMonth; cellTag++, date++)
		{
		sprintf (dateString, "%d", date);
		[[[dayMatrix findCellWithTag: cellTag] setTitle: dateString] setEnabled: isEnabled];
		}
		
	[self _highlightCurrentDay];
	[monthTextField setStringValue: MONTHS [month - 1]];
	[yearTextField setIntValue: year];

	[[dayMatrix window] reenableDisplay];
	[self display];
	return self;
}
	
	
- _nextText: sender
{
	//  Called by 'takeFrom' methods to assist UI flow.
	
	if ([sender isKindOf: [TextField class]]) 
		if ([[sender nextText] respondsTo: @selector(selectText:)])
			[[sender nextText] selectText:nil];

	return self;
}


//----------------------------------------------------------------------------------------------------
//	Initializing and Freeing
//----------------------------------------------------------------------------------------------------
- initFrame: (const NXRect*) frameRect
{
	//  Init and display current date.  Create an ActionCell instance to
	//  hold our target/action values. Enable day selection.
	
	[super initFrame: frameRect];
	[self setCell: [[ActionCell allocFromZone:[self zone]] init]];
	[self setEnabled: YES];
	[self _initMonthTextField];
	[self _initYearTextField];
	[self _initWeekdayTextField];
	[self _initDayMatrix];
	[self _initWithSystemDate];
	[self _updateCalendar];
	return self;
}


- free
{
	if (dayMatrix) [dayMatrix free];
	if (monthTextField) [monthTextField free];
	if (yearTextField) [yearTextField free];
	if (weekdayTextField) [weekdayTextField free];
	return [super free];
}


//----------------------------------------------------------------------------------------------------
//	Accessors
//----------------------------------------------------------------------------------------------------
- month: (int) aMonth
{
	month = (aMonth < 1 || aMonth > 12 ? 1 : aMonth);
	[self _updateCalendar];
	return self;
}


- day: (int) aDay
{
	day = (aDay < 1 || aDay > [self _lastDayOfMonth] ? 0 : aDay);
	[self _highlightCurrentDay];
	return self;
}


- year: (int) aYear
{
	year = (aYear < 1 || aYear > 9999 ? 1 : aYear);
	[self _updateCalendar];
	return self;
}


- (const char*) monthStringValue
{
	return [monthTextField stringValue];
}


- (const char*) dayStringValue
{
	return ([[dayMatrix selectedCell] title]);
}


- (const char*) yearStringValue
{
	return [yearTextField stringValue];
}


- (const char*) dayOfWeekStringValue
{
	return DAYS [([[dayMatrix selectedCell] tag] % 7)];
}


- (const char*) dateStringValue
{
	if (day) 
		sprintf (dateString, "%s %d, %.4d", [self monthStringValue], day, [self yearIntValue]);
	else
		sprintf (dateString, "%s %.4d", [self monthStringValue], [self yearIntValue]);

	return dateString;
}


- (int) monthIntValue
{
	return month;
}
	
	
- (int) dayIntValue
{
	return day;
}


- (int) yearIntValue
{
	return year;
}


- (int) dayOfWeekIntValue
{
	return ([[dayMatrix selectedCell] tag] % 7);
}


- (const char*) stringValue
{
	return [self dateStringValue];
}


- (int) intValue
{
	return day;
}


- (float) floatValue
{
	return (float) day;
}


- (double) doubleValue
{
	return (double) day;
}


//----------------------------------------------------------------------------------------------------
//	Enabling Day Selection
//----------------------------------------------------------------------------------------------------
- setEnabled: (BOOL) aFlag
{
	//  Set enabling of day selection to aFlag and redisplay calendar (this will 
	//  force update of dayMatrix buttonCells which are what actually gets
	//  enabled or disabled).  Since the subview dayMatrix handles the mouse 
	//  events we're interested in, we (the superview) will always remain disabled.
	
	isEnabled = aFlag;
	[self _updateCalendar];
	[super setEnabled: NO];
	return self;
}


- (BOOL) isEnabled
{
	return isEnabled;
}


//----------------------------------------------------------------------------------------------------
//	Action Methods
//----------------------------------------------------------------------------------------------------
- incrementMonth: sender
{
	if (++month > 12) month = 1;
	[self _updateCalendar];
	return self;
}


- decrementMonth: sender
{
	if (--month < 1) month = 12;
	[self _updateCalendar];
	return self;
}


- incrementYear: sender
{
	if (++year > 9999) year = 1;
	[self _updateCalendar];
	return self;
}


- decrementYear: sender
{
	if (--year < 1) year = 9999;
	[self _updateCalendar];
	return self;
}


- takeMonthIntValueFrom: sender
{
	if ([sender isKindOf: [Matrix class]]) sender = [sender selectedCell];
	if ([sender respondsTo: @selector (intValue)]) [self month: [sender intValue]];
	[self _nextText: sender];
	return self;
}


- takeDayIntValueFrom: sender
{
	if ([sender isKindOf: [Matrix class]]) sender = [sender selectedCell];
	if ([sender respondsTo: @selector (intValue)]) [self day: [sender intValue]];
	[self _nextText: sender];
	return self;
}


- takeYearIntValueFrom: sender
{
	if ([sender isKindOf: [Matrix class]]) sender = [sender selectedCell];
	if ([sender respondsTo: @selector (intValue)]) [self year: [sender intValue]];
	[self _nextText: sender];
	return self;
}


- showSystemDate: sender
{
	[self _initWithSystemDate];
	[self _updateCalendar];
	return self;
}


//----------------------------------------------------------------------------------------------------
//	Archiving
//----------------------------------------------------------------------------------------------------
- read: (NXTypedStream*) stream
{
	[super read: stream];
	NXReadTypes (stream, "iii@@@c", 
		&month, &day, &year, &monthTextField, &dayMatrix, &yearTextField, &isEnabled);
	return self;
}


- write: (NXTypedStream*) stream
{
	[super write: stream];
	NXWriteTypes (stream, "iii@@@c", 
		&month, &day, &year, &monthTextField, &dayMatrix, &yearTextField, &isEnabled);
	return self;
}


//----------------------------------------------------------------------------------------------------
//	IB Methods
//----------------------------------------------------------------------------------------------------
- (const char*) getInspectorClassName
{
	//  Returns the name of the class responsible for managing custom 
	//  IB Attributes inspection.
		
	return "CalendarInspector";
}


- getMinSize:(NXSize *)minSize maxSize:(NXSize *)maxSize from:(int)where
{
	//  Constrains resize within IB.
	
	minSize->width = maxSize->width = 175.0;
	minSize->height = maxSize->height = 137.0;
	return self;
}


@end