#import "Date.h"
#import <appkit/appkit.h>

#import <time.h>
#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <math.h>

#define OK 1
#define CANCEL 2
#define DATE_SIZE 18
static const char *errNibPath = "DateError.nib";
static const char *DayOfWeek[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static const char *MonthStr[] = {"Jan","Feb","Mar","Apr",
						"May","June","July","Aug",
						"Sep","Oct","Nov","Dec"};
static BOOL parseError;
static id dateField;	// A TextField used to allow a user to edit incorrect dates
static id selField;		// A TextField used to display the method which invoked invalidDate
static char tmpdateString[DATE_SIZE];

@implementation Date

/* Class methods */
/* Used to get the SEL method for displaying which
	method called invalidDate: */
+ (const char *) selName : (SEL) theSEL
{
	if(theSEL == @selector(initFromString:) )
		return "initFromString:";
	else if(theSEL ==  @selector(initFromDate:::) )
		return "initFromDate:::";
	else if(theSEL ==  @selector(initFromJulianDate:) )
		return "initFromJulianDate:";
	else
		return "NoMethod";
}

- setErrorMode : (int) mode
{
	errorMode = mode;
	return self;
}

- free
{
	if(dateString != NULL)
		NX_FREE(dateString);
	return [super free];
}

/* Parse a date string("mm/dd/yy[yy]") into its integer components */
- parseDateString : (char *) dateStrng : (Day *) d : (Month *) m : (Year *) y
{
char tmpDate[16],*tmpDatePtr,*token;
short yr,century;
	/* Assign the integer values to day,month,year */
	strcpy(tmpDate, dateStrng);
	tmpDatePtr = tmpDate;
	token = strtok(tmpDatePtr,"/");
	tmpDatePtr = NULL;
	if(token == NULL)
	{
		parseError = YES;
		*m = 0;
	}
	else
		*m = atoi(token);
	token = strtok(tmpDatePtr,"/");
	if(token == NULL)
	{
		parseError = YES;
		*d = 0;
	}
	else
		*d = atoi(token);
	token = strtok(tmpDatePtr,"/");
	if(token == NULL)
	{
		parseError = YES;
		*y = 0;
	}
	else
		*y = atoi(token);
	if(*y < 100)
	{	// Get the current century
		[self now : NULL : NULL : &yr : NULL : NULL : NULL];
		century = yr - (yr % 100);
		(*y) += century;
	}
	return self;
}

/* The day, month & year must be set before calling */
- (BOOL) validDate
{
short calDay,calMonth,calYear;
long jDate;
	/* Convert date to Julian day number and back */
	jDate = [self julianRep : day : month : year];
	[self calendarDate : jDate : &calDay : &calMonth : &calYear];
	/* See if the date has changed */
	if(day != calDay)
		return NO;
	else if(month != calMonth)
		return NO;
	else if(year != calYear)
		return NO;
	else
		return YES;
}

- invalidDate : (SEL) callingMethod;
{
int rtnVal;
static id editErrorPanel = nil;
char dateStrng[16];
	switch(errorMode)
	{
		case 0 :
			return nil;
			break;
		case 1 :
			NXRunAlertPanel(NULL,"%d/%d/%d is not a valid date",
				NULL,NULL, NULL,month,day,year);
			return nil;
			break;
		case 2 :
			if(editErrorPanel == nil)
			{
				editErrorPanel = [NXApp loadNibFile : errNibPath
					owner : self withNames : YES];
				if(editErrorPanel == nil)
				{
					NXRunAlertPanel(NULL,"%d/%d/%d is not a valid date",
						NULL,NULL, NULL,month,day,year);
					return nil;
				}
			}
			if(parseError == YES)
				strcpy(dateStrng,dateString);
			else
				sprintf(dateStrng,"%d/%d/%d",month,day,year);
			/* Get the panel fields by name and initialize then */
			dateField = NXGetNamedObject("DateErrordateField", editErrorPanel);
			[dateField setStringValue : dateStrng];
			selField = NXGetNamedObject("DateErrorselField", editErrorPanel);
			[selField setStringValue : [Date selName : callingMethod]];
			/* Put the error panel in a modal loop */
			rtnVal = [NXApp runModalFor : editErrorPanel];
			[editErrorPanel orderOut : self];
			if(rtnVal == OK)
			{
				/* Assign the string representation */
				if(!dateString)
					NX_MALLOC(dateString,char,11);
				sprintf(dateString,"%d/%d/%d",month,day,year);
				/* Assign the Julian date representation*/
				date = [self julianRep : day : month : year];
				return self;
			}
			else
				return nil;
			break;
		default :
			return nil;
	}
}

/* Method invoked by the Ok button of the editErrorPanel */
- dateOk : sender
{
	parseError = NO;
	[self parseDateString : (char *) [dateField stringValue] : &day : &month : &year];
	if(parseError == YES || [self validDate] == NO)
		NXBeep();
	else
		[NXApp stopModal : OK];
	return self;
}
/* Method invoked by the ``Return nil'' button of the editErrorPanel */
- dateCancel : sender
{
	[NXApp stopModal : CANCEL];
	return self;
}

- initFromString : (char *) theDate
{
	parseError = NO;
	[self parseDateString : theDate : &day : &month : &year];
	if(parseError == YES)
	{
		dateString = theDate;
		return [self invalidDate : _cmd];
	}
	if([self validDate] == NO)
		return [self invalidDate : _cmd];

	/* Assign the string representation */
	if(!dateString)
		NX_MALLOC(dateString,char,11);
	sprintf(dateString,"%d/%d/%d",month,day,year);

	/* Assign the Julian representation */
	date = [self julianRep : day : month : year];

	return self;
}

- initFromDate : (Day) d : (Month) m : (Year) y
{
short yr,century;

	/* Assign the integer values to day,month,year */
	day = d;
	month = m;
	if(y < 100)
	{
		[self now : NULL : NULL : &yr : NULL : NULL : NULL];
		century = yr - (yr % 100);
		y += century;
	}
	year = y;
	if([self validDate] == NO)
		return [self invalidDate : _cmd];
	
	/* Assign the Julian representation */
	date = [self julianRep : day : month : year];

	/* Assign the string representation */
	if(!dateString)
		NX_MALLOC(dateString,char,11);
	sprintf(dateString,"%d/%d/%d",month,day,year);
	
	return self;
}

- initFromJulianDate : (JulianDate) jDate
{
	date = jDate;
	[self calendarDate : jDate : &day : &month : &year];
	if([self validDate] == NO)
		return [self invalidDate : _cmd];
	if(!dateString)
		NX_MALLOC(dateString,char,11);
	sprintf(dateString,"%d/%d/%d",month,day,year);
	
	return self;
}

- initFromNow
{

	[self now : &day : &month : &year : &hours : &minutes : &seconds];
	/* Assign the Julian representation */
	date = [self julianRep : day : month : year];

	/* Assign the string representation */
	if(!dateString)
		NX_MALLOC(dateString,char,11);
	sprintf(dateString,"%d/%d/%d",month,day,year);
	
	return self;
}

- setTime : (short) hr : (short) min : (short) sec
{
	hours = hr;
	minutes = min;
	seconds = sec;
	return self;
}

- (void) now : (Day *) d : (Month *) m : (Year *) y : (short *) hr : (short *) min : (short *) sec
{
struct tm *timeStruct;
time_t now;

	time(&now);
	timeStruct = localtime(&now);
	/* Assign the integer values to day,month,year */
	if(d != NULL)
		*d = timeStruct->tm_mday;
	if(m != NULL)
		*m = timeStruct->tm_mon + 1;
	if(y != NULL)
		*y = timeStruct->tm_year + 1900;
	if(hr != NULL)
		*hr = timeStruct->tm_hour;
	if(min != NULL)
		*min = timeStruct->tm_min;
	if(sec != NULL)
		*sec = timeStruct->tm_sec;
}

- (long) sysTime
{
	return time( (long *) 0);
}

/* Conversion functions */
- calendarDate : (long) jDate : (Day *) dy : (Month *) m : (Year *) y
{
long a,b,c,d,e,z,alpha;

	/* Calculate the day, month and year */
	z = jDate + 1;
	if(z < 2299161L)
		a = z;
	else
	{
		alpha = (long) ((z - 1867216.25) / 36524.25);
		a = z + 1 + alpha - alpha / 4;
	}
	b = a + 1524;
	c = (long) ((b - 122.1) / 365.25);
	d = (long) (365.25 * c);
	e = (long) ((b - d) / 30.6001);
	*dy = (Day) b - d - (long)(30.6001 * e);
	*m = (Month) (e < 13.5) ? e - 1 : e - 13;
	*y = (Year) (*m > 2.5) ? (c - 4716) : c - 4715;

	return self;
}

- (JulianDate) julianRep : (Day) d : (Month) m : (Year) y
{
int a,b;
long jDate;
float year_corr;
	
	/* Assign the Julian representation */
	year_corr = (y > 0 ? 0.0 : 0.75);
	if(m <= 2)
	{
		y --;
		m += 12;
	}
	b = 0;
	/* Cope with Greogrian calendar reform */
	if(y * 10000.0 + m * 100.0 + d >= 15821015.0)
	{
		a = y / 100;
		b = 2 - a + a / 4;
	}
	jDate = (JulianDate)(365.25 * y - year_corr) + (JulianDate)(30.6001 * (m+1)) +
			(JulianDate)(d + 1720994L + b);
	return jDate;
}

- (JulianDate) julianDate
{
	return(date);
}

- (Day) day
{
	return day;
}

- (Month) month
{
	return((Month) month - 1);
}

- (Year) year
{
	return((Year) year);
}
- (short) hours
{
	return hours;
}
- (short) minutes
{
	return minutes;
}
- (short) seconds
{
	return seconds;
}

- (unsigned long) extendedDate
{
unsigned long extendedDate;
	extendedDate =1000 * date + 100 * hours + minutes;
	return extendedDate;
}

- (void) time : (short *) hr : (short *) min : (short *) sec
{
	if(hr != NULL)
		*hr = hours;
	if(min != NULL)
		*min = minutes;
	if(sec != NULL)
		*sec = seconds;
}

- (const char *) stringDate
{
	strcpy(tmpdateString,dateString);
	return tmpdateString;
}

- (const char *) stringFixedDate
{
	sprintf(tmpdateString,"%.2d/%.2d/%.4d",month,day,year);
	return tmpdateString;
}

- (const char *) fullStringDate
{
	sprintf(tmpdateString,"%s %s %.2d %.4d",
		DayOfWeek[[self weekDay]],MonthStr[month-1],day,year);
	return tmpdateString;
}

- (const char *) time24hr
{
	sprintf(tmpdateString,"%.2d:%.2d",hours,minutes);
	return tmpdateString;
}

- (const char *) timeAM_PM
{
int hrs,am_pm;
	if(hours > 12)
	{
		hrs = hours - 12;
		am_pm = 1;
	}
	else
	{
		hrs = hours;
		am_pm = 0;
	}
	sprintf(tmpdateString,"%d:%.2d %s",hrs,minutes,(am_pm == 0 ? "AM" : "PM"));
	return tmpdateString;
}

- (WeekDay) weekDay
{
	// Return day : [0-6] = Sunday - Saturday
	return (WeekDay)((date + 2) % 7);
}

- (int) weekOfYear
{
long day1;
int offset,weekNo;
	day1 = [self julianRep: 1 : 1 : year];
	offset = (day1 + 2) % 7;
	weekNo = (date - day1 + offset) / 7;
	if(weekNo == 52)
		weekNo = 51;	// Leap year overshoot on last day
	return weekNo;
}

/* Output */
- (void) print
{
	printf("\tdateString : %s\n",dateString);
	printf("\tJulianDate : %ld\n",date);
	printf("\tday : %d month : %d year : %d\n",day,month,year);
}

/*\ ---------------------- ObjectArchival Protocol ---------------------- \*/
static inline char *ClassName() { return "Date"; }
static inline long ArchiveVersion()
{
/* --- MethodDescription
	ReturnValue: Current ObjectArchival version of object;
	Description: This routine returns the current version number of
		the methods associated with the ObjectArchival protocol for
		this object.  This value should be written to the archive
		stream to indicate the archive structure.;
*/
long version;
	version = 1000 * DATE_VERS + 10 * DATE_SUBVERS + DATE_TYPE;
	return version;
}

- (const char *) name
{
	return "Date";
}

- initFromTStream:(NXTypedStream *) stream
{
/* --- MethodDescription
	ReturnValue: self;
	Description: This method sends the init message to super,
		unarchives the instance information from the typed
		stream by messaging self with readFromTStream:, and finishes
		initializing of the instance;
	Args:
		stream: The typed stream from which the object is to be unarchived;
*/
	[super init];
	if( [self readFromTStream: stream] == nil )
		return nil;
	/* Recompute the other instance variables from Julian date */
	[self initFromJulianDate: date];

	return self;
}

- readFromTStream:(NXTypedStream *) stream
{
/* --- MethodDescription
	ReturnValue: self;
	Description: This method unarchives the information necessary to
		reconstruct an object instance.  The information unarchived
		is the same as archived by the writeToTStream: method.;
	Args:
		stream: The typed stream from which the object is to be unarchived;
*/
long version;
const char *className;
void *data1 = NULL, *data2 = NULL;

NX_DURING
[self debug: SUPER_DEBUG method: _cmd, "class = %s; superclass = %s\n",
	[[self class] name], [[self superclass] name]];
[self debug: SUPER_DEBUG method: _cmd, "\tinstance = %s\n", ClassName()];

	/* First read the class name & archive version */
	NXReadTypes(stream, "*i", &className, &version);
	if( strcmp(className, ClassName()) != 0 )
	{
		NXAllocErrorData(strlen(className)+1, &data1);
		NXAllocErrorData(strlen(ClassName())+1, &data2);
		strcpy(data1, className);
		strcpy(data2, ClassName());
		NX_RAISE(eWrongClassName, data1, data2);
	}
	/* Unarchive this version format */
	switch( version )
	{
		case DATE_VERSION_0:
			/* Unarchive the julian date */
			NXReadType(stream, "i", &date);
			break;
		default:
			NXAllocErrorData(strlen([[self class] name])+1, &data1);
			NXAllocErrorData(sizeof(long), &data2);
			strcpy(data1, [[self class] name]);
			*((long *) data2) = version;
			NX_RAISE(eBadObjVersion, data1, data2);
			break;
	}

NX_HANDLER
	[self free];
	NX_RERAISE();
NX_ENDHANDLER

	return self;
}

- writeToTStream:(NXTypedStream *) stream
{
/* --- MethodDescription
	ReturnValue: self;
	Description: This method archives the information necessary to
		reconstruct an object instance.;
	Args:
		stream: The typed stream to which the object is to be archived;
*/
long version;
const char *className;

NX_DURING
[self debug: SUPER_DEBUG method: _cmd, "class = %s; superclass = %s\n",
	[[self class] name], [[self superclass] name]];
[self debug: SUPER_DEBUG method: _cmd, "\tinstance = %s\n", ClassName()];

	/* First write the class name & archive version */
	className = ClassName();
	version = ArchiveVersion();
	NXWriteTypes(stream, "*i", &className, &version);

	/* Archive the julian date */
	NXWriteType(stream, "i", &date);

NX_HANDLER
	NX_RERAISE();
NX_ENDHANDLER

	return self;
}

@end
/* RCS Information:
	$Author: me $;
	$Date: 93/02/23 02:00:34 $;
	$Source: /usr1/me/NeXTSrc/MyClasses/RCS/Date.m,v $;
	$Revision: 1.1 $;
*/
