//
//  MiscSerialPort.m -- Serial port I/O support
//          Written by W. Eric Norum Copyright (c) 1997 by W. Eric Norum.
//          $Revision: 2.1 $   $Date: 1997/04/02 15:32:59 $
//          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 "MiscSerialPort.h"
#import <libc.h>
#import <string.h>
#import <errno.h>
#import <sys/fcntl.h>
#import <sys/ioctl.h>
#ifdef USE_TERMIOS
#import <termios.h>
#endif

NSString *MiscSerialPortReadCompletionNotification = @"MiscSerialPortReadCompletionNotification";
NSString *MiscSerialPortNotificationDataItem;

@implementation MiscSerialPort

+ (void)initialize
{
	MiscSerialPortNotificationDataItem = NSFileHandleNotificationDataItem;
}

- (NSString *)errorString
{
	return [[errorString copy] autorelease];
}

- (void)_setErrorString:(NSString *)format, ...
{
	va_list args;
	
	va_start (args, format);
	[errorString autorelease];
	errorString = [[NSString alloc] initWithFormat:format arguments:args];
	va_end (args);
}
	
- (void)_serialDataArrived:(NSNotification *)notification
{
	/*
	 * Pass on the notification
	 */
	[[NSNotificationCenter defaultCenter]
				postNotificationName:MiscSerialPortReadCompletionNotification
				object:self
				userInfo:[notification userInfo]];

	/*
	 * Continue reading
	 */
	[fileHandle readInBackgroundAndNotify];
}

- (BOOL)setParameters
{
	int ratecode;
#ifdef USE_TERMIOS
	struct termios termios;
#else
	struct sgttyb sgttyb;
	struct tchars tchars;
	struct ltchars ltchars;
	int linediscipline, localmode;
#endif

	switch (baud) {
	default:	return NO;
	case 38400:	case EXTB:		ratecode = EXTB;	break;
	case 19200:	case EXTA:		ratecode = EXTA;	break;
	case 9600:	case B9600:		ratecode = B9600;	break;
	case 4800:	case B4800:		ratecode = B4800;	break;
	case 2400:	case B2400:		ratecode = B2400;	break;
	case 1200:	case B1200:		ratecode = B1200;	break;
	case 300:	case B300:		ratecode = B300;	break;
	}
#ifdef USE_TERMIOS
	switch (characterLength) {
	case 5: termios.c_cflag = CS5; break;
	case 6: termios.c_cflag = CS6; break;
	case 7: termios.c_cflag = CS7; break;
	case 8: termios.c_cflag = CS8; break;
	default:
		[self _setErrorString:@"Illegal character length"];
		return NO;
	}
	switch (parity) {
	case MiscSerialPortNoParity:
		break;

	case MiscSerialPortEvenParity:
		termios.c_cflag |= PARENB;
		break;

	case MiscSerialPortOddParity:
		termios.c_cflag |= PARENB | PARODD;
		break;

	default:
		[self _setErrorString:@"Illegal parity type"];
		return NO;
	}
	termios.c_cflag |= CREAD | HUPCL;
	termios.c_iflag = 0;
	termios.c_oflag = 0;
	termios.c_lflag = 0;
	termios.c_cc[VMIN] = 10;
	termios.c_cc[VTIME] = 1;
	cfsetispeed (&termios, ratecode);
	cfsetospeed (&termios, ratecode);
	if (tcsetattr (fd, TCSADRAIN, &termios) < 0) {
#else
	if (characterLength != ((parity == MiscSerialPortNoParity) ? 8 : 7)) {
		[self _setErrorString:@"Illegal combination of parity type and character length"];
		return NO;
	}
	linediscipline = OTTYDISC;
	sgttyb.sg_ispeed = sgttyb.sg_ospeed = ratecode;
	sgttyb.sg_erase = sgttyb.sg_kill = -1;
	switch (parity) {
	case MiscSerialPortNoParity:
		sgttyb.sg_flags = RAW;
		break;

	case MiscSerialPortEvenParity:
		sgttyb.sg_flags = EVENP | CBREAK;
		break;

	case MiscSerialPortOddParity:
		sgttyb.sg_flags = ODDP | CBREAK;
		break;

	default:
		[self _setErrorString:@"Illegal parity type"];
		return NO;
	}
	tchars.t_intrc = tchars.t_quitc = tchars.t_eofc = -1;
	tchars.t_startc = tchars.t_stopc = tchars.t_brkc = -1;
	localmode = 0;
	ltchars.t_suspc = ltchars.t_dsuspc = ltchars.t_rprntc = -1;
	ltchars.t_flushc = ltchars.t_werasc = ltchars.t_lnextc = -1;
	if ((ioctl (fd, TIOCSETD, &linediscipline) < 0)
	 || (ioctl (fd, TIOCSETP, &sgttyb) < 0)
	 || (ioctl (fd, TIOCSETC, &tchars) < 0)
	 || (ioctl (fd, TIOCLSET, &localmode) < 0)
	 || (ioctl (fd, TIOCSLTC, &ltchars) < 0)) {
#endif
		[self _setErrorString:@"Can't set `%@' parameters: %s", portName, strerror (errno)];
		return NO;
	}
	return YES;
}

- (BOOL)setCharacterLength:(int)clen parity:(int)par
{
	int oldCharacterLength = characterLength;
	enum MiscSerialPortParity oldParity = parity;

	characterLength = clen;
	parity = par;
	if (![self setParameters]) {
		characterLength = oldCharacterLength;
		parity = oldParity;
		return NO;
	}
	return YES;
}

- initWithPort:(NSString *)aString baud:(int)anInt
{
	fd = open ([aString cString], O_RDWR | O_EXCL);
	if (fd < 0) {
		[self _setErrorString:@"Can't open `%@': %s", aString, strerror(errno)];
		return nil;
	}
	if (ioctl (fd, TIOCEXCL, NULL) < 0) {
		[self _setErrorString:@"Can't make `%@' exclusive-use: %s", portName, strerror (errno)];
		close (fd);
		return nil;
	}
	baud = anInt;
	characterLength = 8;
	parity = MiscSerialPortNoParity;
	if (![self setParameters]) {
		close (fd);
		return nil;
	}
	isOpen = YES;
	portName = [aString copy];
	fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
	[fileHandle readInBackgroundAndNotify];
	[[NSNotificationCenter defaultCenter] addObserver:self
									selector:@selector(_serialDataArrived:)
									name:NSFileHandleReadCompletionNotification
									object:fileHandle];
	return self;
}

- (void)dealloc
{
	if (isOpen) {
		[[NSNotificationCenter defaultCenter] removeObserver:self
									name:NSFileHandleReadCompletionNotification
									object:fileHandle];
		[portName release];
		[fileHandle release];
	}
	[errorString release];
	[super dealloc];
}

- (BOOL)_writeRawBuffer:(const void *)buf length:(unsigned int)length
{
	if (write (fd, buf, length) < 0) {
		[self _setErrorString:@"Write failed: %s", strerror (errno)];
		return NO;
	}
	return YES;
}

- (BOOL)writeData:(NSData *)data
{
	return [self _writeRawBuffer:[data bytes] length:[data length]];
}
	
- (BOOL)writeString:(NSString *)aString
{
	return [self _writeRawBuffer:[aString cString] length:[aString length]];
}

@end
