//
// MailWatchView.m
//		A BackSpace Module
//		Version 1.1		11/09/94
//		by Robert Lutwak	robert@amo.mit.edu
//
// Revision History
//
// Version 1.1
//
// Replaced calls to opendir() and readdir() with stat() on file name.
// This way we can catch zero-size mail files left behind
// by some mail clients
//
// Added dwrite for no-mail gray level: dwrite MailWatch DarkGray (0.0-1.0)
// 
// Replaced dumb time-counting loop with call to currentTimeInMS
// Added dwrite for when to check (in seconds): dwrite MailWatch TimeToCheck
//
// Obliterated random color change
// Switched to Hue-Saturation-Brightness color method
// Now Hue is scaled 0.0-1.0 proportional amount of mail on 0-100KB
//
// Version 1.2
// 
// Added "magic" names to list for: "time" "day" "date"
// Version 1.3
// Added cool new pref. panel for defaults
// Added "hostname" option
// Added Energy adjustment
// Ya' know, this is just getting to different for minor indices...
//
//
// Version 2.0
//
// Restructuring of pretty much everything.
//
// 2/3/96	Fixed bug in initUsers: which caused an an unitialized user to be
//			created if the NameList was empty
//			Added check for MAXU in addName: to avoid writing beyond bounds
//			of users[].  Now addName: returns -1 if full.
//
// 2/4/96   Fixed assignment of BOOL users[TAG].active in initFrame: so that
//          BOOL is assigned 0 or 1 rather than strcmp("YES", "NO")
//
//	2/7/96	Moved default loading to sizeTo: because BackSpace's "All"
//			setting doesn't call initFrame: !!!
//
//	2/24/96	Added check for presence of Help.store in calling program.
//			If application doesn't support NeXTstep help (like Fiend.app),
//			don't try to load help supplement, hence avoiding annoying 
//			"Fiend Doesn't support NeXTstep Help" panel.  Instead, use
//			Edit.app to display help files
//				


#import "MailWatchView.h"
#import <appkit/appkit.h>
#import <strings.h>
#import <appkit/Font.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <defaults/defaults.h>
#import <math.h>
#import <time.h>
#import <dpsclient/wraps.h>
#import <appkit/FontManager.h>
#include <pwd.h>
#import "Thinker.h"

@implementation MailWatchView

- checkMail:(int)tag
{
	struct stat fileinfo;
	char path[100];
	int size;
  	time_t tTime;
  	struct tm *currentTime;
	
	
	if (tag >= NAMETAGSTART) {
								// Construct full path
		strcpy(path, MailDirectory);
		strcat(path, "/");
		strcat(path, users[tag].name);
									// Get file info check size
		if ( (stat(path, &fileinfo) < 0)
			|| ((size=fileinfo.st_size) == 0) ) {
			users[tag].HasMail = NO;
		}
	
		else {
			users[tag].HasMail = YES;
			
			if (BW) {
				users[tag].hue = 1.0;
			}
			else {
								// Change color to indicate amount of Mail 
				users[tag].hue = (double) size / (double)(MaxMail * 1000);
				if (users[tag].hue > 1.0) users[tag].hue = 1.0;
			}
		}
	}
						/* Time */
	else if (tag == TIMETAG) {
  		time(&tTime);
  		currentTime= localtime(&tTime);
  		strftime(users[tag].name, 30, "%H:%M", currentTime);
		
		if (BW) users[tag].hue = 1.0;
		else users[tag].hue = (float) (currentTime->tm_min) / 60.0;
	}
						/* Day */
	else if (tag == DAYTAG) {
  		time(&tTime);
  		currentTime= localtime(&tTime);

  		strftime(users[tag].name, 10, "%A", currentTime);
		if (BW) users[tag].hue = 1.0;
		else users[tag].hue = (float) (currentTime->tm_wday) / 7.0;

	}

						/* Date */
	else if (tag == DATETAG) {
  		time(&tTime);
  		currentTime= localtime(&tTime);

  		strftime(users[tag].name, 10, "%m/%d", currentTime);
		if (BW) users[tag].hue = 1.0;
		else users[tag].hue = (float) (currentTime->tm_mday) / 31.0;
	}
							/* Host */
	else if (tag == HOSTTAG) {
 		users[tag].hue += (double)Energy/1.e6;	
		if (users[tag].hue > 1.0) users[tag].hue -= 1.0;
	}

	users[tag].rect.size.width 
		= [NameFont[(int)users[tag].HasMail] getWidthOf:users[tag].name];  	

	users[tag].rect.size.height
		= [NameFont[(int)users[tag].HasMail] pointSize];  
		
	users[tag].mass 
		= (float) (users[tag].rect.size.height * users[tag].rect.size.width);					

	return self;
}


// Elastic collision between two point particles.
// Recoil velocities are calculated by simultaneously solving:
//		m1v1 + m2v2 = m1v1' + m2v2' (momentum conservation)
// and
// 		m1 v1^2 + m2 v2^2 = m1 (v1')^2 + m2 (v2')^2 (energy conservation)
//
- collide:(int)i1:(int)i2
{
	float newv1, newv2;
	float m1, m2, v2, v1;


	m1 = users[i1].mass;
	m2 = users[i2].mass;

	v1 = users[i1].v.x;
	v2 = users[i2].v.x;

	newv1 = v2 * 2.0 * m2 / (m2 + m1) - v1 * (m2 - m1) / (m2 + m1);
	newv2 =  v2 * (m2 - m1) / (m2 + m1) + v1 * 2.0 * m1 / (m2 + m1);

	users[i1].v.x = newv1;
	users[i2].v.x = newv2;

	v1 = users[i1].v.y;
	v2 = users[i2].v.y;

	newv1 = v2 * 2.0 * m2 / (m2 + m1) - v1 * (m2 - m1) / (m2 + m1);
	newv2 =  v2 * (m2 - m1) / (m2 + m1) + v1 * 2.0 * m1 / (m2 + m1);

	users[i1].v.y = newv1;
	users[i2].v.y = newv2;

	return self;
}


- move:(int)u
{
	int i;

										// Take a step 
	NXOffsetRect(&users[u].rect, users[u].v.x, users[u].v.y);

							// Check for Elastic Collisions
	for (i=0; i < numusers; ++i) {
		if (i == u) continue;
		if (!users[i].active) continue;
		if (NXIntersectsRect(&users[i].rect,&users[u].rect)) {
			NXOffsetRect(&users[u].rect, -1.0*users[u].v.x, -1.0*users[u].v.y);
			[self collide:i:u]; 
			NXOffsetRect(&users[u].rect, users[u].v.x, users[u].v.y);
		}
	}


							// Specular reflection from walls
	if ( (users[u].rect.origin.x <= bounds.origin.x) 
		|| ((users[u].rect.origin.x+users[u].rect.size.width) >= bounds.size.width) ) {
		NXOffsetRect(&users[u].rect, -2.0*users[u].v.x, 0.0);
   		users[u].v.x *= -1.0;
	}
		
  	if ( ((users[u].rect.origin.y-users[u].rect.size.height) <= bounds.origin.y)
		|| (users[u].rect.origin.y >= bounds.size.height) ) {
		NXOffsetRect(&users[u].rect, 0.0, -2.0 * users[u].v.y);
   		users[u].v.y *= -1.0;
	}


	return self;
}

- oneStep
{
	int i;
	BStimeval CurrentTime;
	int checkIt=0;

							// Check for Mail if it's time
	CurrentTime = currentTimeInMs();
	if ((CurrentTime - LastTime) > TimeToCheck) {
		checkIt = 1;
		LastTime = CurrentTime;
	}

	for (i=0; i < numusers ; ++i) {
		if (!users[i].active) continue;

								// Erase last iteration
		[self eraseName:i];
 
 		if (checkIt) [self checkMail:i];
		
							// Move as necessary
		[self move:i]; 

		[self drawName:i];
	}

	return self;
}

- eraseName: (int)u
{
 		PSsetgray(0.0);
      	PSmoveto(users[u].rect.origin.x,users[u].rect.origin.y);
		
		[NameFont[(int)users[u].HasMail] set];
		PSshow(users[u].name);  

		return self;
}

- drawName: (int) tag
{
 						// Set Color
      	if (users[tag].HasMail) {
			if (BW) PSsetgray(users[tag].hue);
			else PSsethsbcolor(users[tag].hue, 1.0, 1.0);		
		}

		else {
			PSsetgray(Gray);
		}
		[NameFont[(int)users[tag].HasMail] set];

							// Draw this iteration
      	PSmoveto(users[tag].rect.origin.x,users[tag].rect.origin.y);
		PSshow(users[tag].name);  
		
		return self;
}


- (const char *)windowTitle
{
  return "MailWatch";
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	if (!rects || !rectCount) return self;
	PSsetgray(0);
	NXRectFill(rects);
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	
//	if (!alreadyInitialized) [self initialize];
		int fontsize;
	static NXDefaultsVector MailWatchDefaults = {
			{"NameList", ""}, 
			{"MailDirectory", "/usr/spool/mail"},
			{"Gray", "0.3"},
			{"MaxMail", "100"},
			{"TimeToCheck", "30"},
			{"NoMailFont", "Times-Roman"},
			{"MailFont", "Times-Italic"},		
			{"NoMailFontSize", "24"},
			{"MailFontSize", "24"},
			{"Time", "YES"},
			{"Date", "YES"},
			{"Day", "YES"},
			{"Host", "YES"},
			{"Energy", "10000"},
			{NULL} };

	[super sizeTo:width :height];

//	Load Defaults:

	NXRegisterDefaults("MailWatch", MailWatchDefaults);

	strncpy(NameList, NXGetDefaultValue("MailWatch", "NameList"), 1000);

	users[TIMETAG].active 
		= (strcmp(NXGetDefaultValue("MailWatch","Time"),"YES") == 0);
	users[DAYTAG].active 
		= (strcmp(NXGetDefaultValue("MailWatch","Day"),"YES") == 0);
	users[DATETAG].active 
		= (strcmp(NXGetDefaultValue("MailWatch","Date"),"YES") == 0);
	users[HOSTTAG].active 
		= (strcmp(NXGetDefaultValue("MailWatch","Host"),"YES") == 0);


	strncpy(MailDirectory, NXGetDefaultValue("MailWatch", "MailDirectory"), 100);

	Gray = atof(NXGetDefaultValue("MailWatch","Gray"));
	MaxMail = atoi(NXGetDefaultValue("MailWatch","MaxMail"));

	fontsize = atoi(NXGetDefaultValue("MailWatch", "MailFontSize"));
  	NameFont[YESMAIL] 
		= [Font newFont:NXGetDefaultValue("MailWatch", "MailFont") 
		size:fontsize];

	fontsize = atoi(NXGetDefaultValue("MailWatch", "NoMailFontSize"));
  	NameFont[NOMAIL]
		 = [Font newFont:NXGetDefaultValue("MailWatch", "NoMailFont") 
		size:fontsize];	
	
	Energy = atoi(NXGetDefaultValue("MailWatch", "Energy"));
	
	TimeToCheck = 1000 * atoi(NXGetDefaultValue("MailWatch","TimeToCheck"));
	LastTime = 0;

//	
  	[self setFlipped:YES];

//	[inspectorPanel display];

// Determine whether to use color or BW
	if ([Window defaultDepthLimit] == NX_TwoBitGrayDepth) BW = 1;

//	alreadyInitialized = YES;
	[self initUsers];
	return self;
}

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	[self allocateGState];		// For faster lock/unlockFocus
	
//	[self initialize];

	return self;
}


- initUsers
{
	char *pn, *un, name[30];
	BOOL done;
	int tag;
	
	numusers = 0;
	for (tag=0; tag < NAMETAGSTART; ++tag) {
		sprintf(name, "tag%d", tag);
		tag = [self addName:name];
	}

	pn = NameList;
	done = NO;
	while (!done) {
		while (*pn == ' ') ++pn;
		un = name;
		while(*pn != ' ') {
			if (*pn == 0) {
				done = YES;
				break;
			}
			*un++ = *pn++;
		}
		*un = 0;
		if (*name != 0)	{
			tag = [self addName:name];
			if (tag < 0) return self;
			users[tag].active = YES;
		}
	}	
	
	return self;
}


//- (BOOL) useBufferedWindow{	return YES;}

- inspector:sender
{
    char buf[MAXPATHLEN+1];
	int tag;



    if (!inspectorPanel) {
							// Load NIB
		[NXBundle 
			getPath:buf 
			forResource:"MailWatchView" 
			ofType:"nib" 
			inDirectory:[(BSThinker()) moduleDirectory:"MailWatch"]
			withVersion:0
		];

		[NXApp 
			loadNibFile:buf 
			owner:self 
			withNames:NO
		];

							// Load Help
		if (helpPanel == nil) {
							// Check for NeXTstep help system
			if ([NXBundle 
				getPath:buf 
				forResource:"Help" 
				ofType:"store" 
				inDirectory:[(BSThinker()) appDirectory]
				withVersion:0
			]) {
				helpPanel = [NXHelpPanel new];	
				sprintf(buf, "%s/English.lproj",
					[(BSThinker()) moduleDirectory:"MailWatch"]);
				[helpPanel addSupplement:"Help" inPath:buf];
			}
		}
				
				
		// Initialization
							// Settings Panel
							// Energy
		[EnergySlider setIntValue:(int)sqrt((double)Energy)];
							// Checking for Mail
		[MailDirectoryText setStringValue:MailDirectory];
		[TimeToCheckSlider setIntValue:(TimeToCheck/1000)];
		[TimeToCheckText setIntValue:(TimeToCheck/1000)];
							// Colors
		[GraySlider setFloatValue:Gray];
		sprintf(buf, "%3.1f", Gray);
		[GrayText setStringValue:buf];
		[MaxMailSlider setIntValue:MaxMail];
		[MaxMailText setIntValue:MaxMail];
		
							// NameList
							// NameBrowser
		[NameBrowser setDelegate:self];
		[NameBrowser setDoubleAction:@selector(browserChanged:)];
		[NameBrowser setTarget:self];
							// NameButtons
		for (tag=0; tag < NAMETAGSTART; ++tag) {
			[[NameButtons findCellWithTag:tag] setState:users[tag].active];
		}

    }

    return inspectorPanel;
}


- setEnergy:sender
{
	char buf[10];
	int i;
	double Eold;
	double factor;
	
	Eold = Energy;
	Energy = [sender intValue] * [sender intValue];

	sprintf(buf, "%d", Energy);
	NXWriteDefault("MailWatch", "Energy", buf);

	factor = sqrt(Energy/Eold);
	
	for (i=0; i < numusers; ++i) {
		users[i].v.x *= factor;
		users[i].v.y *= factor;
	}
	return self;
}

- setTimeToCheck:sender
{
	char buf[10];
	int Secs;
	
	Secs = [sender intValue];
	sprintf(buf, "%d", Secs);
	NXWriteDefault("MailWatch", "TimeToCheck", buf);
	[TimeToCheckSlider setIntValue:Secs];
	[TimeToCheckText setIntValue:Secs];

	TimeToCheck = Secs * 1000;  	/* in milliseconds */

	return self;
}

- clearNames
{
	int i;
	id myView;
	
	[self lockFocus];
	myView = [(BSThinker()) backView];
	for (i=0; i < numusers; ++i) [myView eraseName:i];
	[self unlockFocus];
	
	return self;
}


- NameButtonsChanged:sender
{
	id selectedButton;
	int tag;
	BOOL State;
	
	selectedButton = [sender selectedCell];
	State = [selectedButton state];
	tag = [selectedButton tag];
	
	users[tag].active = State;

	NXWriteDefault("MailWatch", [selectedButton title], State ? "YES" : "NO");	

	[self clearNames];

	return self;
}

- browserChanged:sender
{
	id cell;
	char title[20];
	int tag;
	
	cell = [sender  selectedCell];
	strcpy(title, [cell stringValue]);
		
	tag = [self addName:title];
	
	if (tag < 0) return self;
	
	if (users[tag].active) {
		users[tag].active = NO;
		[cell setImage:[[NXImage findImageNamed:"CheckOff"] copy]];
	}
	else {
		users[tag].active = YES;
		[cell setImage:[[NXImage findImageNamed:"CheckOn"] copy]];
	}

	[sender displayAllColumns];
	
	*NameList = 0;
	for (tag=NAMETAGSTART; tag < numusers; ++tag)
		if (users[tag].active)
			sprintf(NameList, "%s %s", NameList, users[tag].name);
	NXWriteDefault("MailWatch", "NameList", NameList);
	
						// Don't leave shadows 
	[self clearNames];

	return self;
}


- setMailDirectory:sender
{	
	strncpy(MailDirectory, [sender stringValue], 100);
	NXWriteDefault("MailWatch", "MailDirectory", MailDirectory);
	return self;
}

-(int)browser:sender fillMatrix:(id)matrix inColumn:(int)col
{
	struct passwd *pwentry;
	int row;
	id newCell;
	int tag;

	if (!CheckOn || !CheckOff) [self initImages];
	
	setpwent();
	for (row=0; (pwentry = getpwent()) != NULL;) {
		if (!strcmp(pwentry->pw_name, "uucp")) continue;
		if (!strcmp(pwentry->pw_name, "nobody")) continue;
		if (!strcmp(pwentry->pw_name, "agent")) continue;
		if (!strcmp(pwentry->pw_name, "daemon")) continue;
		if (!strcmp(pwentry->pw_name, "sybase")) continue;
		
		[matrix addRow];

		newCell = [matrix cellAt:row:col];
		
		[newCell initTextCell:pwentry->pw_name];
		[newCell setLoaded:YES];
		[newCell setLeaf:YES];
		
		for (tag=NAMETAGSTART; tag < numusers; ++tag) {
			if (!strcmp(users[tag].name, pwentry->pw_name)) break;
		}
		if (tag != numusers) 
			[newCell setImage:[[NXImage findImageNamed:"CheckOn"] copy]];
		else 
			[newCell setImage:[[NXImage findImageNamed:"CheckOff"] copy]];
		++row;
	}
	endpwent();
	

	return(row);
}

- initImages
{
	CheckOn = [NXImage findImageNamed:"CheckOn"];
	CheckOff = [NXImage findImageNamed:"CheckOff"];
	return self;

	return self;
}

								// Returns tag of name
								// Creating new entry if necessary
- (int)addName:(char *)newName
{
	BOOL bad;
	int j;
	double v, vx, vy;
	int tag;
	
					// Check to see if it already exists
	for (tag=0; tag < numusers; ++tag) {
		if (!strcmp(users[tag].name, newName)) return tag;
	}
	
					// Check to make sure we've got room
	if (numusers >= MAXU) return(-1);
		
					// Gotta create it
//	fprintf(stderr, "Creating Name %s\n", newName);
	strcpy(users[tag].name, newName);
	
	if (tag < NAMETAGSTART) {
		users[tag].HasMail = YES;
		if (tag == HOSTTAG) gethostname(users[HOSTTAG].name, NAMELEN);
	}
	else {
		users[tag].active = NO;
	}

	[self checkMail:tag];

//	Find an unoccupied place to put it:
	bad = YES;
	while (bad) {
		users[tag].rect.origin.x = 
			randBetween(bounds.origin.x, 
			bounds.size.width-users[tag].rect.size.width);
		users[tag].rect.origin.y = 
			randBetween(bounds.origin.y+users[tag].rect.size.height, 
			bounds.size.height-users[tag].rect.size.height);

		for (bad=NO,j=tag-1; j >= 0; --j) {
			if (NXIntersectsRect(&users[j].rect, &users[tag].rect))
			bad = YES;
		}							
	}
			
	v = sqrt(2 * Energy / users[tag].mass);

 	vx = randBetween(0, v);
  	vy = sqrt (v*v - vx * vx);

	users[tag].v.x = vx;
	users[tag].v.y = vy;	
	
	++numusers;
	
	return tag;

}

- inspectorWillBeRemoved
{
	if (SettingsPanel) [SettingsPanel orderOut:self];
	if (NameListInspector) [NameListInspector orderOut:self];
	if (InfoPanel) [InfoPanel orderOut:self];
	if (fontPanel) [fontPanel orderOut:self];
	if (helpPanel) [helpPanel orderOut:self];

	return self;
}

- helpPushed:sender
{
	char HelpPath[MAXPATHLEN + 1];

	sprintf(HelpPath, "%s/English.lproj/Help/Overview.rtfd", 
			[(BSThinker()) moduleDirectory:"MailWatch"]);

							// If NeXTstep help is loaded, use it
	if (helpPanel) {
		[helpPanel makeKeyAndOrderFront:self];
		[helpPanel showFile:HelpPath atMarker:NULL];
	}
							// Otherwise, use Edit.app
	else {
		[[Application workspace] openFile:HelpPath withApplication:"Edit"];
	}
	
	return self;
}

- fontPushed:sender
{	
	if (fontManager == nil) fontManager = [FontManager new];
	
	[fontManager setSelFont:NameFont[(int)settingMailFont] isMultiple:NO];

	if (fontPanel == nil) {
		fontPanel = [fontManager getFontPanel:YES];
		[fontPanel setAccessoryView:fontPopUpView];
	}

	[fontPanel setDelegate:self];
	[fontPanel makeKeyAndOrderFront:self];
	return self;
}

- changeFont:sender
{
	char buf1[20], buf2[20];


	[self clearNames];
	NameFont[(int)settingMailFont] 
		= [fontManager convertFont:NameFont[(int)settingMailFont]];

							/* Either "MailFont" or "NoMailFont" */
	strcpy(buf1, [[fontPopUp findCellWithTag:(int)settingMailFont] title]);
	NXWriteDefault("MailWatch", buf1, [NameFont[(int)settingMailFont] name]);

							/* Make that "MailFontSize" or "NoMailFontSize" */
	strcat(buf1, "Size");
	sprintf(buf2, "%g", [NameFont[(int)settingMailFont] pointSize]);
	NXWriteDefault("MailWatch", buf1, buf2);		


	return self;
}

- fontPopUpChanged:sender
{
	settingMailFont = [[sender selectedCell] tag];
	[fontManager setSelFont:NameFont[(int)settingMailFont] isMultiple:NO];

	return self;
}

- setGray:sender
{
	char buf[10];
	
	Gray = [sender floatValue];
	
	sprintf(buf, "%3.1f", Gray);
	
	[GrayText setStringValue:buf];
	
	NXWriteDefault("MailWatch", "Gray", buf);

	return self;

}

- setMaxMail:sender
{
	char buf[30];
	
	MaxMail = [sender intValue];
	
	[MaxMailText setIntValue:MaxMail];
	sprintf(buf, "%d", MaxMail);
	NXWriteDefault("MailWatch", "MaxMail", buf);

	return self;
}

@end
