//
// Distributor.m
// LGDCommunicationKit Examples, Release 1.0 (prerelease)
//
// Copyright (c) 1993, Looking Glass Design, Inc.
// All rights reserved.
//
// Project Manager:  Paul Girone
// Original Author:  Mike Gobbi
// Creation Date:    June 10, 1993
// $Revision: 1.3 $
//

#import "Distributor.h"
#import <appkit/Application.h>
#import <appkit/ButtonCell.h>
#import <appkit/Matrix.h>
#import <appkit/Menu.h>
#import <assert.h>
#import <errno.h>
#import <lgd/commkit/LGDAppSerialPort.h>
#import <libc.h>
#import <pwd.h>
#import <signal.h>
#import "TextExtensions.h"

#define POPUP_STRING(button) [button title]

static id port;
void hangup(void)
{
	// close the connection
	if ([port isOpen]) {
		[port close];
	}
	
	// unlock the device
	if ([port isLocked]) {
		[port unlock];
	}
}

void cleanup(void)
{
	// ensure that we are hung up, then die
	hangup();
	exit(1);
}

@implementation Distributor

// Status

- displayStatus:sender
{
	NXRunAlertPanel(NULL, "%s\ndevice %s (fd %d) is %s and %s, errno=%d",
		NULL, NULL, NULL,
		[serialPort statusString],
		[serialPort device],
		[serialPort _fileDescriptor],
		[serialPort isLocked] ? "locked" : "unlocked",
		[serialPort isOpen] ? "open" : "closed",
		errno);
	return self;
}

// Communication

- send:sender
{
	int result;
	char buffer[20];
	
	// either treat field as integer...
	if ([byteSwitch state]) {
		result = [serialPort sendCharacter:[inputField intValue]];
		sprintf(buffer, "<%d>", [inputField intValue]);
		[outgoingField appendString:buffer addNewline:YES];
	
	// ...or else treat field as string
	} else {
		result = [serialPort sendString:[inputField stringValue]
			withTrailer:"\r"];
		[outgoingField appendString:[inputField stringValue]
			addNewline:YES];
	}
	
	// check for errors
	if (result<0) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	
	// tidy up the GUI
	[outgoingField scrollSelToVisible];
	[inputField selectText:self];
	
	return self;
}

- poll:sender
{
	char buffer[80];
	int count;
	int character;
	
	// either display input as integers...
	if ([byteSwitch state]) {
		for (count=[serialPort getCharacter:&character];
			count > 0;
			count=[serialPort getCharacter:&character]) {
			
			assert(character!=EOF);
			sprintf(buffer, "<%d>", character);
			[incomingField appendString:buffer addNewline:YES];
			[incomingField scrollSelToVisible];
		}
	
	// ... or else display as a string
	} else {
		for (count=[serialPort getString:buffer
				max:80
				termChars:"\n\r"];
			count > 0;
			count=[serialPort getString:buffer
				max:80
				termChars:"\n\r"]) {
			
			[incomingField appendString:buffer addNewline:NO];
			[incomingField scrollSelToVisible];
		}
	}
	
	// check for errors
	if (count!=0) {
	
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
		
		// don't keep trying if we had an error
		if ([serialPort action]){
			[autoPollSwitch performClick:self];
		}
	}
	
	// tidy up the GUI
	// we want to use a delayed call because in auto-polling
	// the continual flicker as multiple lines are polled looks
	// bad.  The 1/2 second delay will ensure that no reselection
	// takes place during multiple-poll operations.
	[inputField
		perform:@selector(selectText:)
		with:self
		afterDelay:0.5
		cancelPrevious:YES];
	return self;
}

- peek:sender
{
	BOOL flag;
	
	// determine whether there is any data
	flag = [serialPort dataAvailable];
	
	// notify the user
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	
	// or display error message
	} else {
		NXRunAlertPanel(NULL, "There is %s data ready to be read",
			NULL, NULL, NULL,
			flag ? "new" : "no");
	}
	
	return self;
}

- clearTextFields:sender
{
	[[incomingField setText:""] selectText:self];
	[[outgoingField setText:""] selectText:self];
	[inputField selectText:self];
	return self;
}

// Buffers

- flushInput:sender
{
	if (![serialPort flushInput]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

- flushOutput:sender
{
	if (![serialPort flushOutput]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

- flushAll:sender
{
	if (![serialPort flush]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

// Settings

- takeCRModFrom:sender
{
	id cell = [sender isKindOf:[Cell class]]
		? sender : [sender selectedCell];
	
	[serialPort setCRMassage:[cell state]];
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

- takeDuplexFrom:sender
{
	id cell = [sender isKindOf:[Cell class]]
		? sender : [sender selectedCell];
	
	// confirm that this rather foolish course of action
	// is in fact desired.
	if ([cell state] && NXRunAlertPanel("Feedback warning",
		"If your modem is set to echo, then setting the "
		"port to echo will result in the next character "
		"you send bouncing back and forth forever, and "
		"you will have to send this process a HUP signal "
		"to regain control.", "Cancel", "Ok", NULL)) {
		
		// turn off switch if the user changed their mind
		[cell perform:@selector(setState:)
			with:(id)(int)NO
			afterDelay:0
			cancelPrevious:YES];
		[window perform:@selector(display)
			with:nil
			afterDelay:0
			cancelPrevious:YES];
		return self;
	}
	
	// if they really do want to, then do it
	[serialPort setDuplex:[cell state]
		? LGD_SP_FULLDUPLEX : LGD_SP_HALFDUPLEX];
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

- takeAutoPollFrom:sender
{
	id cell = [sender isKindOf:[Cell class]]
		? sender : [sender selectedCell];
	
	// we set the target in awakeFromNib, so we need only
	// set the action here
	[serialPort setAction:[cell state]
		? @selector(poll:) : NULL];
	return self;
}

- takeModeFrom:sender
{
	id cell = [sender isKindOf:[Cell class]]
		? sender : [sender selectedCell];
	
	if (![serialPort isOpen]) {
		return self;
	}
	
	[serialPort setMode:[cell tag]];
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

// Signals

- raiseDTR:sender
{
	[serialPort raiseDTR];
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

- lowerDTR:sender
{
	[serialPort lowerDTR];
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

- raiseBRK:sender
{
	[serialPort raiseBreak];
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

- lowerBRK:sender
{
	[serialPort lowerBreak];
	if ([serialPort status]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	return self;
}

// Application delegate methods

- appDidInit:anApplication
{
	char *username;
	int signals[] = {
		SIGINT, SIGQUIT, SIGBUS, SIGSEGV, SIGTERM, 0
	};
	int i;
	
	// set ourself up to die nicely no matter what happens
	// (well, almost whatever.  We can't catch KILL)
	port = serialPort;
	signal(SIGHUP, (void*)hangup);
	for (i=0; signals[i]; i++) {
		signal(signals[i], (void *)cleanup);
	}
	
	// check uid
	username = getpwuid(geteuid())->pw_name;
	if (strcmp(username, "root") && strcmp(username, "uucp")) {
		NXRunAlertPanel("Process Owner", "WARNING: %s. %s",
			NULL, NULL, NULL,
			"Not running as root or uucp",
			"Permission errors are likely");
	}
	
	return self;
}

- appWillTerminate:anApplication
{
	// try the orderly shutdown first
	if ([serialPort isOpen] && ![serialPort unlockAndClose]) {
		NXRunAlertPanel(NULL, "%s",
			NULL, NULL, NULL,
			[serialPort statusString]);
		[serialPort clearStatus];
	}
	
	// freeing the serial port is the best way to ensure that
	// it is disconnected properly if the orderly shutdown failed
	[serialPort free];
	
	return self;
}

// SerialPort delegate methods

- serialPortWillOpen:aPort
{
	const char *device = POPUP_STRING(devicePopup);
	[aPort setDevice:device];
	return self;
}

- serialPortDidOpen:aPort
{
	[self takeModeFrom:modeMatrix];
	[self takeCRModFrom:CRModSwitch];
	[self takeDuplexFrom:duplexSwitch];
	[sendButton setEnabled:YES];
	[pollButton setEnabled:YES];
	[peekButton setEnabled:YES];
	[devicePopup setEnabled:NO];
	[inputField selectText:self];
	
	return self;
}

- serialPortDidClose:aPort
{
	[sendButton setEnabled:NO];
	[pollButton setEnabled:NO];
	[peekButton setEnabled:NO];
	[devicePopup setEnabled:YES];
	
	return self;
}

// Archiving

- awakeFromNib
{
	[sendButton setEnabled:NO];
	[pollButton setEnabled:NO];
	[peekButton setEnabled:NO];
	
	return self;
}

@end
