// --------------------------------------------------------------------------
// Citadel: Edit.CPP
//
// Message editor and related code.

#include "ctdl.h"
#pragma hdrstop

#include "extedit.h"
#include "room.h"
#include "tallybuf.h"
#include "net.h"
#include "group.h"
#include "viewlog.h"
#include "msg.h"
#include "log.h"
#include "hall.h"
#include "menus.h"
#include "blurbs.h"
#include "events.h"
#include "helpfiles.h"
#include "filerdwr.h"
#include "miscovl.h"
#include "term.h"


// --------------------------------------------------------------------------
// Contents
//
// editText()		handles the end-of-message-entry menu.
// putheader()		prints header for message entry
// getText()		reads a message from the user
// matchString()	searches for match to given string.
// replaceString()	corrects typos in message entry
// wordcount()		talleys # lines, word & characters message contains
// xPutStr()		Put a string to a file w/o trailing blank
// GetFileMessage() gets a null-terminated string from a file


void spellCheck(char *buf, int lim);
void replaceString(char *buf, int lim, Bool ver, char *oldStr);
static void wordcount(const char *buf);

// --------------------------------------------------------------------------
// editText(): Handles the end-of-message-entry menu.
//
// Input:
//	Message *EditBuffer: The message to edit
//	int lim: The maximum length allowed
//	Message *HoldBuffer: Where to copy of the message if Abort; set to NULL
//		if calling for non-message-editing reasons. (.FE)
//
// Return value:
//	TRUE: User picked Save
//	FALSE: User picked Abort
//	CERROR: User picked Continue

static int editText(Message *EditBuffer, int lim, Message *HoldBuffer)
	{
	int ch, ich;

	VerifyHeap();

	strcpy(TI()gprompt, getmsg(403));
	SetDoWhat(PROMPT);

	TI()UserControl.Reset();
	ansi(14);
	TI()OC.MCI_goto = FALSE;

	do
		{
		TI()UserControl.SetOutFlag(IMPERVIOUS);

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

		Bool MayAltF3Timeout = FALSE;

		// We prepare for CheckTimeOnSystem calling a Terminate()...
		if (TI()altF3Timeout)
			{
			TI()CurrentUser->SetMessage(EditBuffer);
			MayAltF3Timeout = TRUE;
			}

		// Then we check the time...
		CheckTimeOnSystem();

		// Then we undo our preparation.
		TI()CurrentUser->SetMessage(NULL);

		CRmPrintf(getmsg(403));

		// If we were not in dager of it, or it didn't happen, get input
		if (!MayAltF3Timeout || TI()altF3Timeout)
			{
			ch = toupper(ich = iCharNE());
			}
		else
			{
			// Else abort.
			SetDoWhat(DUNO);
			return (FALSE);
			}

		if (RunExternalEditor(ch, MAXTEXT, EditBuffer->GetTextPointer()))
			{
			continue;
			}

		switch (ch)
			{
			case ESC:
			case 'A':
				{
				mPrintfCR(getmsg(653));

				if (	// nothing to lose
						!*EditBuffer->GetText() ||

						// user doesn't want to be asked
						!TI()CurrentUser->IsConfirmAbort() ||

						// user confirmed
						getYesNo(getmsg(57), 0))
					{
					if (HoldBuffer)
						{
						*HoldBuffer = *EditBuffer;
						}

					SetDoWhat(DUNO);
					return (FALSE);
					}

				break;
				}

#ifdef GOODBYE
			case 'B':
				{
				char a[80], b[80];

				getString("word 1", a, 79, FALSE, ECHO, ns);
				getString("word 2", b, 79, FALSE, ECHO, ns);

				strupr(a);
				strupr(b);

				mPrintf("\n match: %d\n ", match(a, b));
				break;
				}
#endif

			case 'C':
				{
				mPrintfCR(getmsg(404));
				doCR();

				if (HoldBuffer)
					{
					TI()UserControl.SetOutFlag(OUTOK);

					putheader(FALSE, EditBuffer);
					doCR();
					}

				int textlen = strlen(EditBuffer->GetText());

				do
					{
					EditBuffer->GetTextPointer()[textlen] = '\0';
					} while (textlen > 0 && isspace(EditBuffer->GetTextPointer()[--textlen]));

				if (textlen < MAXTEXT - 1)
					{
					if (textlen < 130 || TI()CurrentUser->IsVerboseContinue())
						{
						mFormat(EditBuffer->GetText());
						}
					else
						{
						char *textptr, *textend;

						for (textptr = EditBuffer->GetTextPointer(),
								textend = textptr + textlen;
								(textend - textptr) >= 129; ++textptr)
							{
							if (*textptr == CTRL_A)
								{
								char wordBuf[MAXWORD + 8];
								int i;

								i = getWord((uchar *) wordBuf,
										(uchar *) textptr, 0, MAXWORD);

								if (IsColorOrFormattingOnly(wordBuf + 1))
									{
									termCap(wordBuf + 1);
									}

								textptr += i;
								ansi(14);
								TI()OC.MCI_goto = FALSE;
								}
							}

						mFormat(textptr);
						}

					ResetOutputControl();
					TI()UserControl.SetOutFlag(IMPERVIOUS);

					return (CERROR);
					}

				mPrintfCR(getmsg(1585));

				break;
				}

			case 'D':
				{
				if (HoldBuffer)
					{
					mPrintfCR(getmsg(405), cfg.Lmsg_nym);
					}
				else
					{
					mPrintfCR(getmsg(624));
					}

				if (getYesNo(getmsg(57), 0))
					{
					EditBuffer->SetText(ns);

					TI()UserControl.SetOutFlag(OUTOK);
					doCR();

					if (HoldBuffer)
						{
						putheader(FALSE, EditBuffer);
						doCR();
						}

					return (CERROR);
					}

				break;
				}

			case 'E':
				{
				if (HoldBuffer)
					{
					mPrintfCR(getmsg(994), cfg.Lmsg_nym);

					if (cfg.allowCrypt == 2 ||
							(cfg.allowCrypt && *EditBuffer->GetToUser()))
						{
						label InputString;

						GetStringWithBlurb(getmsg(993), InputString,
								LABELSIZE, EditBuffer->GetEncryptionKey(),
								B_ENCRYPT);

						if (*InputString)
							{
							normalizeString(InputString);
							EditBuffer->SetEncryptionKey(InputString);
							}
						}
					else
						{
						if (cfg.allowCrypt)
							{
							CRmPrintfCR(getmsg(190));
							}
						else
							{
							CRmPrintfCR(getmsg(7));
							}
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case 'F':
				{
				mPrintfCR(getmsg(409));
				replaceString(EditBuffer->GetTextPointer(), lim, TRUE, NULL);
				break;
				}

			case 'G':
				{
				if (HoldBuffer)
					{
					label GroupName;

					mPrintfCR(getmsg(236), cfg.Ugroup_nym);

					do
						{
						getString(cfg.Lgroup_nym, GroupName, LABELSIZE, TRUE,
								ECHO, EditBuffer->GetGroup());

						if (*GroupName == '?')
							{
							ListGroups(TRUE);
							}
						} while (*GroupName == '?');

					if (*GroupName)
						{
						normalizeString(GroupName);

						if (*GroupName)
							{
							g_slot GroupSlot;

							GroupSlot = FindGroupByPartialName(GroupName,
									FALSE);

							if (GroupSlot == CERROR ||
									!TI()CurrentUser->IsInGroup(GroupSlot))
								{
								CRmPrintf(getmsg(947), cfg.Lgroup_nym);
								}
							else
								{
								EditBuffer->SetGroup(GroupData->GetEntry(
										GroupSlot)->GetName());
								}
							}
						else
							{
							EditBuffer->SetGroup(ns);
							}
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case 'I':
			case '_':
				{
				if (HoldBuffer)
					{
					char InputString[100];

					mPrintfCR(getmsg(17));

					GetStringWithBlurb(getmsg(416), InputString, 90,
							EditBuffer->GetUserSignature(), B_SIGNATUR);

					if (*InputString)
						{
						normalizeString(InputString);
						EditBuffer->SetUserSignature(InputString);
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case 'L':
				{
				if (HoldBuffer)
					{
					mPrintfCR(getmsg(407));
					CRmPrintfCR(getmsg(408), cfg.Lroom_nym,
							GetRoomName(TI()thisRoom), cfg.Lhall_nym,
							HallData->GetEntry(TI()thisHall)->GetName());
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case 'P':
				{
				mPrintfCR(getmsg(410));
				doCR();
				TI()UserControl.SetOutFlag(OUTOK);

				if (HoldBuffer)
					{
					putheader(FALSE, EditBuffer);
					doCR();
					}

				mFormat(EditBuffer->GetText());
				ResetOutputControl();

				if (HoldBuffer)
					{
					if ((*EditBuffer->GetSignature() ||
							*EditBuffer->GetUserSignature()) &&
							TI()CurrentUser->IsViewSignatures())
						{
						termCap(TERM_BOLD);
						doCR();
						CRmPrintf(getmsg(625));
						termCap(TERM_NORMAL);

						if (*EditBuffer->GetUserSignature())
							{
							CRmPrintf(pcts, EditBuffer->GetUserSignature());
							}

						if (*EditBuffer->GetSignature())
							{
							CRmPrintf(pcts, EditBuffer->GetSignature());
							}
						}
					}

				termCap(TERM_NORMAL);
				TI()UserControl.SetOutFlag(IMPERVIOUS);

				doCR();
				break;
				}

			case 'R':
				{
				mPrintfCR(getmsg(411));
				replaceString(EditBuffer->GetTextPointer(), lim, FALSE, NULL);
				break;
				}

			case 'S':
				{
				if (HoldBuffer)
					{
					mPrintf(getmsg(850), cfg.Umsg_done, cfg.Lmsg_nym);
					}
				else
					{
					mPrintf(getmsg(626));
					}

				if (TI()CurrentUser->GetSpellCheckMode() == 3)
					{
					CRCRmPrintfCR(getmsg(973));
					spellCheck(EditBuffer->GetTextPointer(), lim);
					}

				if (TI()CurrentUser->IsConfirmSave())
					{
					if (TI()CurrentUser->GetSpellCheckMode() != 3)
						{
						doCR();
						}

					if (!getYesNo((TI()CurrentUser->GetSpellCheckMode() == 3) ?
								getmsg(964) : getmsg(57), 1))
						{
						break;
						}
					}

				if (strlen(EditBuffer->GetText()) < 2 &&
						!*EditBuffer->GetSubject() &&
						!*EditBuffer->GetLinkedApplication())
					{
					if (HoldBuffer)
						{
						mPrintfCR(getmsg(412), cfg.Lmsg_nym);
						TI()MS.Entered--;
						}
					}
				else
					{
					if (!TI()CurrentUser->IsConfirmSave() &&
							TI()CurrentUser->GetSpellCheckMode() != 3)
						{
						doCR();
						}

					RunAutomaticExternalEditors(MAXTEXT,
							EditBuffer->GetTextPointer());
					}

				if (HoldBuffer)
					{
					TI()MS.Entered++;	// increment # messages entered
					cyclesignature();	// load a new signature from CONFIG.CIT
					doEvent(EVT_SAVEMSG);
					}

				SetDoWhat(DUNO);
				return (TRUE);
				}

			case 'U':
				{
				if (HoldBuffer)
					{
					char InputString[100];

					mPrintfCR(getmsg(1223));

					GetStringWithBlurb(getmsg(415), InputString, 80,
							EditBuffer->GetSubject(), B_SUBJECT);

					if (*InputString)
						{
						normalizeString(InputString);
						EditBuffer->SetSubject(InputString);
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case 'V':
				{
				mPrintfCR(getmsg(37));
				spellCheck(EditBuffer->GetTextPointer(), lim);
				break;
				}

			case 'W':
				{
				mPrintfCR(getmsg(413));
				doCR();
				wordcount(EditBuffer->GetText());
				break;
				}

			case '~':
				{
				if (HoldBuffer)
					{
					AllowESC = changeYesNo(getmsg(627), AllowESC);
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case '?':
				{
				oChar('?');
				showMenu(HoldBuffer ? M_EDIT : M_EDITTEXT);
				break;
				}

			case '!':
				{
				if (HoldBuffer)
					{
					mPrintfCR(getmsg(1394));

					if (onConsole && TI()CurrentUser->IsSysop())
						{
						char InputString[128];

						GetStringWithBlurb(getmsg(1392), InputString, 127,
								EditBuffer->GetLinkedApplication(), B_LINKAPP);

						if (*InputString)
							{
							normalizeString(InputString);
							EditBuffer->SetLinkedApplication(InputString);
							}
						}
					else
						{
						BadMenuSelection(ich);
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case '@':
				{
				if (HoldBuffer)
					{
					mPrintfCR(getmsg(628));

					if (TI()loggedIn)
						{
						label SaveToUser;
						label SaveToNodeName;
						label SaveToRegion;
						label SaveDestinationAddress;

						strcpy(SaveToUser, EditBuffer->GetToUser());
						strcpy(SaveToNodeName, EditBuffer->GetToNodeName());
						strcpy(SaveToRegion, EditBuffer->GetToRegion());
						strcpy(SaveDestinationAddress, EditBuffer->GetDestinationAddress());

						char destination[LABELSIZE + LABELSIZE + LABELSIZE + LABELSIZE + 7];

						do
							{
							getString(getmsg(1134), destination,
									LABELSIZE + LABELSIZE + LABELSIZE + 6,
									TRUE, ECHO, ns);

							normalizeString(destination);
							stripansi(destination);

							if (*destination == '?')
								{
								ListUsers(TI()CurrentUser->IsAide());
								}
							} while (*destination == '?');

						if (*destination)
							{
							if (SameString(destination, spc))
								{
								EditBuffer->SetToUser(ns);
								EditBuffer->SetToNodeName(ns);
								EditBuffer->SetToRegion(ns);
								EditBuffer->SetDestinationAddress(ns);
								}
							else
								{
								label ToUser, ToNode, ToRegion, ToAddress;

								parseNetAddress(destination,
									ToUser, ToNode, ToRegion, ToAddress);

								if (!*ToUser)
									{
									strcpy(ToUser, getmsg(1020));
									}

								EditBuffer->SetToUser(ToUser);
								EditBuffer->SetToNodeName(ToNode);
								EditBuffer->SetToRegion(ToRegion);
								EditBuffer->SetDestinationAddress(ToAddress);
								}
							}

						if (*EditBuffer->GetToUser())
							{
							if (!ResolveMessageAddressingIssues(EditBuffer, MODEM))
								{
								EditBuffer->SetToUser(SaveToUser);
								EditBuffer->SetToNodeName(SaveToNodeName);
								EditBuffer->SetToRegion(SaveToRegion);
								EditBuffer->SetDestinationAddress(SaveDestinationAddress);
								}
							}

						if (*EditBuffer->GetToUser())
							{
							if ((SameString(EditBuffer->GetToUser(), getmsg(1020))) ||
								(SameString(EditBuffer->GetToUser(), getmsg(1019))) ||
								(SameString(EditBuffer->GetAuthor(), getmsg(1020))) ||
								(SameString(EditBuffer->GetAuthor(), getmsg(1019))))
								{
								TI()OC.Echo = BOTH;
								setio();
								}
							else
								{
								TI()OC.Echo = CALLER;
								setio();
								}
							}
						}
					else
						{
						CRmPrintfCR(getmsg(239));
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case '#':
				{
				if (HoldBuffer)
					{
					// special delivery options
					mPrintfCR(getmsg(982));

					if (*EditBuffer->GetToUser())
						{
						EditBuffer->SetReceiptConfirmationRequested(
								getYesNo(getmsg(983),
								EditBuffer->IsReceiptConfirmationRequested()));
						}
					else
						{
						CRmPrintfCR(getmsg(984));
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case '*':
				{
				if (HoldBuffer)
					{
					mPrintfCR(getmsg(423));

					if (cfg.titles && TI()CurrentUser->IsViewTitleSurname())
						{
						label InputString;

						GetStringWithBlurb(getmsg(253), InputString,
								LABELSIZE, EditBuffer->GetTitle(),
								B_MNAMECHG);

						if (*InputString)
							{
							normalizeString(InputString);
							EditBuffer->SetTitle(InputString);
							}
						}

					if (TI()CurrentUser->IsSysop())
						{
						label InputString;

						GetStringWithBlurb(getmsg(424), InputString,
								LABELSIZE, EditBuffer->GetAuthor(),
								B_MNAMECHG);

						if (*InputString)
							{
							normalizeString(InputString);
							EditBuffer->SetAuthor(InputString);
							}
						}

					if (cfg.surnames && TI()CurrentUser->IsViewTitleSurname())
						{
						label InputString;

						GetStringWithBlurb(getmsg(254), InputString,
								LABELSIZE, EditBuffer->GetSurname(),
								B_MNAMECHG);

						if (*InputString)
							{
							normalizeString(InputString);
							EditBuffer->SetSurname(InputString);
							}
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			case '<':
				{
				if (TI()CurrentUser->IsSysop() && onConsole)
					{
					char *TextFromFile = new char[MAXTEXT];

					if (TextFromFile)
						{
						char FileName[80];

						mPrintfCR(getmsg(421));

						GetStringWithBlurb(getmsg(1073), FileName, 79, ns,
								B_FILENSRT);

						normalizeString(FileName);

						if (*FileName)
							{
							FILE *fd;

							if ((fd = fopen(FileName, FO_RB)) != NULL)
								{
								GetFileMessage(fd, TextFromFile, MAXTEXT - 1);
								fclose(fd);

								EditBuffer->AppendText(TextFromFile);
								}
							else
								{
								CRmPrintfCR(getmsg(422));
								}
							}

						delete [] TextFromFile;
						}
					else
						{
						CRmPrintfCR(getmsg(188), getmsg(360));
						}
					}
				else
					{
					BadMenuSelection(ich);
					}

				break;
				}

			default:
				{
				BadMenuSelection(ich);
				break;
				}
			}
		} while ((HaveConnectionToUser()));

	SetDoWhat(DUNO);
	return (FALSE);
	}


// --------------------------------------------------------------------------
// putheader(): Prints header for message entry.
//
// Input:
//	Bool first: TRUE if the first time called for the message (Right after
//		the Enter command is picked, not during Continue or Print formatted)
//	Message *Msg: The message

void putheader(Bool first, Message *Msg)
	{
	char dtstr[80];
	const EchoType oldEcho = TI()OC.Echo;

	if (first)
		{
		TI()OC.Echo = BOTH;
		setio();
		}

	termCap(TERM_BOLD);

	if (!IsRoomAnonymous(TI()thisRoom))
		{
		strftime(dtstr, 79, (TI()loggedIn) ?
				TI()CurrentUser->GetDateStamp() : cfg.datestamp, 0l);

		mPrintf(getmsg(1576), dtstr);
		}
	else
		{
		mPrintf(getmsg(1486));
		}

	mPrintf(getmsg(425));

	if (!IsRoomAnonymous(TI()thisRoom))
		{
		if (Msg->GetTitle()[0] && TI()CurrentUser->IsViewTitleSurname() &&
				((cfg.titles &&
				!(Msg->GetOriginNodeName()[0])) || cfg.nettitles))
			{
			mPrintf(getmsg(846), Msg->GetTitle());
			}

		mPrintf(getmsg(845), Msg->GetAuthor());

		if ((Msg->GetSurname()[0] && TI()CurrentUser->IsViewTitleSurname() &&
				((cfg.surnames && !(Msg->GetOriginNodeName()[0])) || cfg.netsurname)) ||
				(SameString(Msg->GetAuthor(), getmsg(1019)) ||
				SameString(Msg->GetAuthor(), getmsg(1020))))
			{
			mPrintf(getmsg(844), Msg->GetSurname());
			}
		}
	else
		{
		mPrintf(getmsg(843), cfg.anonauthor);
		}

	if (Msg->GetToUser()[0])		mPrintf(getmsg(426), Msg->GetToUser());
	if (Msg->GetForward()[0])		mPrintf(getmsg(427), Msg->GetForward());
	if (Msg->GetToNodeName()[0])	mPrintf(getmsg(211), Msg->GetToNodeName());
	if (Msg->GetToRegion()[0])		mPrintf(getmsg(229), Msg->GetToRegion());
	if (Msg->GetToCountry()[0]) 	mPrintf(getmsg(229), Msg->GetToCountry());
	if (Msg->GetGroup()[0]) 		mPrintf(getmsg(1040), Msg->GetGroup());

	if (Msg->IsLocal())
		{
		mPrintf(getmsg(985), cfg.Lmsg_nym);
		}

	if (Msg->IsReceiptConfirmationRequested())
		{
		mPrintf(getmsg(992));
		}

	if (*Msg->GetPublicToUser())
		{
		CRmPrintf(getmsg(1049), Msg->GetPublicToUser());
		}

	if (*Msg->GetSubject() && TI()CurrentUser->IsViewSubjects())
		{
		CRmPrintf(getmsg(1048), Msg->GetSubject());
		}

	termCap(TERM_NORMAL);

	TI()OC.Echo = oldEcho;
	setio();
	}


// --------------------------------------------------------------------------
// getText(): Reads a message from the user
//
// Input:
//	Message *Msg: A place to put the message, or NULL if just editing block
//		of text
//	char *Buffer: A place to put a block of test, or NULL if editing a
//		message
//
// Return value:
//	TRUE: User wants it saved
//	FALSE: User doesn't want it saved

#define GTWORDSIZE	50		// For wordwrap, replace in log extensions

Bool getText(Message *Msg, char *Buffer)
	{
	Bool done, beeped = FALSE;
	char c = 0, *buf;
	int i, toReturn;

	VerifyHeap();

	AllowESC = FALSE;

	if (Msg)
		{
		if (!TI()CurrentUser->IsExpert())
			{
			dispBlb(B_ENTRY);
			TI()UserControl.SetOutFlag(OUTOK);

			CRmPrintfCR(getmsg(430), ltoac(MAXTEXT));
			mPrintfCR(getmsg(431), cfg.Lmsg_nym);
			}

		TI()UserControl.SetOutFlag(IMPERVIOUS);

		doCR();
		putheader(TRUE, Msg);
		doCR();

		buf = Msg->GetTextPointer();
		}
	else
		{
		buf = Buffer;
		}

	TI()UserControl.SetOutFlag(OUTOK);

	for (	// look at the end
			i = strlen(buf);

			// and strip out any whitespace (is isspace(10) == TRUE???)
			// isspace() matches: spc, FF, NL, CR, HT, VT
			i > 0 && isspace(buf[i - 1]);

			// and keep going
			i--)
		{
		buf[i - 1] = 0;
		}

	VerifyHeap();

	mFormat(buf);
	ResetOutputControl();

	TI()UserControl.SetOutFlag(IMPERVIOUS);

	done = FALSE;

	VerifyHeap();

	do
		{
		VerifyHeap();

		int termPos = 0;
		label tempTerm;

		tempTerm[0] = 0;

		SetDoWhat(MESSAGETEXT);
		i = strlen(buf);

		char word[GTWORDSIZE + 1];
		int wsize = 0;

		int ReplaceCharCounter = 0;

		while (!done && i < (MAXTEXT - 1) && (HaveConnectionToUser()))
			{
			c = iCharNE();

			if (ReplaceCharCounter)
				{
				ReplaceCharCounter--;
				}

			if (termPos)
				{
				if (c == '\b')
					{
					if (termPos > 2)
						{
						termPos -= 1;
						}
					else
						{
						termPos = 0;
						}
					}
				else
					{
					tempTerm[termPos++] = c;
					tempTerm[termPos] = 0;

					if (c == 'X')
						{
						if (i + termPos >= (MAXTEXT - 1))
							{
							oChar(BELL);
							}
						else
							{
							strcat(buf, tempTerm);
							i += termPos;
							termCap(tempTerm + 1);
							ansi(14);
							TI()OC.MCI_goto = FALSE;
							}

						termPos = 0;
						}
					else if (termPos >= LABELSIZE)
						{
						oChar(BELL);
						termPos = 0;
						}
					}
				}
			else
				{
				switch (c)						// Analyze what they typed
					{
					case CTRL_A:				// CTRL+A>nsi
						{
						const int d = iCharNE();

						if (ReplaceCharCounter)
							{
							ReplaceCharCounter--;
							}

						if (d == '?')
							{
							dispHlp(H_ANSI);
							TI()UserControl.SetOutFlag(OUTOK);

							for (i = strlen(buf);
									i > 0 && buf[i - 1] == '\n'; i--)
								{
								buf[i-1] = 0;
								}

							doCR(2);

							if (Msg)
								{
								TI()UserControl.SetOutFlag(OUTOK);

								putheader(FALSE, Msg);
								doCR();
								}

							mFormat(buf);
							ResetOutputControl();

							TI()UserControl.SetOutFlag(IMPERVIOUS);
							}
						else if (d == 'X')
							{
							tempTerm[0] = CTRL_A;
							tempTerm[1] = 'X';
							tempTerm[2] = 0;
							termPos = 2;
							}
						else if (
								(d >= '0' && d <= '8')      ||
								(d >= 'A' && d <= 'H')      ||
								(d == TERM_DATE[0]) 		||
								(d == TERM_TIME[0]) 		||
								(d == TERM_USERNAME[0]) 	||
								(d == TERM_FIRSTNAME[0])	||
								(d == TERM_POOP[0]) 		||
								(d == TERM_RND_BACK[0]) 	||
								(d == TERM_RND_FORE[0]) 	||
								(d >= 'a' && d <= 'h')
								)
							{
							if ((d != '5') && (d != '6'))
								{
								tempTerm[0] = d;
								tempTerm[1] = 0;

								termCap(tempTerm);
								}

							buf[i++] = 0x01;
							buf[i++] = d;
							}
						else
							{
							oChar(BELL);
							}

						break;
						}

					case 26:						// Control+Z
						{
						done = TRUE;

						if (Msg)
							{
							TI()MS.Entered++;
							}

						break;
						}

					case '\b':                      // Backspace
						{
						if (i > 0 && buf[i - 1] == '\t')    // Tab
							{
							doBS();
							i--;

							while ((TI()OC.CrtColumn % 8) != 1)
								{
								doBS();
								}
							}
						else if (i > 0 && buf[i - 1] != '\n')
							{
							doBS();
							i--;

							if (wsize > 0)
								{
								wsize--;
								}
							}
						else
							{
							oChar(BELL);
							}

						break;
						}

					case '\n':                          // Newline
						{
						if (!i || (buf[i - 1] == '\n'))
							{
							done = TRUE;
							break;
							}
						}

					case ESC:
						{
						if (c == ESC && !AllowESC)
							{
							echocharacter(BELL);
							break;
							}
						}

					default:
						{
						if ((ispunct(c) || isspace(c)) && wsize &&
								!ReplaceCharCounter)
							{
							// #replace in logext
							word[wsize] = 0;

							for (const pairedStrings *cur = TI()CurrentUser->
									GetReplacePointer(); cur;
									cur = (pairedStrings *) getNextLL(cur))
								{
								if (SameString(word, cur->string1))
									{
									// Make sure there is nothing else in the
									// keyboard buffer to mess us up...
									// KeyboardBuffer.Flush();

									// Here are some assumptions: if word
									// is initial cap, make the replacement
									// be initial cap. if it is all cap, make
									// the replacement be all cap. else,
									// replace in replacement's case.
									Bool InitialCap = isupper(word[0]);

									Bool AllCap = TRUE;
									for (int j = 0; j <= wsize; j++)
										{
										if (islower(word[j]))
											{
											AllCap = FALSE;
											break;
											}
										}

									// Okay... we know the case. Now do it

									// First, insert our original character
									char Wow[2];
									Wow[0] = c;
									Wow[1] = 0;
									KeyboardBuffer.InsertString(Wow);
									ReplaceCharCounter = 2; // This character plus 1

									// Create a buffer for creating the
									// replacements...
									int StringLength = strlen(cur->string2);

									if (wsize > StringLength)
										{
										StringLength = wsize;
										}

									char *String = new char[StringLength + 1];

									if (String)
										{
										// Now insert our new text...
	Bool DoneFirst = FALSE, InANSI = FALSE, InMCI = FALSE;
	for (j = 0; j < strlen(cur->string2); j++)
		{
		char Cur = cur->string2[j];

		// If this is ^A/^B, start ANSI
		if (Cur == CTRL_A || Cur == CTRL_B)
			{
			InANSI = TRUE;
			}

		// If this is X, start extended MCI
		if (InANSI && toupper(Cur) == 'X')
			{
			InMCI = TRUE;
			}

		// Capitalize if needed, but only if not in ANSI or MCI
		if (AllCap || (InitialCap && !DoneFirst) && !InANSI && !InMCI)
			{
			String[j] = toupper(Cur);
			}
		else
			{
			String[j] = Cur;
			}

		// We have done our first "normal" character... no more initial cap
		if (!InANSI && !InMCI)
			{
			DoneFirst = TRUE;
			}

		// At the first X, InANSI is still TRUE, so InMCI stays on. At
		// the second X, InANSI is now FALSE, so InMCI turns off.
		if (InMCI && toupper(Cur) == 'X');
			{
			InMCI = InANSI;
			}

		// If we have done a non-^A/^B character, ANSI turns off.
		if (Cur != CTRL_A && Cur != CTRL_B)
			{
			InANSI = FALSE;
			}
		}
	String[j] = 0;



										if (KeyboardBuffer.InsertString(String))
											{
											ReplaceCharCounter += strlen(String);

											// And insert BSes before that...
											for (j = 0; j < wsize; j++)
												{
												String[j] = '\b';
												}
											String[j] = 0;

											if (KeyboardBuffer.InsertString(String))
												{
												ReplaceCharCounter += wsize;
												}
											}

										delete [] String;
										}

									// And skip our character for now...
									c = 0;

									break;
									}
								}
							}

						if (c && (c == '\n' || c == ' ' || c == '\t' ||
								wsize >= GTWORDSIZE))
							{
							wsize = 0;
							}
						else if (c)
							{
							if (TI()OC.CrtColumn >=
									TI()CurrentUser->GetWidth() - 1)
								{
								if (wsize)
									{
									doBS(wsize);

									doCR();

									for (int j = 0; j < wsize; j++)
										{
										echocharacter(word[j]);
										}
									}
								else
									{
									doCR();
									}
								}

							word[wsize] = c;
							wsize++;
							}

						if (c)
							{
							echocharacter(c);
							buf[i++] = c;

							if (i > MAXTEXT - 80 && !beeped)
								{
								beeped = TRUE;
								oChar(BELL);
								}
							}

						break;
						}
					}
				}

			buf[i] = 0; 						// null to terminate message

			if (i >= (MAXTEXT - 1))
				{
				mPrintfCR(getmsg(432));
				KeyboardBuffer.Flush();
				}
			}

		done = FALSE;							// In case they Continue

		termCap(TERM_NORMAL);

		if (c == 26 && i != (MAXTEXT - 1))		// if was Control+Z
			{
			buf[i++] = '\n';                    // end with newline
			buf[i] = 0;
			toReturn = TRUE;					// yes, save it

			CRmPrintf(getmsg(850), cfg.Umsg_done, cfg.Lmsg_nym);

			if (Msg)
				{
				if (strlen(buf) > 1 || (Msg &&
						(*Msg->GetSubject() || *Msg->GetLinkedApplication())))
					{
					// load a new signature from config.cit
					cyclesignature();
					}
				else
					{
					mPrintf(getmsg(412), cfg.Lmsg_nym);
					TI()MS.Entered--;
					}
				}

			doCR();
			}
		else						// done or lost carrier
			{
			if (Msg)
				{
				delete TI()MS.AbortedMessage;
				TI()MS.AbortedMessage = NULL;

				compactMemory();

				TI()MS.AbortedMessage = new Message;

				if (TI()MS.AbortedMessage)
					{
					toReturn = editText(Msg, (MAXTEXT - 1),
							TI()MS.AbortedMessage);

					VerifyHeap();

					// editText() might indirectly delete TI()MS.AbortedMessage
					// if Alt+F3 timeout happens
					if (TI()MS.AbortedMessage &&
							(toReturn || !*TI()MS.AbortedMessage->GetText()))
						{
						delete TI()MS.AbortedMessage;
						TI()MS.AbortedMessage = NULL;
						}
					}
				else
					{
					mPrintf(getmsg(188), getmsg(629));
					toReturn = FALSE;
					}
				}
			else
				{
				Message *Msg1 = new Message;

				if (Msg1)
					{
					Msg1->SetText(buf);

					toReturn = editText(Msg1, (MAXTEXT - 1), NULL);

					CopyStringToBuffer(buf, Msg1->GetText(), MAXTEXT - 1);

					delete Msg1;
					}
				else
					{
					mPrintf(getmsg(188), getmsg(629));
					toReturn = FALSE;
					}
				}
			}
		} while ((toReturn == CERROR) && (HaveConnectionToUser()));
				// CERROR returned from editor means continue

	if (Msg && !HaveConnectionToUser() && (strlen(buf) > 1) && TI()loggedIn)
		{
		jumpback jb;

		jb.hall = TI()thisHall;
		jb.room = TI()thisRoom;
		jb.newpointer = TI()CurrentUser->GetRoomNewPointer(TI()thisRoom);
		jb.bypass = WasRoomBypassed(TI()thisRoom);
		jb.newMsgs = GetRoomNewMessages(TI()thisRoom);

		TI()CurrentUser->JumpbackPush(jb);
		TI()CurrentUser->SetMessage(Msg);
		}

	if (toReturn == TRUE || toReturn == CERROR) // Filter null messages
		{
		toReturn = FALSE;

		for (i = 0; buf[i] != 0 && !toReturn; i++)
			{
			toReturn = (buf[i] > ' ');
			}

		if (!toReturn)
			{
			if (Msg)
				{
				toReturn = *Msg->GetSubject() || *Msg->GetLinkedApplication()
						? TRUE : FALSE;
				}
			}
		}

	return (toReturn);
	}


// --------------------------------------------------------------------------
// matchString(): Searches for match to given string.
//
// Notes:
//	Runs backward through buffer so we get most recent match first.

static char *matchString(char *buf, const char *pattern, char *bufEnd, int ver, Bool exact)
	{
	char *loc, *pc2;
	const char *pc1;
	Bool foundIt;

	for (loc = bufEnd, foundIt = FALSE; !foundIt && --loc >= buf;)
		{
		for (pc1 = pattern, pc2 = loc, foundIt = TRUE; *pc1 && foundIt;)
			{
			if (tolower(*pc1++) != tolower(*pc2++))
				{
				foundIt = FALSE;
				}
			}

		if (foundIt && exact)
			{
			if (
				(loc != buf && isalnum(loc[-1]))
				||
				isalnum(loc[strlen(pattern)])
			)
				{
				foundIt = FALSE;
				}
			}

		if (ver && foundIt)
			{
			char subbuf[11];

			strncpy(subbuf, buf + 10 > loc ? buf : loc - 10,
				(uint) (loc - buf) > 10 ? 10 : (uint) (loc - buf));

			subbuf[(uint) (loc - buf) > 10 ? 10 : (uint) (loc - buf)] = 0;

			CRmPrintf(pcts, subbuf);

			if (*TI()term.bold)
				{
				termCap(TERM_BOLD);
				}
			else
				{
				mPrintf(getmsg(1558));
				}

			mPrintf(pcts, pattern);

			if (*TI()term.bold)
				{
				termCap(TERM_NORMAL);
				}
			else
				{
				mPrintf(getmsg(1559));
				}

			strncpy(subbuf, loc + strlen(pattern), 10);

			subbuf[10] = 0;
			mPrintfCR(pcts, subbuf);

			if (!getYesNo(getmsg(433), 0))
				{
				foundIt = FALSE;
				}
			}
		}

	return (foundIt ? loc : NULL);
	}


// --------------------------------------------------------------------------
// replaceString(): Lets user correct typos in message entry.

void replaceString(char *buf, int lim, Bool ver, char *oldStr)
	{
	char oldString[260];
	char newString[260];
	char *loc, *textEnd;
	char *pc;
	int incr, length;

	// find terminal null
	for (textEnd = buf, length = 0; *textEnd; length++, textEnd++);

	if (oldStr)
		{
		strcpy(oldString, oldStr);
		}
	else
		{
		getString(getmsg(434), oldString, 256, FALSE, ECHO, ns);
		}

	if (!*oldString)
		{
//		doCR();
		return;
		}

	if (!oldStr)
		{
		if ((loc = matchString(buf, oldString, textEnd, ver, FALSE)) == NULL)
			{
			mPrintfCR(getmsg(435));
			return;
			}
		}

	if (!getString(oldStr ? getmsg(774) : getmsg(436), newString, 256, FALSE,
			ECHO, ns))
		{
		return;
		}

	if (oldStr)
		{
		if (!*newString)
			{
			return;
			}

		char *Orig = strdup(oldString);
		char *New = strdup(newString);

		if (Orig && New)
			{
			stripansi(Orig);
			stripansi(New);
			strlwr(Orig);
			strlwr(New);

			char Prompt[256];
			sprintf(Prompt, getmsg(638), Orig, New);

			if (getYesNo(Prompt, 0))
				{
				if (!TI()CurrentUser->AddReplace(Orig, New))
					{
					mPrintf(getmsg(188), getmsg(1110));
					}
				}
			}
		else
			{
			mPrintf(getmsg(188), getmsg(1110));
			}

		delete [] Orig;
		delete [] New;
		}

	do
		{
		if (oldStr)
			{
			if ((loc = matchString(buf, oldString, textEnd, ver, TRUE)) == NULL)
				{
				return;
				}
			}

		for (textEnd = buf, length = 0; *textEnd; length++, textEnd++)
			{
			;
			}

		if ((length + (strlen(newString) - strlen(oldString))) >= lim)
			{
			mPrintfCR(getmsg(432));
			return;
			}

		// delete old string
		for (pc = loc, incr = strlen(oldString); (*pc = *(pc + incr)) != 0;
				pc++);
		textEnd -= incr;

		// make room for new string
		for (pc = textEnd, incr = strlen(newString); pc >= loc; pc--)
			{
			*(pc + incr) = *pc;
			}

		// insert new string
		for (pc = newString; *pc; *loc++ = *pc++);

		textEnd = loc;
		} while (oldStr);
	}

// --------------------------------------------------------------------------
// wordcount(): Counts lines, words, and characters in a block of text, and
//	displays the numbers to the user.
//
// Input:
//	const char *buf: The text to count

static void wordcount(const char *buf)
	{
	int lines = 0, words = 0;

	const char *counter = buf;
	const int chars = strlen(buf);

	while (*counter++)
		{
		if (*counter == ' ')
			{
			if ((*(counter - 1) != ' ') && (*(counter - 1) != '\n'))
				{
				words++;
				}
			}

		 if (*counter == '\n')
			{
			if ((*(counter - 1) != ' ') && (*(counter - 1) != '\n'))
				{
				words++;
				}

			lines++;
			}
		}

	label Words, Chars;
	strcpy(Words, ltoac(words));
	strcpy(Chars, ltoac(chars));
	mPrintfCR(getmsg(437), ltoac(lines), lines == 1 ? ns : justs,
			Words, words == 1 ? ns : justs, Chars, chars == 1 ? ns : justs);
	}


// --------------------------------------------------------------------------
// GetFileMessage(): Gets a string from a file.
//
// Input:
//	FILE *fl: The file to read
//	char *str: Where to put the text
//	int mlen: Maximum length to read
//
// Output:
//	char *str: The read text
//
// Notes:
//	The string is considered to come to an end at a CR, 0xFF, or 0x00.

void GetFileMessage(FILE *fl, char *str, int mlen)
	{
	register int l = 0;

	while (!feof(fl) && l < mlen)
		{
		const char ch = (uchar) fgetc(fl);

		if (ch != '\r' && ch != '\xFF' && ch)
			{
			*str++ = ch;
			l++;
			}
		}

	*str = 0;
	}
