//
//	MiscTime.m -- a generic class to simplify manipulation of times
//		Written by Don Yacktman (c) 1993 by Don Yacktman.
//				Version 1.0.  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//		This is a free object!  Contact the author for the latest version.
//		Don Yacktman, 4279 N. Ivy Lane, Provo, UT, 84604
//		e-mail:  Don_Yacktman@byu.edu
//
//	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.
//	

	// *****  denotes an unfinished/unimplemented method.

#import <misckit/MiscTime.h>
#import <sys/time.h>

// a few functions to handle date wierdnesses

static int isLeapYear(int year)
{
	// *****
	return 0;
}

static int Misc_daysUpTo(int month, int year)
{
	int array[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
	if (month < 2) return array[month];
	return (array[month] + isLeapYear(year));
}

static int Misc_daysIn(int month, int year)
{
	int array[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	if (month != 1) return array[month];
	return (array[month] + isLeapYear(year));
}

@implementation MiscTime

- init
{
	id ret;
	if (!_initted) ret = [super init];
	else ret = self;
	_initted = YES;
	dow = MISC_TIME_UNKNOWN_DOW;
	if (!myStringValue) myStringValue = (char *)malloc(256);
	year = 0; day = 0; month = 0; hour = 0;
	minute = 0; second = 0; microsecond = 0;
	isRelative = NO;
	return ret;
}

- initWithCurrentTime
{
	struct timeval tp;
	struct timezone tzp;
	struct tm *tv;
	
	id ret = [self init];

	gettimeofday(&tp, &tzp);
	tv = localtime(&tp.tv_sec);
	
	microsecond = tp.tv_usec;
	second = tv->tm_sec;
	minute = tv->tm_min;
	hour   = tv->tm_hour;
	day    = tv->tm_mday - 1;
	dow    = tv->tm_wday;
	month  = tv->tm_mon;
	year   = tv->tm_year;
	return ret;
}

- calcDayOfWeek
{
	// ***** 
	return self;
}

- setYear:(int)t { year = t; dow = MISC_TIME_UNKNOWN_DOW; return self; }
- setMonth:(int)t { month = t; dow = MISC_TIME_UNKNOWN_DOW; return self; }
- setDayOfWeek:(int)t { dow = t; return self; }
- setDay:(int)t { day = t; dow = MISC_TIME_UNKNOWN_DOW; return self; }
- setHour:(int)t { hour = t; return self; }
- setMinute:(int)t { minute = t; return self; }
- setSecond:(int)t { second = t; return self; }
- setMicrosecond:(int)t { microsecond = t; return self; }
- setRelative:(BOOL)t { isRelative = t; return self; }

- (int)_nintValue // ignores usecs.
{	// ***** Not quite right; ignores leap years
	return ((((((year - 1970) * MISC_TIME_MAX_DAYS
				+ Misc_daysUpTo(month, year) + day)
				* MISC_TIME_MAX_HOURS) + hour)
				* MISC_TIME_MAX_MINUTES + minute)
				* MISC_TIME_MAX_SECONDS + second);
}

- (int)intValue // # seconds since Jan 1, 1970
{ // rounds usec. and add in...
	return ([self _nintValue] +
				((microsecond < MISC_TIME_MAX_MICROSECONDS / 2) ? 0 : 1));
}

- (double)doubleValue
{
	// *****
	return 0.0;
}

- (float)floatValue
{
	// *****
	return 0.0;
}

- (const char *)stringValue
{ // Need to allow the user to set the time and/or date format! *****
	// Don't free this!  that's why it's a const!
	if (!myStringValue) myStringValue = (char *)malloc(256);
	sprintf(myStringValue, "%ld : %ld : %ld", hour, minute, second);
	return myStringValue;
}

- free
{
	free(myStringValue);
	return [super free];
}

- (int)dayOfWeek
{
	if (dow < 0) [self calcDayOfWeek];
	return dow;
}

- (int)year { return year; }
- (int)month { return month; }
- (int)day { return day; }
- (int)hour { return hour; }
- (int)minute { return minute; }
- (int)second { return second; }
- (int)microsecond { return microsecond; }
- (BOOL)isRelative { return isRelative; }


- addTime:aTime
{
	if (![aTime isKindOf:[MiscTime class]]) return nil;
	[self addMicroseconds:[aTime microsecond]];
	[self addSeconds:[aTime second]];
	[self addMinutes:[aTime minute]];
	[self addHours:[aTime hour]];
	[self addDays:[aTime day]];
	[self addMonths:[aTime month]];
	[self addYears:[aTime year]];
	return self;
}

- subtractTime:aTime
{
	if (![aTime isKindOf:[MiscTime class]]) return nil;
	[self subtractMicroseconds:[aTime microsecond]];
	[self subtractSeconds:[aTime second]];
	[self subtractMinutes:[aTime minute]];
	[self subtractHours:[aTime hour]];
	[self subtractDays:[aTime day]];
	[self subtractMonths:[aTime month]];
	[self subtractYears:[aTime year]];
	return self;
}

- addMicroseconds:(long)t
{
	long x = [self microsecond] + t;
	if (!t) return self;
	[self setMicrosecond:(x % MISC_TIME_MAX_MICROSECONDS)];
	[self addSeconds:(x / MISC_TIME_MAX_MICROSECONDS)];
	return self;
}

- addSeconds:(long)t
{
	long x = [self second] + t;
	if (!t) return self;
	[self setSecond:(x % MISC_TIME_MAX_SECONDS)];
	[self addMinutes:(x / MISC_TIME_MAX_SECONDS)];
	return self;
}

- addMinutes:(long)t
{
	long x = [self minute] + t;
	if (!t) return self;
	[self setMinute:(x % MISC_TIME_MAX_MINUTES)];
	[self addHours:(x / MISC_TIME_MAX_MINUTES)];
	return self;
}

- addHours:(long)t
{
	long x = [self hour] + t;
	if (!t) return self;
	[self setHour:(x % MISC_TIME_MAX_HOURS)];
	[self addDays:(x / MISC_TIME_MAX_HOURS)];
	return self;
}

- addDays:(long)t
{
	long x = [self day] + t; int k;
	if (!t) return self;
	
	k = Misc_daysIn(month, year);
	while (x >= k) { // This is slow, but it works.
		[self addMonths:1];
		x -= k; k = Misc_daysIn(month, year);
	}
	[self setDay:(x % MISC_TIME_MAX_DAYS)];
	return self;
}

- addWeeks:(long)t
{
	return [self addDays:(t * 7)];
}

- addMonths:(long)t
{
	long x = [self month] + t;
	if (!t) return self;
	[self setMonth:(x % MISC_TIME_MAX_MONTHS)];
	[self addYears:(x / MISC_TIME_MAX_MONTHS)];
	return self;
}

- addYears:(long)t
{
	if (t) [self setYear:([self year] + t)];
	return self;
}

- (BOOL)isAfter:aTime
{ // returns YES if we are "older" than time passed in
	int si = [self _nintValue];
	int oi = [aTime _nintValue];
	if (si > oi) return YES;
	if ((si == oi) && ([self microsecond] > [aTime microsecond])) return YES;
	return NO;
}

- (BOOL)isEqual:aTime
{ // returns YES if we are "same" as time passed in
	if (([self _nintValue] == [aTime _nintValue]) &&
			([self microsecond] == [aTime microsecond])) return YES;
	return NO;
}

- subtractMicroseconds:(long)t
{
	long us = t % MISC_TIME_MAX_MICROSECONDS;
	long s = t / MISC_TIME_MAX_MICROSECONDS;
	long sus = [self microsecond];
	if (us > sus) { s++; sus += MISC_TIME_MAX_MICROSECONDS; }
	[self subtractSeconds:s];
	[self setMicrosecond:(sus - us)];
	return self;
}

- subtractSeconds:(long)t
{
	long s = t % MISC_TIME_MAX_SECONDS;
	long m = t / MISC_TIME_MAX_SECONDS;
	long ss = [self second];
	if (s > ss) { m++; ss += MISC_TIME_MAX_SECONDS; }
	[self subtractMinutes:m];
	[self setSecond:(ss - s)];
	return self;
}

- subtractMinutes:(long)t
{
	long m = t % MISC_TIME_MAX_MINUTES;
	long h = t / MISC_TIME_MAX_MINUTES;
	long sm = [self minute];
	if (m > sm) { h++; sm += MISC_TIME_MAX_MINUTES; }
	[self subtractHours:h];
	[self setMinute:(sm - m)];
	return self;
}

- subtractHours:(long)t
{
	long h = t % MISC_TIME_MAX_HOURS;
	long d = t / MISC_TIME_MAX_HOURS;
	long sh = [self hour];
	if (h > sh) { d++; sh += MISC_TIME_MAX_HOURS; }
	[self subtractDays:d];
	[self setHour:(sh - h)];
	return self;
}

- subtractDays:(long)t
{
	long sd = [self day];
	while (t > sd) { // this is slow, but it works.
		sd += Misc_daysIn(month, year);
		[self subtractMonths:1];
	}
	[self setDay:(sd - t)];
	return self;
}

- subtractWeeks:(long)t
{
	return [self subtractDays:(t * 7)];
}

- subtractMonths:(long)t
{
	long m = t % MISC_TIME_MAX_MONTHS;
	long y = t / MISC_TIME_MAX_MONTHS;
	long sm = [self month];
	if (m > sm) { y++; sm += MISC_TIME_MAX_MONTHS; }
	[self subtractYears:y];
	[self setMonth:(sm - m)];
	return self;
}

- subtractYears:(long)t
{
	if (t) [self setYear:([self year] - t)];
	return self;
}

- copy
{
	id myCopy = [[MiscTime alloc] init];
	[myCopy setYear:year];
	[myCopy setMonth:month];
	[myCopy setDay:day];
	[myCopy setDayOfWeek:dow];
	[myCopy setHour:hour];
	[myCopy setMinute:minute];
	[myCopy setSecond:second];
	[myCopy setMicrosecond:microsecond];
	[myCopy setRelative:isRelative];
	return myCopy;
}

- copyTimeFrom:aTime
{
	[self setYear:[aTime year]];
	[self setMonth:[aTime month]];
	[self setDay:[aTime day]];
	[self setDayOfWeek:[aTime dayOfWeek]];
	[self setHour:[aTime hour]];
	[self setMinute:[aTime minute]];
	[self setSecond:[aTime second]];
	[self setMicrosecond:[aTime microsecond]];
	[self setRelative:[aTime isRelative]];
	return self;
}

- read:(NXTypedStream *)stream
{
	[super read:stream];
	NXReadTypes(stream, "cllllllll", &isRelative, &microsecond, &second,
			&minute, &hour, &dow, &day, &month, &year);
	if (!myStringValue) myStringValue = (char *)malloc(256);
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteTypes(stream, "cllllllll", &isRelative, &microsecond, &second,
			&minute, &hour, &dow, &day, &month, &year);
	return self;
}

// NXTransport protocol implementation:
- encodeUsing:(id <NXEncoding>)portal
{
	[portal encodeData:&isRelative  ofType:"c"];
	[portal encodeData:&microsecond ofType:"i"];
	[portal encodeData:&second      ofType:"i"];
	[portal encodeData:&minute      ofType:"i"];
	[portal encodeData:&hour        ofType:"i"];
	[portal encodeData:&dow         ofType:"i"];
	[portal encodeData:&day         ofType:"i"];
	[portal encodeData:&month       ofType:"i"];
	[portal encodeData:&year        ofType:"i"];
	return self;
}

- decodeUsing:(id <NXDecoding>)portal
{
	[portal decodeData:&isRelative  ofType:"c"];
	[portal decodeData:&microsecond ofType:"i"];
	[portal decodeData:&second      ofType:"i"];
	[portal decodeData:&minute      ofType:"i"];
	[portal decodeData:&hour        ofType:"i"];
	[portal decodeData:&dow         ofType:"i"];
	[portal decodeData:&day         ofType:"i"];
	[portal decodeData:&month       ofType:"i"];
	[portal decodeData:&year        ofType:"i"];
	return self;
}

- encodeRemotelyFor:(NXConnection *)connection
	freeAfterEncoding:(BOOL *)flagp isBycopy:(BOOL)isByCopy
{
	if (isByCopy) {
		*flagp = NO; // object will copy.
		return self; //encode object (copy it)
	}
	*flagp = NO; // object will copy.
	// super will encode the proxy otherwise
	return [super encodeRemotelyFor:connection
				freeAfterEncoding:flagp isBycopy:isByCopy];
}

@end
