#include "/include/memory.h"

#include "dialer.h"

BOOL online;

static UBYTE packetBuf[1024];
static UBYTE safePacketBuf[8 * sizeof(packetBuf)];

static const UBYTE hexTable[] = "0123456789ABCDEF";

static LONG safeBufIndex = 0;
static BOOL lastSafeSpecial;
static BOOL lastSafeHex;

 /*
  * add a character to the safe buf.  insert an intelligent name for special
  * characers and convert unprintable to hex values. calling with a parameter
  * of -1 means to close the buffer off.
  */

static WORD AddToSafeBuf(WORD c)
{
	STRPTR specialName;

	if (safeBufIndex == 0) {
		lastSafeSpecial = TRUE;
		lastSafeHex = FALSE;
	}

	if (c == -1) {
		if (safeBufIndex == 0 || lastSafeSpecial == FALSE) {
			if (safeBufIndex == 0)
				safePacketBuf[safeBufIndex++] = '\"';

			safePacketBuf[safeBufIndex++] = '\"';
		}

		safePacketBuf[safeBufIndex] = '\0';
		safeBufIndex = 0;
	}
	else {
		switch (c) {
		case HSHAKE_STX:
			specialName = "STX";
			break;

		case HSHAKE_ETX:
			specialName = "ETX";
			break;

		case HSHAKE_EOT:
			specialName = "EOT";
			break;

		case HSHAKE_ACK:
			specialName = "ACK";
			break;

		case HSHAKE_NAK:
			specialName = "NAK";
			break;

		case HSHAKE_ETB:
			specialName = "ETB";
			break;

		case HSHAKE_RS:
			specialName = "RS";
			break;

		case HSHAKE_US:
			specialName = "US";
			break;

		case '\r':
			specialName = "CR";
			break;

		case '\x1B':
			specialName = "ESC";
			break;

		default:
			specialName = NULL;
			break;
		}

		if (specialName != NULL) {
			if (lastSafeSpecial == FALSE) {
				safePacketBuf[safeBufIndex++] = '\"';
				lastSafeSpecial = TRUE;
			}

			if (safeBufIndex)
				safePacketBuf[safeBufIndex++] = ' ';

			while (*specialName)
				safePacketBuf[safeBufIndex++] = *specialName++;

			lastSafeHex = FALSE;
		}
		else {
			if (lastSafeSpecial) {
				if (safeBufIndex)
					safePacketBuf[safeBufIndex++] = ' ';

				safePacketBuf[safeBufIndex++] = '\"';

				lastSafeSpecial = FALSE;
			}

			if (isprint(c)) {

				/*
				 * check to see if the last character was a
				 * hex value.  if so, see if the current
				 * characer is a valid hex digit.  if so,
				 * insert " " so that one doesn't get
				 * confused reading the translated output as
				 * otherwise it would look like a larger hex
				 * digit has been specified than really was.
				 */
				if (lastSafeHex) {
					if (isxdigit(c)) {
						safePacketBuf[safeBufIndex++] = '\"';
						safePacketBuf[safeBufIndex++] = ' ';
						safePacketBuf[safeBufIndex++] = '\"';
					}

					lastSafeHex = FALSE;
				}

				if (c == '\"')
					safePacketBuf[safeBufIndex++] = '\\';

				safePacketBuf[safeBufIndex++] = c;
			}
			else {
				safePacketBuf[safeBufIndex++] = '\\';
				safePacketBuf[safeBufIndex++] = 'x';
				safePacketBuf[safeBufIndex++] = hexTable[(c >> 4) & 0xF];
				safePacketBuf[safeBufIndex++] = hexTable[c & 0xF];
				lastSafeHex = TRUE;
			}
		}
	}

	return c;
}

static LONG GetPacket(STRPTR errorMsg)
{
	LONG index;
	UWORD c;

	safeBufIndex = index = 0;

	while (c = SerGetRawChar(20)) {
		if (c == TIMEOUT) {
			ErrorMsg("%s: timed out trying to read packet from service.", errorMsg);
			ULog(ph, -1, "%s: timed out trying to read packet from service.", errorMsg);
			return -1;
		}
		else if (c == '\n')
			continue;
		else if (c == '\r')
			break;

		packetBuf[index++] = AddToSafeBuf(c);

		if (index == (sizeof(packetBuf) - 2)) {
			ErrorMsg("%s: panic attack - packet from service too long.", errorMsg);
			ULog(ph, -1, "%s: panic attack - packet from service too long.", errorMsg);
			return -2;
		}
	}

	packetBuf[index] = '\0';
	AddToSafeBuf(-1);

	ULog(ph, 5, "getpacket: got %s", safePacketBuf);

	return index;
}

#define MAX_LOGIN_RETRIES (5)

static int DialService(PagerService_t * svc)
{
	int retries;
	LONG len;
	STRPTR ptr;
	BOOL doWrite;

	Use8N1(svc->svc_Use8N1);
	SwitchBaud(svc->svc_BaudRate);

	if (!InitModem())
		return 2;

	SerWrite(svc->svc_Phone, strlen(svc->svc_Phone));
	SerPutChar('\r');

	ULog(ph, -1, "dialed %s (%s) on modem %ld", svc->svc_Name, svc->svc_Phone, openedModem);

	if (SerWaitString("CONNECT", 60)) {
		online = TRUE;

		/* eat up to the newline after the connect message */

		SerWaitString("\r\n", 1);

		Delay(TICKS_PER_SECOND);

		/* first look for the ID= prompt */

		for (retries = 0; retries < MAX_LOGIN_RETRIES; retries++) {
			SerPutChar('\r');

			if (SerWaitString("ID=", 2))
				break;
		}

		if (retries == MAX_LOGIN_RETRIES) {
			ErrorMsg("login failed: no \"ID=\" from the service.");
			ULog(ph, -1, "login failed: no \"ID=\" from the service.");
			return 4;
		}

		/* eat any characters trailing the ID= prompt */

		while (SerGetRawChar(1) != TIMEOUT) ;

		/* now try and log in in automatic mode */

		doWrite = TRUE;

		for (retries = 0; retries < MAX_LOGIN_RETRIES; retries++) {
			if (doWrite) {
				SerWrite("\x1B" "PG", 3);
				SerWrite(svc->svc_Password, strlen(svc->svc_Password));
				SerPutChar('\r');
				doWrite = FALSE;
			}

			if ((len = GetPacket("login failed")) < 0)
				return 4;
			else {
				if (len == 1 && packetBuf[0] == HSHAKE_ACK) {
					for (retries = 0; retries < MAX_LOGIN_RETRIES; retries++) {
						if ((len = GetPacket("login failed")) < 0)
							return 4;
						else if (packetBuf[0] == '\x1B' && packetBuf[1] == '[' && packetBuf[2] == 'p') {
							ULog(ph, -1, "login successful");
							return 0;
						}
					}

					ErrorMsg("login failed: retry limit exceeded");
					ULog(ph, -1, "login failed: retry limit exceeded");
					return 4;
				}
				else if (len == 1 && packetBuf[0] == HSHAKE_NAK) {
					doWrite = TRUE;
					continue;
				}
				else if (len == 2 && packetBuf[0] == '\x1B' && packetBuf[1] == HSHAKE_EOT) {
					ErrorMsg("login failed: service refused connection.");
					ULog(ph, -1, "login failed: service refused connection.");
					return 4;
				}
				else if (packetBuf[0]) {
					ptr = packetBuf;

					while (isspace(*ptr))
						ptr++;

					if (ptr[0] == 'I' && ptr[1] == 'D' && ptr[2] == '=') {
						retries--;
					}
					else {
						ULog(ph, 2, "login: unknown packet received - %s", safePacketBuf);
					}
				}
			}
		}

		ErrorMsg("login failed: retry limit exceeded");
		ULog(ph, -1, "login failed: retry limit exceeded");
		return 4;
	}
	else {
		ErrorMsg("login failed: no carrier detected.");
		ULog(ph, -1, "login failed: no carrier detected.");
		return 4;
	}
}

static UBYTE inputBuffer[256];

static const UBYTE checkSumTable[] = "0123456789:;<=>?";

static int SendBlock(void)
{
	int sum;
	int i, c;
	int retries;
	LONG len;
	BOOL doWrite;

	sum = safeBufIndex = i = 0;

	while (c = inputBuffer[i++])
		sum += AddToSafeBuf(c);

	i--;

	inputBuffer[i++] = AddToSafeBuf(checkSumTable[(sum >> 8) & 0xF]);
	inputBuffer[i++] = AddToSafeBuf(checkSumTable[(sum >> 4) & 0xF]);
	inputBuffer[i++] = AddToSafeBuf(checkSumTable[sum & 0xF]);
	inputBuffer[i++] = AddToSafeBuf('\r');
	AddToSafeBuf(-1);

	ULog(ph, 4, "sendblock: sent %s", safePacketBuf);

	doWrite = TRUE;

	for (retries = 0; retries < 100; retries++) {
		if (doWrite) {
			SerWrite(inputBuffer, i);
			doWrite = FALSE;
		}

		if ((len = GetPacket("sendblock failed")) < 0)
			return 1;
		else {
			if (len == 1 && packetBuf[0] == HSHAKE_ACK)
				return 0;
			else if (len == 1 && packetBuf[0] == HSHAKE_NAK) {
				ULog(ph, 2, "sendblock: received nak -- retrying");
				doWrite = TRUE;
				continue;
			}
			else if (len == 1 && packetBuf[0] == HSHAKE_RS) {
				ErrorMsg("sendblock failed: service rejected block");
				ULog(ph, -1, "sendblock failed: service rejected block");
				return 3;
			}
			else if (len == 2 && packetBuf[0] == '\x1B' && packetBuf[1] == HSHAKE_EOT) {
				ErrorMsg("sendblock failed: service requested hangup");
				ULog(ph, -1, "sendblock failed: service requested hangup");
				return 1;
			}
			else if (packetBuf[0]) {
				ULog(ph, 2, "sendblock: unknown packet received - %s", safePacketBuf);
			}
		}
	}

	ErrorMsg("sendblock failed: retry limit exceeded");
	ULog(ph, -1, "sendblock failed: retry limit exceeded");

	return 1;
}

static int ProcessSpoolFile(PagerService_t * svc, BPTR fh, STRPTR spoolFileName, STRPTR nameBuffer, STRPTR pinBuffer)
{
	LONG tag, blockLen;
	int readOffset;
	int result;
	int pageCount;

	/* make sure we are connected to the service. */

	if (!online)
		if (result = DialService(svc))
			return result;

	/* loop sending pages in the spoolfile to the service */

	for (tag = FGetC(fh), pageCount = result = 0; !result && tag != END_OF_SPOOL; pageCount++) {
		if ((tag != PAGE_START) || ((tag = FGetC(fh)) == EOF)) {
			ErrorMsg("error reading spool file \"%s\"", spoolFileName);
			ULog(ph, -1, "error reading spool file \"%s\"", spoolFileName);
			return 3;
		}

		inputBuffer[0] = HSHAKE_STX;
		strcpy(&inputBuffer[1], pinBuffer);
		readOffset = strlen(inputBuffer);
		inputBuffer[readOffset++] = '\r';

		for (; !result && tag != PAGE_END;) {
			if ((tag != BLOCK_START) ||
			    ((blockLen = FGetC(fh)) == EOF) || (blockLen + readOffset + 1 > 250) ||
			    (FRead(fh, &inputBuffer[readOffset], 1, blockLen) != blockLen) ||
			    ((tag = FGetC(fh)) != BLOCK_END) || ((tag = FGetC(fh)) == EOF)) {
				ErrorMsg("error reading spool file \"%s\"", spoolFileName);
				ULog(ph, -1, "error reading spool file \"%s\"", spoolFileName);
				return 3;
			}

			if (tag == PAGE_END) {
				inputBuffer[readOffset + blockLen++] = '\r';
				inputBuffer[readOffset + blockLen++] = HSHAKE_ETX;
			}
			else
				inputBuffer[readOffset + blockLen++] = HSHAKE_US;

			inputBuffer[readOffset + blockLen] = 0;

			result = SendBlock();

			readOffset = 1;
		}

		tag = FGetC(fh);
	}

	if (!result)
		ULog(ph, -1, "delivered message to %s (%s) in %ld page%s", pinBuffer, nameBuffer, pageCount, (pageCount == 1 ? "" : "s"));

	return result;
}

static STRPTR ReadLongItem(BPTR fh, STRPTR spoolFileName)
{
	LONG longBuf;
	STRPTR buffer;

	if (FRead(fh, &longBuf, sizeof(longBuf), 1) == 1) {
		if (buffer = MyAllocVec(longBuf + 1)) {
			if (FRead(fh, buffer, 1, longBuf) == longBuf) {
				buffer[longBuf] = '\0';
				return buffer;
			}
			else {
				ErrorMsg("error reading spool file \"%s\"", spoolFileName);
				ULog(ph, -1, "error reading spool file \"%s\"", spoolFileName);
			}

			MyFreeVec(buffer);
		}
		else
			ErrorMsg("out of memory!");
	}
	else {
		ErrorMsg("error reading spool file \"%s\"", spoolFileName);
		ULog(ph, -1, "error reading spool file \"%s\"", spoolFileName);
	}

	return buffer;
}

static UBYTE RenameBuf[FILENAME_BUF_SIZE];

int SendSpoolFile(PagerService_t * svc, STRPTR spoolFileName)
{
	BPTR fh;
	STRPTR nameBuffer, pinBuffer;
	int result;

	result = 4;

	if (LockFile(ph, spoolFileName)) {
		if (fh = Open(spoolFileName, MODE_OLDFILE)) {
			result = 3;

			if (FRead(fh, inputBuffer, 1, strlen(SPOOLMAGIC) + 1) == strlen(SPOOLMAGIC) + 1) {
				if (strcmp(inputBuffer, SPOOLMAGIC) == 0) {
					if (nameBuffer = ReadLongItem(fh, spoolFileName)) {
						if (pinBuffer = ReadLongItem(fh, spoolFileName)) {
							result = ProcessSpoolFile(svc, fh, spoolFileName, nameBuffer, pinBuffer);

							MyFreeVec(pinBuffer);
						}

						MyFreeVec(nameBuffer);
					}
				}
				else {
					ErrorMsg("\"%s\" is not a valid spool file", spoolFileName);
					ULog(ph, -1, "\"%s\" is not a valid spool file", spoolFileName);
				}
			}
			else {
				ErrorMsg("error reading spool file \"%s\"", spoolFileName);
				ULog(ph, -1, "error reading spool file \"%s\"", spoolFileName);
			}

			Close(fh);
		}
		else {
			ErrorMsg("couldn't open spool file \"%s\"", spoolFileName);
			ULog(ph, -1, "couldn't open spool file \"%s\"", spoolFileName);
		}

		UnLockFile(ph, spoolFileName);
	}
	else {
		ErrorMsg("couldn't lock spool file \"%s\"", spoolFileName);
		ULog(ph, -1, "couldn't lock spool file \"%s\"", spoolFileName);
	}

	/*
	 * if the spool file is bad (result = 3) then rename it so we don't
	 * get hung up on it.  in this case we return 0 so that we can
	 * continue sending other files while on-line
	 */

	if (result == 3) {
		strcpy(RenameBuf, spoolFileName);
		strcat(RenameBuf, ".bad");

		DeleteFile(RenameBuf);	/* sanity check */

		if (Rename(spoolFileName, RenameBuf) == DOSTRUE) {
			ULog(ph, -1, "renamed \"%s\" to \"%s\".", spoolFileName, RenameBuf);
			result = 0;
		}
		else
			ULog(ph, -1, "unable to rename \"%s\" to \"%s\".", spoolFileName, RenameBuf);
	}

	/*
	 * if we are all done and sent the spool file without any trouble
	 * then we need to delete the file
	 */

	if (!result)
		DeleteFile(spoolFileName);

	return result;
}
