#include "/include/memory.h"

#include "dialer.h"

 /*
  * note that you probably don't want to have both debug's turned on at the
  * same time...
  */

/* #define DEBUG_WRITES */
/* #define DEBUG_READS */

 /* storage for various magic items */

static struct IOExtSer SerialRead;
static struct IOExtSer SerialWrite;
static struct MsgPort *SReadPort;
static struct MsgPort *SWritePort;
static ULONG SReadSigMask;
static ULONG SWriteSigMask;
static BOOL SerialUp = FALSE;

static UBYTE SReadBuf[256];
static ULONG SReadPos;
static ULONG SReadSize;

static UBYTE SWriteBuf[256];
static ULONG SWritePos;

/* hold some magic info */

static struct MsgPort *TimerPort;
struct timerequest TimerIO;
ULONG TimerSigMask;
BOOL TimerUp = FALSE;

/* open up the timer for use */

static BOOL OpenTimer(void)
{
	if (!(TimerPort = CreatePort(NULL, 0)))
		return FALSE;

	TimerSigMask = 1L << TimerPort->mp_SigBit;

	if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)&TimerIO, 0)) {
		DeletePort(TimerPort);
		return FALSE;
	}

	TimerIO.tr_node.io_Message.mn_ReplyPort = TimerPort;

	TimerUp = TRUE;

	return TRUE;
}

 /* shut down our timer handling stuff */

static void CloseTimer(void)
{
	if (TimerUp) {
		CloseDevice((struct IORequest *)&TimerIO);
		DeletePort(TimerPort);
		TimerUp = FALSE;
	}
}

typedef struct {
	STRPTR mi_Device;
	ULONG mi_Unit;
	ULONG mi_MaxBaud;
	BOOL mi_MatchDTE;
	BOOL mi_CTSRTS;
	UBYTE mi_ModemInit[1];
}
ModemInfo_t;

ModemInfo_t *openedMi = NULL;

ULONG currentBaud;
BOOL use8N1;

 /* try and OpenDevice() and set the appropriate serial parameters */

static BOOL InitSer(ModemInfo_t * mi)
{
	register struct IOExtSer *ior = &SerialRead;
	register struct IOExtSer *iow = &SerialWrite;

	/* try and open the device */
	ior->io_SerFlags = 0;
	if (mi->mi_CTSRTS)
		ior->io_SerFlags |= SERF_7WIRE;
	else
		ior->io_SerFlags &= ~SERF_7WIRE;

	ior->IOSer.io_Message.mn_ReplyPort = SReadPort;

	if (OpenDevice(openedMi->mi_Device, openedMi->mi_Unit, (struct IORequest *)ior, 0)) {
		return FALSE;
	}

	ior->IOSer.io_Command = SDCMD_QUERY;
	DoIO((struct IORequest *)ior);

	/*
	 * supposedly you are only allowed to set SERF_7WIRE before you open
	 * the device.  However, it seems that the oldser.device used by the
	 * A2232 is screwing this up.  So we reset it after out open device
	 * call just in case.
	 */

	if (mi->mi_CTSRTS)
		ior->io_SerFlags |= SERF_7WIRE;
	else
		ior->io_SerFlags &= ~SERF_7WIRE;

	ior->io_Baud = currentBaud;
	ior->io_StopBits = 1;
	ior->io_RBufLen = 1024;

	if (use8N1) {
		ior->io_ReadLen = ior->io_WriteLen = 8;
		ior->io_SerFlags &= ~(SERF_PARTY_ODD | SERF_PARTY_ON | SERF_XDISABLED);	/* insure no parity is
											 * in use */
	}
	else {
		ior->io_ReadLen = ior->io_WriteLen = 7;
		ior->io_SerFlags &= ~(SERF_PARTY_ODD | SERF_XDISABLED);
		ior->io_SerFlags |= SERF_PARTY_ON;
		ior->io_ExtFlags &= ~SEXTF_MSPON;
	};

	ior->IOSer.io_Command = SDCMD_SETPARAMS;
	if (DoIO((struct IORequest *)ior)) {
		CloseDevice((struct IORequest *)&SerialRead);
		return FALSE;
	}

	CopyMem((APTR)ior, (APTR)iow, sizeof(struct IOExtSer));

	iow->IOSer.io_Message.mn_ReplyPort = SWritePort;

	SReadPos = SReadSize = 0;

	SerialUp = TRUE;

	return TRUE;
}

 /* open up the serial device */

static BOOL OpenSerial(ModemInfo_t * mi, BOOL AttemptMode)
{
	STRPTR ODUresult;

	/* save these away for when we need to close the device */

	openedMi = mi;

	/* allocate our message ports */
	if (!(SReadPort = CreatePort(NULL, 0))) {
		openedMi = NULL;
		return FALSE;
	}

	if (!(SWritePort = CreatePort(NULL, 0))) {
		DeletePort(SReadPort);
		openedMi = NULL;
		return FALSE;
	}

	SReadSigMask = 1L << SReadPort->mp_SigBit;
	SWriteSigMask = 1L << SWritePort->mp_SigBit;

	if (AttemptMode)
		ODUresult = AttemptDevUnit(openedMi->mi_Device, openedMi->mi_Unit, PROGNAME, 0L);
	else
		ODUresult = LockDevUnit(openedMi->mi_Device, openedMi->mi_Unit, PROGNAME, 0L);

	if (ODUresult) {
		DeletePort(SWritePort);
		DeletePort(SReadPort);
		openedMi = NULL;
		return FALSE;
	}

	currentBaud = mi->mi_MaxBaud;
	use8N1 = FALSE;

	if (!InitSer(mi)) {
		FreeDevUnit(openedMi->mi_Device, openedMi->mi_Unit);

		DeletePort(SWritePort);
		DeletePort(SReadPort);
		openedMi = NULL;
		return FALSE;
	}

	if (!OpenTimer()) {
		CloseSerial();
		return FALSE;
	}

	return TRUE;
}

 /* this expects that the baud rate passed in is the baud rate of the service */

void SwitchBaud(ULONG baud)
{
	currentBaud = baud;

	if (openedMi->mi_MatchDTE) {
		if (baud > openedMi->mi_MaxBaud)
			baud = openedMi->mi_MaxBaud;
	}
	else
		baud = openedMi->mi_MaxBaud;

	SerialRead.io_Baud = baud;
	SerialRead.IOSer.io_Command = SDCMD_SETPARAMS;
	DoIO((struct IORequest *)&SerialRead);

	SerialWrite.io_Baud = baud;
	SerialWrite.IOSer.io_Command = SDCMD_SETPARAMS;
	DoIO((struct IORequest *)&SerialWrite);
}

BOOL Connected(void)
{
	SerialRead.IOSer.io_Command = SDCMD_QUERY;
	DoIO((struct IORequest *)&SerialRead);

	if (SerialRead.io_Status & (1L << 5))
		return FALSE;
	else
		return TRUE;
}

#define MODEM_TEMPLATE "DEVICE/A,UNIT/N/A,MAXBAUD/N/A,MATCHDTE/A,CTSRTS/A,INITSTRING/A"
enum ModemArgs {
	ARG_DEVICE,
	ARG_UNIT,
	ARG_BAUD,
	ARG_DTE,
	ARG_CTS,
	ARG_INIT,
	ARG_sizeof
};

static ModemInfo_t *ReadModemInfo(LONG modemNumber)
{
	ModemInfo_t *mi = NULL;
	UBYTE modemName[32];
	STRPTR modemConfig;
	STRPTR buffer;
	struct RDArgs *ArgsPtr;
	struct RDArgs *MyArgs;
	STRPTR ArgArray[ARG_sizeof];

	sprintf(modemName, "MODEM%ld", modemNumber);

	if (modemNumber == 1)
		modemConfig = FindPagerConfigDefault(ph, modemName, "serial.device 0 1200 y n ATZ\\r\\d\\d\\dATM0\\r\\dATD");
	else
		modemConfig = FindPagerConfig(ph, modemName);

	if (modemConfig) {
		if (buffer = MyAllocVec(strlen(modemConfig) + 4)) {
			strcpy(buffer, modemConfig);
			strcat(buffer, "\n");

			if (MyArgs = (struct RDArgs *)AllocDosObject(DOS_RDARGS, TAG_DONE)) {
				MyArgs->RDA_Flags |= RDAF_NOPROMPT;
				MyArgs->RDA_Source.CS_Buffer = buffer;
				MyArgs->RDA_Source.CS_Length = strlen(buffer);
				MyArgs->RDA_Source.CS_CurChr = 0L;

				memset((char *)ArgArray, 0, sizeof(ArgArray));
				if (ArgsPtr = ReadArgs(MODEM_TEMPLATE, (LONG *)&ArgArray, MyArgs)) {
					if (mi = (ModemInfo_t *) MyAllocVec(sizeof(ModemInfo_t) + strlen(ArgArray[ARG_DEVICE]) + strlen(ArgArray[ARG_INIT]) + 2)) {
						strcpy(mi->mi_ModemInit, ArgArray[ARG_INIT]);

						mi->mi_Device = &mi->mi_ModemInit[strlen(mi->mi_ModemInit) + 1];
						strcpy(mi->mi_Device, ArgArray[ARG_DEVICE]);

						mi->mi_Unit = *((LONG *)ArgArray[ARG_UNIT]);
						mi->mi_MaxBaud = *((LONG *)ArgArray[ARG_BAUD]);
						mi->mi_MatchDTE = PagerConfigYesNo(ArgArray[ARG_DTE]);
						mi->mi_CTSRTS = PagerConfigYesNo(ArgArray[ARG_CTS]);
					}
					else
						ErrorMsg("out of memory!");

					FreeArgs(ArgsPtr);
				}
				else {
					ErrorMsg("%s config entry badly formatted.", modemName);
					ULog(ph, -1, "%s config entry badly formatted.", modemName);
				}

				FreeDosObject(DOS_RDARGS, MyArgs);
			}
			else
				ErrorMsg("out of memory!");

			MyFreeVec(buffer);
		}
		else
			ErrorMsg("out of memory!");

		FreePagerConfig(modemConfig);
	}

	return mi;
}

LONG OpenModem(LONG modemNumber, LONG modemStart)
{
	ModemInfo_t *mi;

	online = FALSE;

	if (modemNumber) {
		if (mi = ReadModemInfo(modemNumber)) {
			if (OpenSerial(mi, FALSE) == FALSE) {
				MyFreeVec(mi);
				return 0L;
			}
			else
				return modemNumber;
		}
	}
	else {
		modemNumber = modemStart;

		while (mi = ReadModemInfo(modemNumber++))
			if (OpenSerial(mi, TRUE))
				return modemNumber - 1;

		return 0L;
	}
}

 /* close down the serial handling stuff */

void CloseSerial(void)
{
	CloseTimer();

	if (SerialUp) {
		CloseDevice((struct IORequest *)&SerialRead);
		online = SerialUp = FALSE;
	}

	FreeDevUnit(openedMi->mi_Device, openedMi->mi_Unit);

	MyFreeVec(openedMi);
	openedMi = NULL;

	DeletePort(SWritePort);
	DeletePort(SReadPort);
}

 /* hang up the modem -- we use a DTR drop to do this */
BOOL HangUp(void)
{
	if (SerialUp) {
		CloseDevice((struct IORequest *)&SerialRead);
		online = SerialUp = FALSE;
	}

	Delay(TICKS_PER_SECOND);

	return InitSer(openedMi);
}

 /* determine if we should be using 8N1 or 7E1 */
BOOL Use8N1(BOOL set8N1)
{
	BOOL old8N1;

	old8N1 = use8N1;

	use8N1 = set8N1;

	/*
	 * if they really changed the use8N1 state then we need to check to
	 * see if the modem is currently on-line and reset it if so.
	 */

	if (use8N1 != old8N1) {
		if (SerialUp) {
			CloseDevice((struct IORequest *)&SerialRead);
			online = SerialUp = FALSE;
		}

		Delay(TICKS_PER_SECOND);

		return InitSer(openedMi);
	}
	else
		return TRUE;
}

 /* flush out the serial read buffer */

void ClearSerial(void)
{
	SReadPos = SReadSize = 0;

	SerialRead.IOSer.io_Command = CMD_CLEAR;
	DoIO((struct IORequest *)&SerialRead);
}

 /* send a string to the serial device */

void SerWrite(STRPTR Buf, ULONG Len)
{
#ifdef DEBUG_WRITES
	FWrite(Output(), Buf, 1, Len);
#endif

	SerialWrite.IOSer.io_Command = CMD_WRITE;
	SerialWrite.IOSer.io_Length = Len;
	SerialWrite.IOSer.io_Data = (APTR)Buf;
	DoIO((struct IORequest *)&SerialWrite);

	if (SerialWrite.IOSer.io_Error)
		ULog(ph, -1, "serial write error %ld", SerialWrite.IOSer.io_Error);
}

void SerPutChar(UBYTE value)
{
	UBYTE Buf[2];

#ifdef DEBUG_WRITES
	FPutC(Output(), value);
#endif

	Buf[0] = value;

	SerialWrite.IOSer.io_Command = CMD_WRITE;
	SerialWrite.IOSer.io_Length = 1;
	SerialWrite.IOSer.io_Data = (APTR)Buf;
	DoIO((struct IORequest *)&SerialWrite);

	if (SerialWrite.IOSer.io_Error)
		ULog(ph, -1, "serial write error %ld", SerialWrite.IOSer.io_Error);
}

 /*
  * wait for a specific string to be read from the device.  this is
  * case-insensitive
  */

BOOL SerWaitString(STRPTR target, LONG timeout)
{
	UWORD byteRead;
	int i, len;

	len = strlen(target);
	i = 0;

	while ((byteRead = SerGetRawChar(timeout)) != TIMEOUT) {
		if (tolower(byteRead) == tolower(target[i])) {
			if (++i == len)
				return TRUE;
		}
		else
			i = 0;
	}

	return FALSE;
}

 /* get a character from the serial device with a timeout */

UWORD SerGetRawChar(LONG timeout)
{
	UBYTE Buffer[2];
	ULONG Signals;
	ULONG secs, micro;

	if (timeout < 0) {
		secs = 0;
		micro = -timeout;
	}
	else {
		secs = timeout;
		micro = 0;
	}

	if (SReadPos < SReadSize) {
#ifdef DEBUG_READS
		Printf("%lc", SReadBuf[SReadPos]);
#endif
		return (UWORD)SReadBuf[SReadPos++];
	}

	SetSignal(0L, SReadSigMask | TimerSigMask);

	SerialRead.IOSer.io_Command = CMD_READ;
	SerialRead.IOSer.io_Length = 1;
	SerialRead.IOSer.io_Data = (APTR)Buffer;
	SendIO((struct IORequest *)&SerialRead);

	TimerIO.tr_node.io_Command = TR_ADDREQUEST;
	TimerIO.tr_time.tv_secs = secs;
	TimerIO.tr_time.tv_micro = micro;
	SendIO((struct IORequest *)&TimerIO);

	Signals = Wait(SReadSigMask | TimerSigMask);

	if (Signals & SReadSigMask) {
		if (!CheckIO((struct IORequest *)&TimerIO)) {
			AbortIO((struct IORequest *)&TimerIO);
		}
		WaitIO((struct IORequest *)&TimerIO);

		WaitIO((struct IORequest *)&SerialRead);

		if (!SerialRead.IOSer.io_Error && SerialRead.IOSer.io_Actual) {
			SerialRead.IOSer.io_Command = SDCMD_QUERY;
			DoIO((struct IORequest *)&SerialRead);

			if (SerialRead.IOSer.io_Actual) {
				SerialRead.IOSer.io_Command = CMD_READ;
				SerialRead.IOSer.io_Length = min(sizeof(SReadBuf), SerialRead.IOSer.io_Actual);
				SerialRead.IOSer.io_Data = (APTR)SReadBuf;
				DoIO((struct IORequest *)&SerialRead);

				if (!SerialRead.IOSer.io_Error) {
					SReadPos = 0;
					SReadSize = SerialRead.IOSer.io_Actual;
				}
			}

#ifdef DEBUG_READS
			Printf("%lc", Buffer[0]);
#endif
			return (UWORD)Buffer[0];
		}
		else
			return 0;
	}

	if (Signals & TimerSigMask)
		WaitIO((struct IORequest *)&TimerIO);

	/* if we get down here we have timed out waiting for a character */

	if (!CheckIO((struct IORequest *)&SerialRead)) {
		AbortIO((struct IORequest *)&SerialRead);
	}
	WaitIO((struct IORequest *)&SerialRead);

	return TIMEOUT;
}

BOOL InitModem(void)
{
	STRPTR ptr;
	UWORD c;
	BOOL validEscape;

#define MAX_AT_RETRIES (3)

	/* try to get the modem's attention. */
	for (c = 0; c < MAX_AT_RETRIES; c++) {
		SerWrite("AT\r", 3);
		if (SerWaitString("OK\r\n", 6))
			break;
	}

	if (c == MAX_AT_RETRIES) {
		ErrorMsg("modem %ld not responding.", openedModem);
		ULog(ph, -1, "modem %ld not responding.", openedModem);
		return FALSE;
	}

	Delay(TICKS_PER_SECOND);

	ClearSerial();

	/* now start sending the init string out */

	ptr = openedMi->mi_ModemInit;

	while (c = *ptr++) {
		validEscape = FALSE;

		if (c == '\\') {
			validEscape = TRUE;

			switch (tolower(*ptr)) {
			case 'd':
				Delay(2 * TICKS_PER_SECOND);
				c = 0xFFFF;
				break;

			case 'r':
				c = '\r';
				break;

			default:
				validEscape = FALSE;
				break;
			}
		}

		if (c != 0xFFFF)
			SerPutChar(c);

		if (c == '\r')
			while ((c = SerGetRawChar(-100)) != TIMEOUT) ;

		if (validEscape) {
			ptr++;
		}
	}

	return TRUE;
}
