//
//	MiscClockView.m -- a simple view class for displaying date/time
//		Written by Scott Anguish Copyright (c) 1993 by Scott Anguish.
//				Version 1.2.  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import <sys/time.h>	
#import <appkit/appkit.h>

#import "MiscClockView.h"

@interface MiscClockView(private)
- _setUp;
@end

@interface MiscClockView(Delegate)
- dateChanged:sender;
@end

@implementation MiscClockView


#define Misc_BuildHImage(a, b, c, d, e) 	NXSetRect(theRect, (b), (c), (d), (e)); \
	(a)[0] = [[NXImage alloc] initFromImage:theImage rect:theRect]; \
	theRect->origin.y += 137.0; \
	(a)[1] = [[NXImage alloc] initFromImage:theImage rect:theRect];

#define Misc_BuildImage(a, b, c, d, e) 	NXSetRect(theRect, (b), (c), (d), (e)); \
	a = [[NXImage alloc] initFromImage:theImage rect:theRect];


+ initialize
{
    	[MiscClockView setVersion:4];
	return self;
}


- _setUp
{
	char           buf[MAXPATHLEN + 1] = "";
	NXRect         rectStorage, *theRect = &rectStorage;
	NXSize         oneSize, twoSize;

	if (theImage) return self; // don't want to leak!

	[[NXBundle bundleForClass:[self class]] getPath:buf forResource:"MiscClockStuff" ofType:"tiff"];
	theImage = [[NXImage alloc] initFromFile:buf];


 /* Yes this is ugly, and there is alot of extra junk in here, but to a
  * large degree at this point I didn't know where else to store these
  * co-ordinates where I wouldn't loose them! */

	Misc_BuildHImage (imageA, 65.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (imageP, 74.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[1], 89.0, 0.0, 4.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[2], 93.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[3], 104.0, 0.0, 8.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[4], 112.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[5], 121.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[6], 130.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[7], 140.0, 0.0, 8.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[8], 148.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[9], 157.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (DigitalNumbers[0], 166.0, 0.0, 9.0, 11.0);
	Misc_BuildHImage (imageColon, 175.0, 0.0, 3.0, 11.0);
	Misc_BuildHImage (imageAM, 179.0, 0.0, 12.0, 6.0);
	Misc_BuildHImage (imagePM, 192.0, 0.0, 11.0, 6.0);

	Misc_BuildHImage (DayNames[1], 0, 65, 19, 5);
	Misc_BuildHImage (DayNames[2], 0, 59, 19, 5);
	Misc_BuildHImage (DayNames[3], 0, 53, 19, 5);
	Misc_BuildHImage (DayNames[4], 0, 47, 19, 5);
	Misc_BuildHImage (DayNames[5], 0, 41, 19, 5);
	Misc_BuildHImage (DayNames[6], 0, 35, 19, 5);
	Misc_BuildHImage (DayNames[0], 0, 29, 19, 5);

	Misc_BuildHImage (MonthNames[0], 40, 66, 22, 5);
	Misc_BuildHImage (MonthNames[1], 40, 60, 22, 5);
	Misc_BuildHImage (MonthNames[2], 40, 54, 22, 5);
	Misc_BuildHImage (MonthNames[3], 40, 48, 22, 5);
	Misc_BuildHImage (MonthNames[4], 40, 42, 22, 5);
	Misc_BuildHImage (MonthNames[5], 40, 36, 22, 5);
	Misc_BuildHImage (MonthNames[6], 40, 30, 22, 5);
	Misc_BuildHImage (MonthNames[7], 40, 24, 22, 5);
	Misc_BuildHImage (MonthNames[8], 40, 18, 22, 5);
	Misc_BuildHImage (MonthNames[9], 40, 12, 22, 5);
	Misc_BuildHImage (MonthNames[10], 40, 6, 22, 5);
	Misc_BuildHImage (MonthNames[11], 40, 0, 22, 5);

	Misc_BuildHImage (DateNumbers[0], 172.0, 14.0, 10.0, 17.0);
	Misc_BuildHImage (DateNumbers[1], 65.0, 14.0, 8.0, 17.0);
	Misc_BuildHImage (DateNumbers[2], 76.0, 14.0, 10.0, 17.0);
	Misc_BuildHImage (DateNumbers[3], 88.0, 14.0, 9.0, 17.0);
	Misc_BuildHImage (DateNumbers[4], 100.0, 14.0, 10.0, 17.0);
	Misc_BuildHImage (DateNumbers[5], 112.0, 14.0, 10.0, 17.0);
	Misc_BuildHImage (DateNumbers[6], 124.0, 14.0, 10.0, 17.0);
	Misc_BuildHImage (DateNumbers[7], 136.0, 14.0, 10.0, 17.0);
	Misc_BuildHImage (DateNumbers[8], 148.0, 14.0, 10.0, 17.0);
	Misc_BuildHImage (DateNumbers[9], 160.0, 14.0, 10.0, 17.0);

	Misc_BuildHImage (YearNumbers[0], 12.0, 8.0, 6.0, 5.0);
	Misc_BuildHImage (YearNumbers[1], 0.0, 20.0, 2.0, 5.0);
	Misc_BuildHImage (YearNumbers[2], 2.0, 20.0, 5.0, 5.0);
	Misc_BuildHImage (YearNumbers[3], 7.0, 20.0, 5.0, 5.0);
	Misc_BuildHImage (YearNumbers[4], 12.0, 20.0, 5.0, 5.0);
	Misc_BuildHImage (YearNumbers[5], 0.0, 14.0, 5.0, 5.0);
	Misc_BuildHImage (YearNumbers[6], 5.0, 14.0, 6.0, 5.0);
	Misc_BuildHImage (YearNumbers[7], 11.0, 14.0, 5.0, 5.0);
	Misc_BuildHImage (YearNumbers[8], 0.0, 8.0, 6.0, 5.0);
	Misc_BuildHImage (YearNumbers[9], 6.0, 8.0, 6.0, 5.0);
	Misc_BuildHImage (YearColon, 18.0, 25.0, 1.0, 5.0);

	Misc_BuildImage (clockbase, 64, 72, 64, 64);

	[DigitalNumbers[1][0] getSize:&oneSize];
	[DigitalNumbers[2][0] getSize:&twoSize];
	colonPosition.x = oneSize.width + twoSize.width + 7.0;

	timedEntry = 0;
	if (keepingTime)
	{
		[self startClock:self];
	}

	return self;
}


- initFrame:(const NXRect *)frameRect
{
		
	[super initFrame:frameRect];
	hidden=NO;
	keepingTime=NO;
	[self _setUp];
	return self;

}

- awake
{
	id ret = [super awake];
	[self _setUp];
	return ret;
}

- awakeFromNib
{
	id ret = [super awake];
	[self _setUp];
	return ret;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	NXPoint        imageOrigin;
	NXSize         size, theSize, tensSize = {0.0, 0.0}, onesSize, twoSize;
	id             imageAyear, imageByear, imageCyear, imageDyear;
	float          width, widthA, widthB, widthC;
	float          imageAdjust;
	int            currentYear, hrs;
	int 			highlight;
	
	highlight = 0;
	if (hidden) {
		PSsetgray(NX_LTGRAY);
		NXRectFill(&bounds);
	}
	
	[DigitalNumbers[1][highlight] getSize:&onesSize];
	[DigitalNumbers[2][highlight] getSize:&twoSize];

	if (!militaryTime)
		colonPosition.x = onesSize.width + twoSize.width + 7.0;
	else
		colonPosition.x = 30;

	if (showYear)
	{
		imageAdjust = 5;
		colonPosition.y = 51;
	}
	else
	{
		imageAdjust = 0;
		colonPosition.y = 46;
	}


	imageOrigin.x = 0;
	imageOrigin.y = 0 + imageAdjust;	//(0)
	[clockbase composite:NX_COPY toPoint:&imageOrigin];


	if (militaryTime)
	{
		hrs = hours;
	}
	else
	{
		hrs = hours % 12;
		if (!hrs)	hrs = 12;
	}
	
	[imageAM[highlight] getSize:&size];
	imageOrigin.x = 44;
	imageOrigin.y = 48 + imageAdjust;	//(48)
	if (!militaryTime)
	{
		if (meridian)
		{
			imageOrigin.x = 45;
			[imagePM[highlight] composite:NX_COPY toPoint:&imageOrigin];
		}
		else
			[imageAM[highlight] composite:NX_COPY toPoint:&imageOrigin];
	}

	// Hours 
	highlight = highlightSelection == 4 ? 1 : 0;
	
	imageOrigin = colonPosition;
	if (hrs / 10 || militaryTime)
		[DigitalNumbers[hrs / 10][highlight] getSize:&tensSize];


	[DigitalNumbers[hrs % 10][highlight] getSize:&onesSize];
	imageOrigin.x -= tensSize.width + onesSize.width;

	if (hrs / 10 || militaryTime)
	{
		[DigitalNumbers[hrs / 10][highlight] composite:NX_COPY toPoint:&imageOrigin];
		imageOrigin.x += tensSize.width;
	}

	[DigitalNumbers[hrs % 10][highlight] composite:NX_COPY toPoint:&imageOrigin];
	imageOrigin.x += onesSize.width;

	// Colon
	highlight = 0;
	[[imageColon[highlight] getSize:&size] composite:NX_COPY toPoint:&imageOrigin];
	imageOrigin.x += size.width;

	// Minutes
	highlight = highlightSelection == 5 ? 1 : 0;

	[[DigitalNumbers[minutes / 10][highlight] getSize:&size] composite:NX_COPY toPoint:&imageOrigin];
	imageOrigin.x += size.width;
	[DigitalNumbers[minutes % 10][highlight] composite:NX_COPY toPoint:&imageOrigin];

	// Weekday
	highlight = 0;
	imageOrigin.x = 21;
	imageOrigin.y = 34 + imageAdjust;	//(34)
	[DayNames[weekday][highlight] composite:NX_COPY toPoint:&imageOrigin];

	// Month
	highlight = highlightSelection == 2 ? 1 : 0;
	imageOrigin.x = 19;
	imageOrigin.y = 9 + imageAdjust;	//(9)
	[MonthNames[month][highlight] composite:NX_COPY toPoint:&imageOrigin];

	tensSize.width = 0.0;
	tensSize.height = 0.0;


	// Day
	highlight = highlightSelection == 3 ? 1 : 0;
	imageOrigin.x = 30;
	imageOrigin.y = 15 + imageAdjust; //(15)
	if (date / 10)
		[DateNumbers[date / 10][highlight] getSize:&tensSize];
	[DateNumbers[date % 10][highlight] getSize:&onesSize];
	imageOrigin.x -= (tensSize.width + onesSize.width) / 2;
	if (date / 10)
		[DateNumbers[date / 10][highlight] composite:NX_COPY toPoint:&imageOrigin];
	imageOrigin.x += tensSize.width + 1;
	[DateNumbers[date % 10][highlight] composite:NX_COPY toPoint:&imageOrigin];

	//Draw the year
	if (showYear)
	{
		// Year
		highlight = highlightSelection == 1 ? 1 : 0;
		currentYear = [self year] + 1900;
		imageAyear = YearNumbers[currentYear / 1000][highlight];
		currentYear = currentYear % 1000;

		imageByear = YearNumbers[currentYear / 100][highlight];
		currentYear = currentYear % 100;

		imageCyear = YearNumbers[currentYear / 10][highlight];
		currentYear = currentYear % 10;

		imageDyear = YearNumbers[currentYear][highlight];

		[imageAyear getSize:&theSize];
		widthA = theSize.width;
		[imageByear getSize:&theSize];
		widthB = theSize.width;
		[imageCyear getSize:&theSize];
		widthC = theSize.width;
		[imageDyear getSize:&theSize];
		width = widthA + widthB + widthC + theSize.width;

		imageOrigin.x = 32 - (width / 2.0);
		imageOrigin.y = 0;
		[imageAyear composite:NX_COPY toPoint:&imageOrigin];
		imageOrigin.x = imageOrigin.x + widthA;
		[imageByear composite:NX_COPY toPoint:&imageOrigin];
		imageOrigin.x = imageOrigin.x + widthB;
		[imageCyear composite:NX_COPY toPoint:&imageOrigin];
		imageOrigin.x = imageOrigin.x + widthC;
		[imageDyear composite:NX_COPY toPoint:&imageOrigin];
	}
	return self;
}

- setSeconds:(int)theSeconds
{
	seconds = theSeconds;
	return self;
}

- setMinutes:(int)theMinutes
{
	minutes = theMinutes;
	return self;
}

- setHours:(int)theHours
{
	hours = theHours;
	return self;
}

- setWeekday:(int)theWeekday
{
	weekday = theWeekday;
	return self;
}

- setDate:(int)theDate
{
	date = theDate;
	return self;
}

- setMonth:(int)theMonth
{
	month = theMonth;
	return self;
}

- setYear:(int)theYear
{
	year = theYear;
	return self;
}

- setMeridian:(int)theMeridian
{
	meridian = theMeridian;
	return self;
}

- takeMinutesFrom:sender
{
	[self setMinutes:[sender intValue]];
	[self display];
	return self;
}

- takeHoursFrom:sender	
{
	int            localhours = [sender intValue];

	localhours %= 24;
	if (localhours < 12)
	{
		[self setMeridian:0];
	}
	else
	{
		[self setMeridian:1];
	}
	[self setHours:localhours];
	[self display];
	return self;
}
	
- takeWeekdayFrom:sender
{
	[self setWeekday:[sender intValue]];
	[self display];
	return self;
}

- takeDateFrom:sender
{
	[self setDate:[sender intValue]];
	[self display];
	return self;
}

- takeMonthFrom:sender
{
	[self setMonth:[sender intValue]];
	[self display];
	return self;
}

- setTime: (struct tm*)theTime
{
	seconds = theTime->tm_sec;
	minutes = theTime->tm_min;
	hours = theTime->tm_hour;
	if (hours < 12)
	{
		meridian = 0;
	}
	else
	{
		meridian = 1;
	};
	date = theTime->tm_mday;
	month = theTime->tm_mon;
	year = theTime->tm_year;
	weekday = theTime->tm_wday;
	return self;
};

- (const char *)getInspectorClassName
{
	return "MiscClockViewInspector";
}

- (int)year
{
	return year;
}

- (int)month
{
	return month;
}

- (int)weekday
{
	return weekday;
}

- (int)date
{
	return date;
}

- (int)hours
{
	return hours;
}

- (int)minutes
{
	return minutes;
}

- (int)seconds
{
	return seconds;
}


- (BOOL)militaryTime
{
	return militaryTime;
}

- (BOOL)showYear
{
	return showYear;
}

- setShowYear:(BOOL)state
{
	showYear = state;
	return self;
}


- setMilitaryTime:(BOOL)state
{
	militaryTime = state;
	return self;
}


- (BOOL)keepingTime
{
	return keepingTime;
}


- setKeepingTime:(BOOL)yesno
{
	keepingTime = yesno;
	return self;
}

- read:(NXTypedStream *) stream
{
	int myVersion = NXTypedStreamClassVersion (stream,[self name]);

	[super read:stream];
	if (myVersion < 2)
	{
		NXReadTypes (stream, "iiiiiiiiii", &seconds, &minutes, &hours,
		&date, &month, &year, &weekday, &meridian, &showYear, &militaryTime);
	}
	else
	{
		NXReadTypes (stream, "iiiiiiiicc", &seconds, &minutes, &hours,
		&date, &month, &year, &weekday, &meridian, &showYear, &militaryTime);
	}
	if (myVersion < 1)
	{
		keepingTime = NO;
	}
	else if (myVersion == 1)
	{
		NXReadType (stream, "i", &keepingTime);
	}
	else
	{
		NXReadType (stream, "c", &keepingTime);
	}
	if (myVersion > 2) {
		NXReadType (stream, "c", &hidden);
	} else {
		hidden = NO;
	}
	if (myVersion > 3)
	{
		delegate = NXReadObject (stream);
	}
	else
	{
		delegate = nil;
	}
	return self;
}

- write:(NXTypedStream *) stream
{
	[super write:stream];
	NXWriteTypes (stream, "iiiiiiiicc", &seconds, &minutes, &hours,
	&date, &month, &year, &weekday, &meridian, &showYear, &militaryTime);
	NXWriteType (stream, "c", &keepingTime);
	NXWriteType (stream, "c", &hidden);
	NXWriteObject (stream, delegate);
	return self;
}

- setHide:(BOOL)hide
{
   hidden = hide;

   return self;
}

- (BOOL)isHidden
{
   return hidden;
}


void 
Animate (timedEntry, now, self)
	DPSTimedEntry  timedEntry;
	double         now;
	id             self;
{
	[(id)self ticktock];
}

- ticktock
{
	struct timeval tp;
	struct timezone tzp;
	struct tm     *tv;

	gettimeofday (&tp, &tzp);
	tv = localtime (&tp.tv_sec);
	[self setTime:tv];
	[self display];
	return self;
}

- startClock:sender
{
	if (!keepingTime)
	{
		[self ticktock];
		timedEntry = DPSAddTimedEntry (60, Animate, (void *)self, NX_MODALRESPTHRESHOLD);
		keepingTime = YES;
		NXPing ();
	}
	return self;
}

- stopClock:sender
{
	if (timedEntry)
	{
		DPSRemoveTimedEntry (timedEntry);
		[self ticktock];
		keepingTime = NO;
		timedEntry = 0;
	}

	return self;
}

- free
{
   int i,j;
	
	for(j=0;j<2;j++)
	{
		[imageA[j] free];
		[imageP[j] free];
		for(i=0;i<10;i++)
			[DigitalNumbers[i][j] free];
		[imageColon[j] free];
		[imageAM[j] free];
		[imagePM[j] free];
		
		for(i=0;i<7;i++)
			[DayNames[i][j] free];
		for(i=0;i<12;i++)
			[MonthNames[i][j] free];
		for(i=0;i<10;i++)
			[DateNumbers[i][j] free];
		for(i=0;i<10;i++)
			[YearNumbers[i][j] free];
		[YearColon[j] free];
	}
	[clockbase free];
	[theImage free];

	if (timedEntry != NULL)
		DPSRemoveTimedEntry (timedEntry);
	return[super free];
}

- setToCurrentTime:sender
{
	struct timeval		tp;
	struct timezone	tzp;
	struct tm     		*tv;

	gettimeofday (&tp, &tzp);
	tv = localtime (&tp.tv_sec);
	[self setTime:tv];
	[self display];
	
	return self;
}

// Action stuff

- mouseDown:(NXEvent *)theEvent
{

    NXPoint	mouseLocation;
	NXRect	minuteRect;
	NXRect	hourRect;
	NXRect	dayRect;
	NXRect	monthRect;
	NXRect	yearRect;
	BOOL	flipped = [self isFlipped];
	int 	oldSelection = highlightSelection;
	mouseLocation = theEvent->location;

	/* ... and convert them into our view coordinate system */

	[self convertPoint:&mouseLocation fromView:nil];

	NXSetRect(&hourRect,colonPosition.x-21.0,colonPosition.y,20.0,11.0);
	NXSetRect(&minuteRect,colonPosition.x+1.0,colonPosition.y,20.0,11.0);
	if(showYear)
	{
		NXSetRect(&dayRect,30.0,20.0,20.0,17.0);
		NXSetRect(&monthRect,19.0,14.0,22.0,5.0);
		NXSetRect(&yearRect,22.0,0.0,20.0,5.0);
	}
	else
	{
		NXSetRect(&dayRect,30.0,15.0,20.0,17.0);
		NXSetRect(&monthRect,19.0,9.0,22.0,5.0);
	}	// Find out what we hit
	
	highlightSelection = 0;
	// 1. Year
	if(showYear)
	{
		if(NXMouseInRect(&mouseLocation, &yearRect, flipped))
		{
			highlightSelection = 1;
    		return oldSelection == highlightSelection ? self : [self display];
		}
	}
	// 2. Month
	if(NXMouseInRect(&mouseLocation, &monthRect, flipped))
	{
		highlightSelection = 2;
	}
	// 3. Day
	else if(NXMouseInRect(&mouseLocation, &dayRect, flipped))
	{
		highlightSelection = 3;
	}
	// 4. Hour
	else if(NXMouseInRect(&mouseLocation, &hourRect, flipped))
	{
		highlightSelection = 4;
	}
	// 5. Minutes
	else if(NXMouseInRect(&mouseLocation, &minuteRect, flipped))
	{
		highlightSelection = 5;
	}
	
    return oldSelection == highlightSelection ? self : [self display];
}


- increment:sender
{
	return [self changeDate:1];
}

- decrement:sender
{
	return [self changeDate:-1];
}

- changeDate:(int) change
{
	static int monthDays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	int daysInMonth;
    struct tm tmDate;
    time_t time;

	
	switch(highlightSelection)
	{
		case 1: // Year
			year += change;
			break;
		case 2: // Month
			month += change;
			break;
		case 3: // Day
			date += change;
			break;
		case 4: // Hours
			hours += change;
			break;
		case 5: // Minutes
			minutes += change;
			break;
		default: // Nothing selected
			return nil;
	}
	
	// Consistency checks
	if(minutes > 59)
	{
		++hours;
		minutes = 0;
	}
	else if(minutes < 0)
	{
		--hours;
		minutes = 59;
	}

	if(hours > 23)
	{
		++date;
		hours = 0;
	}
	else if(hours < 0)
	{
		--date;
		hours = 23;
	}
	meridian = hours > 12 ? 1 : 0;
	
	if(month != 1)
	{
		daysInMonth = monthDays[month];
	}
	else
	{
		daysInMonth = (year % 4) ? 28 : 29;
	}
	
	if(date > daysInMonth)
	{
		++month;
		date = 1;
	}
	else if(date < 1)
	{
		--month;
		if(month != 1)
		{
			daysInMonth = monthDays[month];
		}
		else
		{
			daysInMonth = (year % 4) ? 28 : 29;
		}
		date = daysInMonth;
	}

	if(month > 11)
	{
		++year;
		month = 0;
	}
	else if(month < 0)
	{
		--year;
		month = 11;
	}
	// We have to set the correct week day

	tmDate.tm_sec = 0;
	tmDate.tm_min = 0;
	tmDate.tm_hour = 0;
	tmDate.tm_mday = 1;
	tmDate.tm_mon = month;
	tmDate.tm_year = year - 1900;
	tmDate.tm_wday = 0;
	tmDate.tm_yday = 0;
	tmDate.tm_isdst = 0;
	tmDate.tm_gmtoff = 0;
	tmDate.tm_zone = "";

	time = mktime(&tmDate);

	tmDate = *localtime(&time);

	time -= tmDate.tm_gmtoff;

	tmDate = *localtime(&time);
	weekday = tmDate.tm_wday;
	if(delegate && [delegate respondsTo:@selector(dateChanged:)])
	{
		[delegate dateChanged:self];
	}
	return [self display];
}

- setDelegate:aDelegate
{
	delegate = aDelegate;
	return self;
}

- delegate
{
	return delegate;
}


@end
