/*
**	$Id: Serial.c,v 1.4 92/04/21 16:59:38 olsen Sta Locker: olsen $
**	$Revision: 1.4 $
**	$Date: 92/04/21 16:59:38 $
**
**	Serial driver support routines
**
**	Copyright © 1990-1992 by Olaf `Olsen' Barthel & MXM
**		All Rights Reserved
*/

#include "termGlobal.h"

	/* Include the OwnDevUnit definitions. */

#include "OwnDevUnit.h"

	/* Local copy of serial driver name and unit number. */

STATIC UBYTE	SerialDevice[40];
STATIC LONG	UnitNumber = -1;

	/* XOn():
	 *
	 *	Perform XON (stop data flow).
	 */

VOID
XOn()
{
	if(Config . Handshaking == HANDSHAKING_XONXOFF)
		Status = STATUS_HOLDING;
}

	/* SetFlags(struct IOExtSer *SomeRequest):
	 *
	 *	Set the contents of a serial device request according
	 *	to the current configuration settings.
	 */

VOID __regargs
SetFlags(struct IOExtSer *SomeRequest)
{
	SomeRequest -> io_Baud		= Config . BaudRate;
	SomeRequest -> io_BrkTime	= Config . BreakLength;
	SomeRequest -> io_ReadLen	= Config . BitsPerChar;
	SomeRequest -> io_WriteLen	= Config . BitsPerChar;
	SomeRequest -> io_StopBits	= Config . StopBits;

	SomeRequest -> io_ExtFlags	&= ~(SEXTF_MSPON|SEXTF_MARK);
	SomeRequest -> io_SerFlags	&= ~(SERF_PARTY_ON|SERF_PARTY_ODD|SERF_7WIRE|SERF_RAD_BOOGIE);

	switch(Config . Parity)
	{
		case PARITY_EVEN:	SomeRequest -> io_SerFlags |= SERF_PARTY_ON;
					break;

		case PARITY_ODD:	SomeRequest -> io_SerFlags |= SERF_PARTY_ON|SERF_PARTY_ODD;
					break;

		case PARITY_MARK:	SomeRequest -> io_SerFlags |= SERF_PARTY_ON;
					SomeRequest -> io_ExtFlags |= SEXTF_MSPON|SEXTF_MARK;
					break;

		case PARITY_SPACE:	SomeRequest -> io_SerFlags |= SERF_PARTY_ON;
					SomeRequest -> io_ExtFlags |= SEXTF_MSPON;
					break;

		default:		break;
	}

	if(Config . Handshaking == HANDSHAKING_RTSCTS || Config . Handshaking == HANDSHAKING_RTSCTS_DSR)
		SomeRequest -> io_SerFlags |= SERF_7WIRE;

	if(Config . HighSpeed & ~SERIAL_SHARED)
		SomeRequest -> io_SerFlags |= SERF_RAD_BOOGIE;

	if(Config . HighSpeed & SERIAL_SHARED)
		SomeRequest -> io_SerFlags |= SERF_SHARED;

	SomeRequest -> io_SerFlags |= SERF_XDISABLED;
}

	/* CallMenu(UBYTE *Name,ULONG Code):
	 *
	 *	Call a menu function either through the name of the corresponding
	 *	menu item or a menu number.
	 */

STATIC VOID __regargs
CallMenu(UBYTE *Name,ULONG Code)
{
	WORD MenuNum = -1,Item = 0,Sub = 0,i;

		/* Are we to look for a name? */

	if(Name)
	{
		WORD Len = strlen(Name);

			/* Scan the menu list... */

		for(i = 0 ; TermMenu[i] . nm_Type != NM_END ; i++)
		{
			switch(TermMenu[i] . nm_Type)
			{
				case NM_TITLE:	MenuNum++;
						Item = Sub = 0;
						break;

				case NM_ITEM:	Sub = 0;
						break;
			}

				/* Did we get a valid name string? */

			if(TermMenu[i] . nm_Label != NM_BARLABEL)
			{
					/* Does the name match our template? */

				if(!Strnicmp(TermMenu[i] . nm_Label,Name,Len))
				{
					struct MenuItem *MenuItem = ItemAddress(Menu,FULLMENUNUM(MenuNum,Item,Sub));

					if(MenuItem)
						HandleCode((ULONG)TermMenu[i] . nm_UserData,NULL,MenuItem);

					break;
				}
			}

			switch(TermMenu[i] . nm_Type)
			{
				case NM_ITEM:	Item++;
						break;

				case NM_SUB:	Sub++;
						break;
			}
		}
	}
	else
	{
		WORD	TheMenu	=  Code % 100,
			TheItem	= (Code / 100) % 100,
			TheSub	=  Code / 10000;

			/* Scan the menu list... */

		for(i = 0 ; TermMenu[i] . nm_Type != NM_END ; i++)
		{
			switch(TermMenu[i] . nm_Type)
			{
				case NM_TITLE:	MenuNum++;
						Item = Sub = 0;
						break;

				case NM_ITEM:	Sub = 0;
						break;
			}

				/* Is it the menu number we want? */

			if(TheMenu == MenuNum && TheItem == Item && TheSub == Sub)
			{
				if(TermMenu[i] . nm_Label != NM_BARLABEL)
				{
					struct MenuItem *MenuItem = ItemAddress(Menu,FULLMENUNUM(MenuNum,Item,Sub));

					if(MenuItem)
						HandleCode((ULONG)TermMenu[i] . nm_UserData,NULL,MenuItem);
				}

				break;
			}

			switch(TermMenu[i] . nm_Type)
			{
				case NM_ITEM:	Item++;
						break;

				case NM_SUB:	Sub++;
						break;
			}
		}
	}
}

	/* SerialCommand(UBYTE *String):
	 *
	 *	Send a command string to the serial line and
	 *	interprete the control sequences.
	 */

VOID __regargs
SerialCommand(UBYTE *String)
{
	LONG	Count = 0,i,Len = strlen(String);

	BYTE	GotControl	= FALSE,
		GotEscape	= FALSE,

		OldStatus,
		BlockSet;

		/* Scan the string. */

	for(i = 0 ; i < Len ; i++)
	{
			/* We are looking for plain characters
			 * and the control ('\') and escape
			 * ('^') characters.
			 */

		if(!GotControl && !GotEscape)
		{
				/* Got a control character,
				 * the next byte will probably be
				 * a command sequence.
				 */

			if(String[i] == '\\')
			{
				GotControl = TRUE;
				continue;
			}

				/* Got an escape character,
				 * the next byte will be some
				 * kind of control character
				 * (such as XON, XOF, bell, etc.).
				 */

			if(String[i] == '^')
			{
				GotEscape = TRUE;
				continue;
			}

				/* This tells us to wait another
				 * second before continuing with
				 * the scanning.
				 */

			if(String[i] == '~')
			{
				if(Count)
				{
					SerWrite(SharedBuffer,Count);

					Count = 0;
				}

				WaitTime(0,MILLION / 2);
				HandleSerial();

				continue;
			}

				/* Stuff the character into the
				 * buffer.
				 */

			SharedBuffer[Count++] = String[i];
		}
		else
		{
				/* Convert the character to a control
				 * style character (^C, etc.).
				 */

			if(GotEscape)
			{
				if(ToUpper(String[i]) >= 'A' && ToUpper(String[i]) <= 91)
					SharedBuffer[Count++] = ToUpper(String[i]) - '@';
				else
					SharedBuffer[Count++] = String[i];

				GotEscape = FALSE;
			}

				/* The next character represents a command. */

			if(GotControl)
			{
				switch(ToUpper(String[i]))
				{
						/* Execute an AmigaDOS command. */

					case 'D':	if(!WeAreBlocking)
							{
								BlockSet = TRUE;

								BlockWindows();
							}
							else
								BlockSet = FALSE;

							SendAmigaDOSCommand(&String[i + 1]);

							if(BlockSet)
								ReleaseWindows();

							return;

						/* Execute an ARexx command. */

					case 'A':	if(!WeAreBlocking)
							{
								BlockSet = TRUE;

								BlockWindows();
							}
							else
								BlockSet = FALSE;

							SendARexxCommand(&String[i + 1]);

							if(BlockSet)
								ReleaseWindows();

							return;

						/* Add the control character ('\'). */

					case '\\':	SharedBuffer[Count++] = '\\';
							break;

						/* This is a backspace. */

					case 'B':	SharedBuffer[Count++] = '\b';
							break;

						/* This is a form feed. */

					case 'F':	SharedBuffer[Count++] = '\f';
							break;

						/* This is a line feed. */

					case 'N':	SharedBuffer[Count++] = '\n';
							break;

						/* Send the current password. */

					case 'P':	if(Password[0])
							{
								if(Count)
								{
									SerWrite(SharedBuffer,Count);

									Count = 0;
								}

								SerWrite(Password,strlen(Password));
							}

							break;

						/* This is a carriage return. */

					case 'R':	SharedBuffer[Count++] = '\r';
							break;

						/* This is a tab. */

					case 'T':	SharedBuffer[Count++] = '\t';
							break;

						/* Send the current user name. */

					case 'U':	if(UserName[0])
							{
								if(Count)
								{
									SerWrite(SharedBuffer,Count);

									Count = 0;
								}

								SerWrite(UserName,strlen(UserName));
							}

							break;

						/* Send a break across the serial line. */

					case 'X':	if(Count)
							{
								SerWrite(SharedBuffer,Count);

								Count = 0;
							}

							if(WriteRequest)
							{
								OldStatus = Status;

								Status = STATUS_BREAKING;

								WriteRequest -> IOSer . io_Command = SDCMD_BREAK;

								DoIO(WriteRequest);

								Status = OldStatus;
							}

							break;

						/* Feed the contents of the
						 * clipboard into the input
						 * stream.
						 */

					case 'I':	if(Count)
								SerWrite(SharedBuffer,Count);

							Count = LoadClip(SharedBuffer,256);

							break;

						/* Send a string to the clipboard. */

					case 'G':	if(String[i + 1])
								SaveClip(&String[i + 1],strlen(&String[i + 1]));

							return;

						/* Produce the escape character. */

					case 'E':	SharedBuffer[Count++] = ESC;
							break;

						/* Call a menu item. */

					case 'C':	i++;

								/* Scan for a menu number or
								 * a single quote...
								 */

							while(i < Len)
							{
								if(String[i] >= '0' && String[i] <= '9')
									break;

								if(String[i] == '\'')
									break;

								if(String[i] != ' ')
									break;

								i++;
							}

							if(i < Len)
							{
								UBYTE DummyBuffer[256];

									/* Did we get a quote? */

								if(String[i] == '\'')
								{
									LONG Start = ++i;

									if(String[Start])
									{
										WORD Length;

										while(i < Len)
										{
											if(String[i] != '\'')
												i++;
											else
												break;
										}

										if(String[i] == '\'')
											Length = i - Start;
										else
											Length = i - Start + 1;

										memcpy(DummyBuffer,&String[Start],Length);

										DummyBuffer[Length] = 0;

										CallMenu(DummyBuffer,0);
									}
								}
								else
								{
									if(String[i] >= '0' && String[i] <= '9')
									{
										LONG Start = i,Length;

										while(i < Len)
										{
											if(String[i] >= '0' && String[i] <= '9')
												i++;
											else
												break;
										}

										if(i == Start)
											Length = 1;
										else
											Length = i - Start;

										memcpy(DummyBuffer,&String[Start],Length);

										DummyBuffer[Length] = 0;

										CallMenu(NULL,atol(DummyBuffer));
									}
								}
							}

							break;

						/* Stuff the character into the buffer. */

					default:	SharedBuffer[Count++] = String[i];
							break;
				}

				GotControl = FALSE;
			}
		}

			/* If the buffer is full, release it. */

		if(Count == 256)
		{
			SerWrite(SharedBuffer,Count);

			Count = 0;
		}
	}

	if(Count)
		SerWrite(SharedBuffer,Count);
}

	/* SerWrite(APTR Buffer,LONG Size):
	 *
	 *	Send a number of bytes across the serial line.
	 */

VOID __regargs
SerWrite(APTR Buffer,LONG Size)
{
	if(WriteRequest && Size)
	{
		if(XProtocolBase)
		{
				/* xpr wants to see the data before it is
				 * transferred.
				 */

			if(TransferBits & XPRS_USERMON)
			{
				if(!(Size = XProtocolUserMon(XprIO,Buffer,Size,Size)))
					return;
			}
		}

			/* If full duplex is enabled, send the entire
			 * buffer.
			 */

		if(Config . Duplex == DUPLEX_FULL)
		{
			WriteRequest -> IOSer . io_Command	= CMD_WRITE;
			WriteRequest -> IOSer . io_Data		= Buffer;
			WriteRequest -> IOSer . io_Length	= Size;

			DoIO(WriteRequest);
		}
		else
		{
			if(XEmulatorBase)
			{
				WriteRequest -> IOSer . io_Command	= CMD_WRITE;
				WriteRequest -> IOSer . io_Data		= Buffer;
				WriteRequest -> IOSer . io_Length	= Size;

				SendIO(WriteRequest);

				ConProcess(Buffer,Size);

				WaitIO(WriteRequest);
			}
			else
			{
				UBYTE *ByteBuffer = Buffer;

					/* Half duplex is enabled, send only
					 * a single character at a time.
					 */

				while(Size--)
				{
					WriteRequest -> IOSer . io_Command	= CMD_WRITE;
					WriteRequest -> IOSer . io_Length	= 1;
					WriteRequest -> IOSer . io_Data		= ByteBuffer;

					SendIO(WriteRequest);

					ConProcess(ByteBuffer,1);

					WaitIO(WriteRequest);

					ByteBuffer++;
				}
			}
		}
	}
}

	/* ClearSerial():
	 *
	 *	Terminate all read/write activity on the serial
	 *	line.
	 */

VOID
ClearSerial()
{
	if(ReadRequest)
	{
		if(!CheckIO(ReadRequest))
			AbortIO(ReadRequest);

		WaitIO(ReadRequest);
	}

	if(WriteRequest)
	{
		WriteRequest -> IOSer . io_Command = CMD_CLEAR;

		DoIO(WriteRequest);
	}
}

	/* DropDTR():
	 *
	 *	Drop the data terminal ready signal (i.e. close, wait a bit
	 *	and then reopen the serial drive).
	 */

BYTE
DropDTR()
{
		/* Finish all serial read activity. */

	ClearSerial();

		/* Do we have any channels to work with? */

	if(ReadRequest && WriteRequest)
	{
			/* Close the device. */

		CloseDevice(ReadRequest);

			/* Wait a bit. */

		WaitTime(1,0);

			/* Set up the original configuration data. */

		SetFlags(ReadRequest);

		ReadRequest -> io_RBufLen = Config . SerBuffSize;

			/* Reopen the driver. */

		if(!OpenDevice(Config . SerialDevice,Config . UnitNumber,ReadRequest,0))
		{
			struct MsgPort *WritePort = WriteRequest -> IOSer . io_Message . mn_ReplyPort;

				/* Fill in the rest. */

			CopyMem(ReadRequest,WriteRequest,sizeof(struct IOExtSer));

			WriteRequest -> IOSer . io_Message . mn_ReplyPort = WritePort;

			WriteRequest -> IOSer . io_Command = SDCMD_SETPARAMS;

			SetFlags(WriteRequest);

			DoIO(WriteRequest);

				/* Restart read activity. */

			ReadRequest -> IOSer . io_Command	= CMD_READ;
			ReadRequest -> IOSer . io_Data		= ReadBuffer;
			ReadRequest -> IOSer . io_Length	= 1;

			SetSignal(0,SIG_SERIAL);

			SendIO(ReadRequest);

			return(TRUE);
		}
		else
			DeleteSerial();
	}
	else
	{
		DeleteSerial();

		return(TRUE);
	}

	return(FALSE);
}

	/* DeleteSerial():
	 *
	 *	Close the serial device and release all associated
	 *	resources.
	 */

VOID
DeleteSerial()
{
	BYTE Closed = FALSE;

	if(ReadRequest)
	{
		if(ReadRequest -> IOSer . io_Device)
		{
			if(!(Config . HighSpeed & SERIAL_SHARED))
			{
				ReadRequest -> IOSer . io_Command = CMD_RESET;

				DoIO(ReadRequest);
			}

			CloseDevice(ReadRequest);

			Closed = TRUE;
		}

		DeleteIORequest(ReadRequest);

		ReadRequest = NULL;
	}

	if(WriteRequest)
	{
		if(WriteRequest -> IOSer . io_Device && !Closed)
		{
			if(!(Config . HighSpeed & SERIAL_SHARED))
			{
				ReadRequest -> IOSer . io_Command = CMD_RESET;

				DoIO(ReadRequest);
			}

			CloseDevice(WriteRequest);
		}

		if(WriteRequest -> IOSer . io_Message . mn_ReplyPort)
			DeleteMsgPort(WriteRequest -> IOSer . io_Message . mn_ReplyPort);

		DeleteIORequest(WriteRequest);

		WriteRequest = NULL;
	}

	if(ReadPort)
	{
		DeleteMsgPort(ReadPort);

		ReadPort = NULL;
	}

	if(ReadBuffer)
	{
		FreeVec(ReadBuffer);

		ReadBuffer = NULL;
	}

	if(HostReadBuffer)
	{
		FreeVec(HostReadBuffer);

		HostReadBuffer = NULL;
	}

	if(OwnDevUnitBase && SerialDevice[0] && UnitNumber != -1)
	{
		FreeDevUnit(SerialDevice,UnitNumber);

		SerialDevice[0] = 0;

		UnitNumber = -1;
	}
}

	/* CreateSerial():
	 *
	 *	Create handles for the serial device and open it.
	 */

UBYTE *
CreateSerial()
{
	struct MsgPort *WritePort;

		/* If OwnDevUnit.library is available, try to lock
		 * the serial driver.
		 */

Start:	if(OwnDevUnitBase)
	{
		UBYTE *Error;

			/* Attempt to lock the device unit... */

		if(Error = AttemptDevUnit(Config . SerialDevice,Config . UnitNumber,TermIDString,NULL))
		{
				/* Check for error type if any. */

			if(!Strnicmp(Error,ODUERR_LEADCHAR,1))
				SPrintf(SharedBuffer,LocaleString(MSG_SERIAL_ERROR_ACCESSING_TXT),Config . SerialDevice,Config . UnitNumber,&Error[1]);
			else
				SPrintf(SharedBuffer,LocaleString(MSG_SERIAL_DEVICE_IN_USE_TXT),Config . SerialDevice,Config . UnitNumber,Error);

			SerialDevice[0] = 0;

			UnitNumber = -1;

			return(SharedBuffer);
		}
		else
		{
			strcpy(SerialDevice,Config . SerialDevice);

			UnitNumber = Config . UnitNumber;
		}
	}

	if(ReadBuffer = AllocVec(Config . SerBuffSize,MEMF_ANY))
	{
		if(XProtocolBase && (TransferBits & XPRS_HOSTNOWAIT))
			HostReadBuffer = AllocVec(Config . SerBuffSize,MEMF_ANY);
		else
			HostReadBuffer = NULL;

		if(ReadPort = (struct MsgPort *)CreateMsgPort())
		{
			if(ReadRequest = (struct IOExtSer *)CreateIORequest(ReadPort,sizeof(struct IOExtSer)))
			{
				SetFlags(ReadRequest);

				ReadRequest -> io_RBufLen = Config . SerBuffSize;

				if(!OpenDevice(Config . SerialDevice,Config . UnitNumber,ReadRequest,0))
				{
					if(WritePort = (struct MsgPort *)CreateMsgPort())
					{
						if(WriteRequest = (struct IOExtSer *)CreateIORequest(WritePort,sizeof(struct IOExtSer)))
						{
							CopyMem(ReadRequest,WriteRequest,sizeof(struct IOExtSer));

							WriteRequest -> IOSer . io_Message . mn_ReplyPort = WritePort;

							WriteRequest -> IOSer . io_Command = SDCMD_SETPARAMS;

							SetFlags(WriteRequest);

							DoIO(WriteRequest);

							/* If RTS/CTS (7 wire handshaking) is
							 * selected, have a look at the DSR
							 * line to see whether the modem
							 * connected is willing to support
							 * this handshaking mode.
							 */

							if(Config . Handshaking == HANDSHAKING_RTSCTS_DSR)
							{
Retry:								WriteRequest -> IOSer . io_Command = SDCMD_QUERY;

								DoIO(WriteRequest);

									/* If the line happens to
									 * be high, there is no
									 * DSR signal present.
									 */

								if(WriteRequest -> io_Status & (1 << 3))
								{
									if(MyEasyRequest(Window,LocaleString(MSG_SERIAL_NO_DSR_SIGNAL_TXT),LocaleString(MSG_SERIAL_RETRY_CANCEL_TXT)))
										goto Retry;
									else
									{
										DeleteSerial();

										Config . Handshaking = HANDSHAKING_XONXOFF;

										SerialMessage = LocaleString(MSG_SERIAL_NO_DSR_SIGNAL_HANDSHAKING_DISABLED_TXT);

										goto Start;
									}
								}
							}

							ReadRequest -> IOSer . io_Command	= CMD_READ;
							ReadRequest -> IOSer . io_Data		= ReadBuffer;
							ReadRequest -> IOSer . io_Length	= 1;

							SetSignal(0,SIG_SERIAL);

							SendIO(ReadRequest);

							return(NULL);
						}
						else
						{
							SerialMessage = NULL;

							DeleteSerial();

							return(LocaleString(MSG_SERIAL_NOT_ENOUGH_MEMORY_TXT));
						}
					}
					else
					{
						SerialMessage = NULL;

						DeleteSerial();

						return(LocaleString(MSG_SERIAL_FAILED_TO_CREATE_WRITE_PORT_TXT));
					}
				}
				else
				{
					ReadRequest -> IOSer . io_Device = NULL;

					DeleteSerial();

					SerialMessage = NULL;

					SPrintf(SharedBuffer,LocaleString(MSG_SERIAL_FAILED_TO_OPEN_UNIT_TXT),Config . SerialDevice,Config . UnitNumber);

					return(SharedBuffer);
				}
			}
			else
			{
				SerialMessage = NULL;

				DeleteSerial();

				return(LocaleString(MSG_SERIAL_NOT_ENOUGH_MEMORY_TXT));
			}
		}
		else
		{
			SerialMessage = NULL;

			DeleteSerial();

			return(LocaleString(MSG_SERIAL_FAILED_TO_CREATE_READ_PORT_TXT));
		}
	}
	else
	{
		SerialMessage = NULL;

		DeleteSerial();

		return(LocaleString(MSG_SERIAL_NOT_ENOUGH_MEMORY_TXT));
	}
}
