// --------------------------------------------------------------------------
// Citadel: Carrier.CPP
//
// Code to detect and report on Carrier.

#include "ctdl.h"
#pragma hdrstop

#include "log.h"
#include "mdmreslt.h"
#include "aplic.h"
#include "cwindows.h"

// --------------------------------------------------------------------------
// Contents
//
// checkCR			Checks for CRs from the data port for half a second.
// findbaud 		Finds the baud from sysop and user supplied data.
// carrdetect		sets global flags for carrier detect
// carrloss 		sets global flags for carrier loss
// ringdetectbaud	sets baud rate according to ring detect
// smartbaud		sets baud rate according to result codes
// modInpStr		get a string from the modem, waiting for upto 3 secs
// checkring		Checks for 'RING' or '2' from data port.
// carrier			checks carrier


// --------------------------------------------------------------------------
// checkCR(): Checks for CRs from the data port for half a second
//
// Return value:
//	TRUE: CR detected
//	FALSE: CR not detected

static Bool checkCR(void)
	{
	pause(50);

	if (TI()SerialPort.IsInputReady() && (TI()SerialPort.Input() == '\r'))
		{
		return (TRUE);
		}

	return (FALSE);
	}


// --------------------------------------------------------------------------
// findbaud(): Finds the baud by checking for CRs from user.
//
// Return value:
//	TRUE: Baud found and set
//	FALSE: Couldn't detect baud

static Bool findbaud(void)
	{
	Bool good = FALSE;
	int time = 0;

	PortSpeedE baudRunner = PS_300;

	while (TI()SerialPort.IsInputReady())
		{
		TI()SerialPort.Input();
		}

	while (TI()SerialPort.HaveCarrier() && !good && time++ < 120)
		{
		TI()SerialPort.SetSpeed(baudRunner);
		good = checkCR();

		if (!good)
			{
			baudRunner = (PortSpeedE) ((baudRunner + 1) % PS_NUM);
			}
		}

	for (ModemSpeedE i = MS_300; i < MS_NUM; i = (ModemSpeedE) (i + 1))
		{
		if (connectbauds[i] == bauds[baudRunner])
			{
			TI()ModemSpeed = i;
			break;
			}
		}

	return (good);
	}


// --------------------------------------------------------------------------
// carrdetect(): sets global flags for carrier detect

void carrdetect(void)
	{
	char temp[30];

	TI()modStat = TRUE;

	time(&TI()conntimestamp);

	// screensaver junk only if fucking_stupid
	if (cfg.fucking_stupid)
		{
		ScreenSaver.Update();
		}

	sprintf(temp, getmsg(165), connectbauds[TI()ModemSpeed]);
	trap(temp, T_CARRIER);

	TI()CurrentUser->SetCredits(cfg.unlogbal);
	time(&TI()LastActiveTime);
	}


// --------------------------------------------------------------------------
// carrloss(): sets global flags for carrier loss

void carrloss(void)
	{
	CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
			CitWindowsMsg(NULL, getmsg(1639));

	TI()UserControl.SetOutFlag(OUTSKIP);

	TI()modStat = FALSE;
	pause(100);
	TI()SerialPort.Disable();
	trap(getmsg(166), T_CARRIER);

	if (w)
		{
		destroyCitWindow(w, FALSE);
		}
	}


// --------------------------------------------------------------------------
// ringdetectbaud(): sets baud rate according to ring detect

static void ringdetectbaud(void)
	{
	CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
			CitWindowsMsg(NULL, getmsg(1641),
			bauds[TI()SerialPort.IsRingIndicator() ? PS_1200 : PS_300]);

	TI()SerialPort.SetSpeed(TI()SerialPort.IsRingIndicator() ?
			PS_1200 : PS_300);

	TI()ModemSpeed = TI()SerialPort.GetSpeed() == PS_300 ? MS_300 : MS_1200;

	if (w)
		{
		destroyCitWindow(w, FALSE);
		}
	}


// --------------------------------------------------------------------------
// getModStr(): get a string from the modem, waiting for up to 3 secs for it.
//
// Input:
//	char *str: String to be filled.
//	uint len: Maximum length of string to be filled, not including nul.
//
// Output:
//	char *str: Filled with string.
//
// Return value:
//	TRUE: String found
//	FALSE: String not found
//
// Notes:
//	Keyboard input aborts. Length is passed, not assumed to be 40.

Bool getModStr(char *str, uint len)
	{
	time_t tm;
	uint l = 0;

	time(&tm);

	if (debug)
		{
		cPrintf(getdbmsg(1));
		}

	while ((time(NULL) - tm) < 4 && !KBReady() && l <= len)
		{
		if (TI()SerialPort.IsInputReady())
			{
			const int c = TI()SerialPort.Input();

			if (c == '\r' || c == '\n') // CR || LF
				{
				str[l] = 0;
				if (debug)
					{
					cPrintf(getdbmsg(0));
					}

				return (TRUE);
				}
			else
				{
				if (debug)
					{
					cPrintf(pctc, c);
					}

				str[l] = (char) c;
				l++;
				}
			}
		}

	if (debug)
		{
		cPrintf(getdbmsg(2));
		}

	str[0] = 0;

	return (FALSE);
	}


static ResultCodeEnum FindResultCode(const char *Test)
	{
	const ModemResultCodes *mrc;

	for (mrc = ResultCodes; mrc; mrc = (ModemResultCodes *) getNextLL(mrc))
		{
		if (mrc->Which == MR_CONNECTX || mrc->Which == MR_CONNECTXE ||
				mrc->Which == MR_CONNECTXC)
			{
			const int SpeedPos = strpos('|', mrc->Code);

			if (SpeedPos)
				{
				if (strncmpi(Test, mrc->Code, SpeedPos - 1) == SAMESTRING)
					{
					label SpeedString;
					const char *ch;
					int i;

					for (ch = Test + SpeedPos - 1, i = 0;
							isdigit(*ch) && i < LABELSIZE; ch++, i++)
						{
						SpeedString[i] = *ch;
						}

					SpeedString[i] = 0;

					if (SameString(mrc->Code + SpeedPos, ch))
						{
						switch(atol(SpeedString))
							{
							case 300: return ((ResultCodeEnum) (mrc->Which + 3));
							case 600: return ((ResultCodeEnum) (mrc->Which + 6));
							case 1200: return ((ResultCodeEnum) (mrc->Which + 9));
							case 2400: return ((ResultCodeEnum) (mrc->Which + 12));
							case 4800: return ((ResultCodeEnum) (mrc->Which + 15));
							case 7200: return ((ResultCodeEnum) (mrc->Which + 18));
							case 9600: return ((ResultCodeEnum) (mrc->Which + 21));
							case 12000: return ((ResultCodeEnum) (mrc->Which + 24));
							case 14400: return ((ResultCodeEnum) (mrc->Which + 27));
							case 16800: return ((ResultCodeEnum) (mrc->Which + 30));
							case 19200: return ((ResultCodeEnum) (mrc->Which + 33));
							case 21600: return ((ResultCodeEnum) (mrc->Which + 36));
							case 24000: return ((ResultCodeEnum) (mrc->Which + 39));
							case 26400: return ((ResultCodeEnum) (mrc->Which + 42));
							case 28800: return ((ResultCodeEnum) (mrc->Which + 45));
							case 38400l: return ((ResultCodeEnum) (mrc->Which + 48));
							case 57600l: return ((ResultCodeEnum) (mrc->Which + 51));
							case 115200l: return ((ResultCodeEnum) (mrc->Which + 54));
							case 230400l: return ((ResultCodeEnum) (mrc->Which + 57));
							}
						}
					}
				}
			else
				{
				return (mrc->Which);
				}
			}
		else if (SameString(Test, mrc->Code))
			{
			return (mrc->Which);
			}
		}

	return (MR_NUM);
	}

static const char *NextResultCode(const char *Test) 	// MR_RUNAPPLIC
	{
	ModemResultCodes *mrc;

	for (mrc = ResultCodes; mrc; mrc = (ModemResultCodes *) getNextLL(mrc))
		{
		if (SameString(Test, mrc->Code))
			{
			return (mrc->next->Code);
			}
		}

	return (ns);
	}


// --------------------------------------------------------------------------
// smartbaud(): sets baud rate according to result codes
//
// Return value:
//	TRUE: Found baud
//	FALSE: Didn't find baud
//	CERROR: NO CARRIER, ERROR, NO DIALTONE, etc.
//
// Notes:
//	Must return TRUE or FALSE or CERROR, else dumbmodem 7 breaks.

int smartbaud(void)
	{
	TI()UsingCompression = FALSE;
	TI()UsingCorrection = FALSE;

	char str[128];

	while (getModStr(str, sizeof(str) - 1))
		{
		ResultCodeEnum code;

		code = FindResultCode(str);

		switch (code)
			{
			case MR_RUNAPPLIC:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(1380), LABELSIZE);
				RunApplication(NextResultCode(str), NULL, TRUE, TRUE);
				break;
				}

			case MR_NOCARRIER:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(171), LABELSIZE);
				return (CERROR);
				}

			case MR_ERROR:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(172), LABELSIZE);
				return (CERROR);
				}

			case MR_NODIALTONE:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(173), LABELSIZE);
				return (CERROR);
				}

			case MR_BUSY:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(174), LABELSIZE);
				return (CERROR);
				}

			case MR_NOANSWER:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(175), LABELSIZE);
				return (CERROR);
				}

			case MR_COMPRESSION:
				{
				TI()UsingCompression = TRUE;
				break;
				}

			case MR_CORRECTION:
				{
				TI()UsingCorrection = TRUE;
				break;
				}

			case MR_CONNECT300C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT300E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT300:
				{
				CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
						CitWindowsMsg(NULL, getmsg(1641),
						bauds[PS_300]);

				TI()SerialPort.SetSpeed(PS_300);
				CopyStringToBuffer(TI()modem_result, getmsg(167), LABELSIZE);
				TI()ModemSpeed = MS_300;

				if (w)
					{
					destroyCitWindow(w, FALSE);
					}

				return (TRUE);
				}

			case MR_CONNECT600C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT600E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT600:
				{
				CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
						CitWindowsMsg(NULL, getmsg(1641),
						bauds[PS_600]);

				TI()SerialPort.SetSpeed(PS_600);
				CopyStringToBuffer(TI()modem_result, getmsg(167), LABELSIZE);
				TI()ModemSpeed = MS_600;

				if (w)
					{
					destroyCitWindow(w, FALSE);
					}

				return (TRUE);
				}

			case MR_CONNECT1200C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT1200E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT1200:
				{
				CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
						CitWindowsMsg(NULL, getmsg(1641),
						bauds[PS_1200]);

				TI()SerialPort.SetSpeed(PS_1200);
				CopyStringToBuffer(TI()modem_result, getmsg(168), LABELSIZE);
				TI()ModemSpeed = MS_1200;

				if (w)
					{
					destroyCitWindow(w, FALSE);
					}

				return (TRUE);
				}

			case MR_CONNECT2400C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT2400E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT2400:
				{
				CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
						CitWindowsMsg(NULL, getmsg(1641),
						bauds[PS_2400]);

				TI()SerialPort.SetSpeed(PS_2400);
				CopyStringToBuffer(TI()modem_result, getmsg(169), LABELSIZE);
				TI()ModemSpeed = MS_2400;

				if (w)
					{
					destroyCitWindow(w, FALSE);
					}

				return (TRUE);
				}

			case MR_CONNECT4800C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT4800E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT4800:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(181), LABELSIZE);
				TI()ModemSpeed = MS_4800;
				return (TRUE);
				}

			case MR_CONNECT7200C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT7200E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT7200:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(182), LABELSIZE);
				TI()ModemSpeed = MS_7200;
				return (TRUE);
				}

			case MR_CONNECT9600C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT9600E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT9600:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(170), LABELSIZE);
				TI()ModemSpeed = MS_9600;
				return (TRUE);
				}

			case MR_CONNECT12000C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT12000E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT12000:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(176), LABELSIZE);
				TI()ModemSpeed = MS_12000;
				return (TRUE);
				}

			case MR_CONNECT14400C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT14400E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT14400:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(177), LABELSIZE);
				TI()ModemSpeed = MS_14400;
				return (TRUE);
				}

			case MR_CONNECT16800C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT16800E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT16800:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(183), LABELSIZE);
				TI()ModemSpeed = MS_16800;
				return (TRUE);
				}

			case MR_CONNECT19200C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT19200E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT19200:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(178), LABELSIZE);
				TI()ModemSpeed = MS_19200;
				return (TRUE);
				}

			case MR_CONNECT21600C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT21600E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT21600:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(178), LABELSIZE);
				TI()ModemSpeed = MS_21600;
				return (TRUE);
				}

			case MR_CONNECT24000C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT24000E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT24000:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(178), LABELSIZE);
				TI()ModemSpeed = MS_24000;
				return (TRUE);
				}

			case MR_CONNECT26400C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT26400E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT26400:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(178), LABELSIZE);
				TI()ModemSpeed = MS_26400;
				return (TRUE);
				}

			case MR_CONNECT28800C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT28800E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT28800:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(776), LABELSIZE);
				TI()ModemSpeed = MS_28800;
				return (TRUE);
				}

			case MR_CONNECT38400C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT38400E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT38400:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(179), LABELSIZE);
				TI()ModemSpeed = MS_38400;
				return (TRUE);
				}

			case MR_CONNECT57600C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT57600E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT57600:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(180), LABELSIZE);
				TI()ModemSpeed = MS_57600;
				return (TRUE);
				}

			case MR_CONNECT115200C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT115200E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT115200:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(159), LABELSIZE);
				TI()ModemSpeed = MS_115200;
				return (TRUE);
				}

			case MR_CONNECT230400C:
				TI()UsingCompression = TRUE;
			case MR_CONNECT230400E:
				TI()UsingCorrection = TRUE;
			case MR_CONNECT230400:
				{
				CopyStringToBuffer(TI()modem_result, getmsg(783), LABELSIZE);
				TI()ModemSpeed = MS_230400;
				return (TRUE);
				}
			}
		}

	return (FALSE);
	}


// --------------------------------------------------------------------------
// checkring(): Checks for #RING (MDMRESLT.CIT) and sends answer string to
//	modem if found.
//
// Notes:
//	This is also where #RUNAPPLIC in MDMRESLT.CIT is processed.

static void checkring(void)
	{
	if (TI()SerialPort.IsInputReady())
		{
		char str[128];

		// don't be interrupted by cron events
		Cron.ResetTimer();

		// make this pause quite small
		pause(25);

		// to deal with the time from "CONNECT" to gotcarrier() being true
		if (SerialPort.HaveCarrier())
			{
			return;
			}

		getModStr(str, sizeof(str) - 1);

		if (FindResultCode(str) == MR_RUNAPPLIC)
			{
			CopyStringToBuffer(TI()modem_result, getmsg(1380), LABELSIZE);
			RunApplication(NextResultCode(str), NULL, TRUE, TRUE);
			return;
			}

		if (cfg.dialring[0])
			{
			if (	(FindResultCode(str) == MR_RING) &&

					// abs in case you set your clock back
					(abs((int) (time(NULL) - TI()lastringtime)) >
						(long) cfg.dialringwait))
				{
				CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
						CitWindowsMsg(NULL, getmsg(1640));

				pause(50);
				outstring(cfg.dialring);
				outstring(br);
				time(&TI()lastringtime);

				if (w)
					{
					destroyCitWindow(w, FALSE);
					}
				}
			}
		}
	}


// --------------------------------------------------------------------------
// carrier(): Checks carrier.
//
// Return value:
//	TRUE: Carrier found
//	FALSE: Carrier not found

Bool carrier(void)
	{
	int c;

	// ignore carrier according to uphours & updays
	if (TI()SerialPort.IsDisabled() ||
			(!(cfg.uphours[hour()] && cfg.updays[dayofweek()]) &&
			!cfg.ignore_uptime))
		{
		TI()SerialPort.Flush();
		return (FALSE);
		}

	// detect #RING in MDMRESLT.CIT?
	checkring();

	if (SerialPort.HaveCarrier())
		{
		switch (cfg.dumbmodem)
			{
			case 0: // smartbaud!
			case 3: // smartbaud! dial_ring (obs)
				{
				c = smartbaud();
				break;
				}

			case 1: // returns
				{
				c = findbaud();
				break;
				}

			case 2: // HS on RI
				{
				ringdetectbaud();
				c = TRUE;
				break;
				}

			default:
			case 4: // forced
				{
				smartbaud(); // hmm. I dunno about this
				TI()SerialPort.SetSpeed(cfg.initbaud);
				c = TRUE;
				break;
				}

			case 5: // forced
				{
				c = smartbaud();
				if (c)								// perhaps c == TRUE
					{
					if (TI()ModemSpeed > MS_2400 || TI()UsingCompression)
						{
						TI()SerialPort.SetSpeed(cfg.initbaud);
						}
					}

				c = TRUE;
				break;
				}

			case 6: 	// downshift
				{
				c = smartbaud();
				if (c)								// perhaps c == TRUE
					{
					if (TI()ModemSpeed > MS_2400 || TI()UsingCompression)
						{
						TI()SerialPort.SetSpeed(cfg.initbaud);
						}
					else
						{
						// 300, 1200, 2400
						CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
								CitWindowsMsg(NULL, getmsg(1641),
								connectbauds[TI()ModemSpeed]);

						dumbmodemHangup();
						TI()SerialPort.Enable();
						outstring(cfg.downshift);
						outstring(br);
						pause(50);

						if (w)
							{
							destroyCitWindow(w, FALSE);
							}
						}
					}

				c = TRUE;
				break;
				}

			case 7: // Practical Peripherals:
					// "CARRIER", DCD on, "PROTOCOL", "CONNECT"
					// comparing with TRUE is evil but more efficient
				{
				c = FALSE;
				while (SerialPort.HaveCarrier() && (c != TRUE)) 	// (c==FALSE || c==CERROR)
					{
					c = smartbaud();
					if (c == TRUE)	// (c!=FALSE && c!=CERROR)
						{
						if (TI()ModemSpeed > MS_2400 || TI()UsingCompression)
							{
							TI()SerialPort.SetSpeed(cfg.initbaud);
							}
						else
							{
							// 300, 1200, 2400
							CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
									CitWindowsMsg(NULL, getmsg(1641),
									connectbauds[TI()ModemSpeed]);

							dumbmodemHangup();
							TI()SerialPort.Enable();
							outstring(cfg.downshift);
							outstring(br);
							pause(50);

							if (w)
								{
								destroyCitWindow(w, FALSE);
								}
							}
						}
					}

				break;
				}
			}

		if (c == TRUE)
			{
			carrdetect();
			StatusLine.Update();
			return (TRUE);
			}
		else
			{
			StatusLine.Update();

			CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
					CitWindowsMsg(NULL, getmsg(1611));

			Initport();

			if (w)
				{
				destroyCitWindow(w, FALSE);
				}

			return (FALSE);
			}
		}
	else
		{
		return (FALSE);
		}
	}
