// --------------------------------------------------------------------------
// Citadel: MsgRead.CPP
//
// Stuff for reading messages

#include "ctdl.h"
#pragma hdrstop

#include "room.h"
#include "auxtab.h"
#include "log.h"
#include "group.h"
#include "net.h"
#include "msg.h"
#include "boolexpr.h"
#include "blurbs.h"
#include "events.h"
#include "aplic.h"
#include "miscovl.h"
#include "term.h"


// --------------------------------------------------------------------------
// Contents
//
// printMessage()		prints message on modem and console
// stepMessage()		find the next message in DIR
// showMessages()		is routine to print roomful of msgs
// printheader()		prints current message header
// GetMessageHeader()	reads a message off disk into RAM.

void ShowWhyMayNotSeeMsg(MaySeeMsgType Why, ulong MsgID);

#define SEARCHUPDATETIME	5

static Bool IsRecipientOfMail(const Message *Msg);
static void printheader(Message *Msg);

void PrintMessageByID(ulong ID)
	{
	Message *Msg = LoadMessageByID(ID, TRUE, TRUE);

	if (Msg)
		{
		delete Msg;
		}
	}


// --------------------------------------------------------------------------
// LoadMessageByID(): Loads a message into RAM from MSG.DAT based on its
//	numeric ID.
//
// Input:
//	ulong ID:			The message ID to load.
//	Bool CheckMaySeeIt: TRUE to only load the message if the user is allowed
//						to see it; FALSE to always load the message.
//	Bool Display:		TRUE to display the message (or error messages if the
//						message could not be loaded) to the user; FALSE to
//						not.
//
// Return value:
//	Message *:			A pointer to the message if it could be loaded, NULL
//						if it couldn't.

Message *LoadMessageByID(ulong ID, Bool CheckMaySeeIt, Bool Display)
	{
	m_slot slot;

	slot = indexslot(ID);

	if (slot == M_SLOT_ERROR)
		{
		if (Display)
			{
			DebugOut(getdbmsg(34), ltoac(ID));
			}

		return (NULL);
		}

	if (CheckMaySeeIt)
		{
		const MaySeeMsgType MaySeeIt = MaySeeIndexMsg(slot);

		if (MaySeeIt != MSM_GOOD)
			{
			if (debug && Display)
				{
				ShowWhyMayNotSeeMsg(MaySeeIt, ID);
				}

			return (NULL);
			}
		}

	Bool referenceFlag;
	ulong referenceID;
	r_slot referenceRoom;
	uchar referenceAttr;

	Bool iter = FALSE;

	do
		{
		slot = indexslot(ID);

		if (slot == M_SLOT_ERROR)
			{
			if (Display)
				{
				DebugOut(getdbmsg(34), ltoac(ID));
				}

			return (NULL);
			}

		if (!getFlags(slot)->IsInuse())
			{
			if (Display)
				{
				mPrintfCR(getmsg(142), cfg.Umsg_nym);
				}

			return (NULL);
			}

		if (!iter)
			{
			referenceFlag = getFlags(slot)->IsCopy();
			referenceRoom = getRoomNum(slot);
			referenceID = ID;
			referenceAttr = 0;
			referenceAttr |= getFlags(slot)->IsReceived()	? ATTR_RECEIVED : 0;
			referenceAttr |= getFlags(slot)->IsReply()		? ATTR_REPLY	: 0;
			referenceAttr |= getFlags(slot)->IsMadevis()	? ATTR_MADEVIS	: 0;
			referenceAttr |= getFlags(slot)->IsCensored()	? ATTR_CENSORED : 0;
			}

		if (getFlags(slot)->IsCopy())
			{
			if (getCopy(slot) <= slot)
				{
				assert(getCopy(slot));

				if (getCopy(slot))
					{
					ID -= getCopy(slot);
					}
				else
					{
					// Copy of self? Something's wrong... give up.
					return (NULL);
					}
				}
			else
				{
				// Copied message has scrolled
				return (NULL);
				}
			}

		iter = TRUE;
		} while (getFlags(slot)->IsCopy());

	ulong LocationInFile = getLocation(slot);

	if (LocationInFile != ULONG_ERROR)
		{
		Message *Msg = new Message;

		if (Msg)
			{
			ReadMessageStatus Status;

			bufferedWinReOpen(msgfl);
			bufferedSeek(msgfl, LocationInFile);
			Status = Msg->ReadAll(RMC_NORMAL, NULL);
			bufferedWinCloseTmp(msgfl);

			if (Status != RMS_OK)
				{
				if (Display)
					{
					mPrintfCR(getmsg(143));
					}

				delete Msg;
				return (NULL);
				}

			if (atol(Msg->GetLocalID()) != ID)
				{
				if (Display)
					{
					label Byte;
					strcpy(Byte, ltoac(LocationInFile));
					mPrintfCR(getmsg(1007), cfg.Lmsg_nym, ltoac(ID), Byte);
					}

				delete Msg;
				return (NULL);
				}

			Msg->SetViewDuplicate(referenceFlag);
			Msg->SetOriginalID(referenceID);
			Msg->SetOriginalAttribute(referenceAttr);
			Msg->SetOriginalRoom(referenceRoom);

			PrepareMsgScriptInit(Msg);
			doEvent(EVT_LOADMESSAGE, InitMsgScript);

			if (!CheckMsgScript(Msg))
				{
				delete Msg;
				return (NULL);
				}

			if (Display)
				{
				PrintMessage(Msg);
				}

			return (Msg);
			}
		else
			{
			if (Display)
				{
				mPrintf(getmsg(188), getmsg(144));
				}

			return (NULL);
			}
		}
	else
		{
		if (Display)
			{
			label Byte;
			strcpy(Byte, ltoac(LocationInFile));

			DebugOut(getdbmsg(35), Byte, ltoac(ID));
			}

		return (NULL);
		}
	}


Bool LogExtensions::IsKillNode(const char *Node) const
	{
	assert(this);
	const strList *theStr;

	for (theStr = le_knode; theStr; theStr = (strList *) getNextLL(theStr))
		{
		if (SameString(theStr->string, Node))
			{
			return (TRUE);
			}
		}

	return (FALSE);
	}

Bool LogExtensions::IsKillText(const char *Text) const
	{
	assert(this);
	const strList *theStr;

	for (theStr = le_ktext; theStr; theStr = (strList *) getNextLL(theStr))
		{
		if (IsSubstr(Text, theStr->string))
			{
			return (TRUE);
			}
		}

	return (FALSE);
	}

Bool LogExtensions::IsKillRegion(const char *Region) const
	{
	assert(this);
	const strList *theStr;

	for (theStr = le_kreg; theStr; theStr = (strList *) getNextLL(theStr))
		{
		if (SameString(theStr->string, Region))
			{
			return (TRUE);
			}
		}

	return (FALSE);
	}

Bool LogExtensions::IsKillUser(const char *User) const
	{
	assert(this);
	const strList *theStr;

	for (theStr = le_kuser; theStr; theStr = (strList *) getNextLL(theStr))
		{
		if (SameString(theStr->string, User))
			{
			return (TRUE);
			}
		}

	return (FALSE);
	}


// for lack of a better place and function name, not prototyped above
static Bool FindTextInComments(const Message *Msg, const char *SearchText)
	{
	const long NumCmt = Msg->GetCommentCount();

	for (long Idx = 1; Idx <= NumCmt; Idx++)
		{
		if (IsSubstr(Msg->GetComment(Idx), SearchText))
			{
			return (TRUE);
			}
		}
	return (FALSE);
	}


void ActuallyDisplayMessage(Message *Msg, Bool ShowHeader, Bool ShowBody,
		Bool ShowSignature)
	{
	if (ShowHeader)
		{
		printheader(Msg);
		}

	if (ShowBody)
		{
		if (Msg->GetFileLink()[0])
			{
			dumpf(Msg->GetFileLink(), 1, FALSE);
			}
		else
			{
			mFormat(Msg->GetText());
			ResetOutputControl();

			doCR();
			}

		if ((Msg->GetSignature()[0] || Msg->GetUserSignature()[0]) &&
				ShowSignature)
			{
			termCap(TERM_BOLD);

			CRmPrintf(getmsg(1575));

			termCap(TERM_NORMAL);

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

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

			doCR();
			}
		}
	}

// --------------------------------------------------------------------------
// PrintMessage(): Displays message on modem and console
//
// Input:
//	Message *Msg: Message to display

void PrintMessage(Message *Msg)
	{
	Bool HeaderPrinted = FALSE;

	if (	TI()CurrentUser->CanModerateRoom(Msg->GetOriginalRoom()) ||

			(TI()loggedIn &&

				SameString(TI()CurrentUser->GetName(), Msg->GetAuthor())))
		{
		TI()UserControl.SetCanK(TRUE);
		}

	if (TI()CurrentUser->IsAide())
		{
		TI()UserControl.SetCanM(TRUE);
		TI()UserControl.SetCanStar(TRUE);
		}

	if (TI()loggedIn)
		{
		TI()UserControl.SetCanAt(TRUE);
		}

	TI()UserControl.SetCanBang(TRUE);
	TI()UserControl.SetCanR(TRUE);

	TI()MRO.DotoMessage = NO_SPECIAL;

	const MaySeeMsgType MaySeeIt = MaySeeMsg(Msg);

	if (MaySeeIt != MSM_GOOD)
		{
		if (debug)
			{
			ShowWhyMayNotSeeMsg(MaySeeIt, atol(Msg->GetLocalID()));
			}

		TI()UserControl.ResetCanFlags();
		return;
		}

	// SearchUser From Acit
	if (TI()rf.User)
		{
		if (	!u_match(deansi(Msg->GetToUser()),				TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetSubject()), 			TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetOriginSoftware()),		TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetAuthor()),				TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetForward()), 			TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetOriginNodeName()),		TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetOriginRegion()),		TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetOriginCountry()),		TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetToNodeName()),			TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetToRegion()),			TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetOriginPhoneNumber()),	TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetToPhoneNumber()),		TI()rf.SearchUser) &&
				!u_match(deansi(Msg->GetToCountry()),			TI()rf.SearchUser))
			{
			TI()UserControl.ResetCanFlags();
			return;
			}
		}

	if (TI()rf.Group)
		{
		TI()GroupNameForNameTester = Msg->GetGroup();

		if (!EvaluateBooleanExpression(TI()rf.SearchGroup,
				GroupNameTester))
			{
			TI()UserControl.ResetCanFlags();
			return;
			}
		}

	if (TI()MRO.Date)
		{
		if (TI()MRO.DatePrevious)
			{
			if (atol(Msg->GetCreationTime()) >= TI()MRO.CheckDate)
				{
				TI()UserControl.ResetCanFlags();
				return;
				}
			}
		else
			{
			if (atol(Msg->GetCreationTime()) < TI()MRO.CheckDate)
				{
				TI()UserControl.ResetCanFlags();
				return;
				}
			}
		}

	if (TI()CurrentUser->IsKillNode(Msg->GetOriginNodeName()) ||
			TI()CurrentUser->IsKillRegion(Msg->GetOriginRegion()) ||
			TI()CurrentUser->IsKillUser(Msg->GetAuthor()) ||
				(
				Msg->IsEncrypted() &&
				TI()CurrentUser->IsExcludeEncryptedMessages()
				)
			)
		{
		if (!TI()CurrentUser->IsHideMessageExclusions())
			{
			CRmPrintfCR(getmsg(346),
					*Msg->GetToUser() ? getmsg(230) : ns,
					*Msg->GetToUser() ? cfg.Lmsg_nym : cfg.Umsg_nym);

			if (!*Msg->GetToUser())
				{
				TI()displayedmessage = TRUE;	// a substituted msg, anyway
				}
			}

		TI()UserControl.ResetCanFlags();
		return;
		}

	Msg->Decompress();

	if (Msg->IsEncrypted() && TI()UserControl.GetOutFlag() == OUTOK)
		{
		// need to get key
		printheader(Msg);
		HeaderPrinted = TRUE;

		mPrintfCR(getmsg(979));

		label Key;

		GetStringWithBlurb(getmsg(978), Key, LABELSIZE,
				Msg->GetEncryptionKey(), B_ENCRYPT);
		normalizeString(Key);

		TI()UserControl.SetOutFlag(OUTOK);

		if (!*Key)
			{
			TI()UserControl.ResetCanFlags();
			return;
			}

		Msg->SetEncryptionKey(Key);

		doCR();
		if (decrypt(Msg->GetTextPointer(), Key))
			{
			Msg->SetEncrypted(FALSE);
			}
		}

	if (TI()rf.Text)
		{
		const char *T = TI()rf.SearchText;

		if (!
				(
					FindTextInComments(Msg, 			T) ||
					IsSubstr(Msg->GetAuthor(),			T) ||
					IsSubstr(Msg->GetTitle(),			T) ||
					IsSubstr(Msg->GetOriginCountry(),	T) ||
					IsSubstr(Msg->GetToCountry(),		T) ||
					IsSubstr(Msg->GetForward(), 		T) ||
					IsSubstr(Msg->GetGroup(),			T) ||
					IsSubstr(Msg->GetOriginNodeName(),	T) ||
					IsSubstr(Msg->GetOriginRegion(),	T) ||
					IsSubstr(Msg->GetCreationRoom(),	T) ||
					IsSubstr(Msg->GetToUser(),			T) ||
					IsSubstr(Msg->GetSurname(), 		T) ||
					IsSubstr(Msg->GetToNodeName(),		T) ||
					IsSubstr(Msg->GetToRegion(),		T) ||
					IsSubstr(Msg->GetUserSignature(),	T) ||
					IsSubstr(Msg->GetSubject(), 		T) ||
					IsSubstr(Msg->GetSignature(),		T) ||
					IsSubstr(Msg->GetText(),			T) ||

					(
						TI()MRO.Verbose &&

						(
						IsSubstr(Msg->GetFromPath(),		T) ||
						IsSubstr(Msg->GetToPath(),			T) ||
						IsSubstr(Msg->GetOriginSoftware(),	T) ||
						IsSubstr(Msg->GetSourceID(),		T) ||
						IsSubstr(Msg->GetTwitRegion(),		T) ||
						IsSubstr(Msg->GetTwitCountry(), 	T) ||
						IsSubstr(Msg->GetOriginPhoneNumber(),T) ||
						IsSubstr(Msg->GetToPhoneNumber(),	T)
						)
					)
				)
			)
			{
			TI()UserControl.ResetCanFlags();
			return;
			}
		}

	if (TI()CurrentUser->IsKillText(Msg->GetText()))
		{
		if (!TI()CurrentUser->IsHideMessageExclusions())
			{
			CRmPrintfCR(getmsg(346),
					*Msg->GetToUser() ? getmsg(230) : ns,
					*Msg->GetToUser() ? cfg.Lmsg_nym : cfg.Umsg_nym);

			if (!*Msg->GetToUser())
				{
				TI()displayedmessage = TRUE;	// a substituted msg, anyway
				}
			}

		TI()UserControl.ResetCanFlags();
		return;
		}

	if (!TI()OC.UseMCI && substr(Msg->GetText(), getmsg(1555), FALSE) != CERROR)
		{
		CRmPrintfCR(getmsg(977), cfg.Umsg_nym);
		TI()UserControl.ResetCanFlags();
		return;
		}

	TI()MS.Read++;	// Increment # messages read
	doEvent(EVT_READMSG);

	if (TI()CurrentUser->IsClearScreenBetweenMessages() &&
			TI()CurrentUser->IsIBMANSI())
		{
		TI()numLines = 0;
		mPrintf("[2J");
		}

	TI()displayedmessage = TRUE;

	ActuallyDisplayMessage(Msg, !HeaderPrinted, !TI()MRO.Headerscan,
			TI()CurrentUser->IsViewSignatures());

	if (!TI()MRO.Headerscan)
		{
		TI()seen = TRUE;
		}

	TI()UserControl.ResetCanFlags();
	termCap(TERM_NORMAL);
	TI()OC.Echo = BOTH;
	setio();

	if (StatusLine.IsFullScreen())
		{
		StatusLine.Update();
		}

	if (*Msg->GetLinkedApplication())
		{
		RunApplication(Msg->GetLinkedApplication(), NULL, TRUE, TRUE);
		}
	}


static void AdjustDownForRoom(ulong *Number)
	{
	if (*Number < cfg.mtoldest)
		{
		*Number = cfg.mtoldest;
		}
	if (*Number > cfg.newest)
		{
		*Number = cfg.newest;
		}

	if (!TI()MRO.All)
		{
		m_slot Slot = indexslot(*Number);

		assert(Slot != M_SLOT_ERROR);

		while (*Number >= cfg.mtoldest && getRoomNum(Slot) != TI()thisRoom)
			{
			(*Number)--;
			Slot--;
			}
		}
	}

static void AdjustUpForRoom(ulong *Number)
	{
	if (*Number < cfg.mtoldest)
		{
		*Number = cfg.mtoldest;
		}
	if (*Number > cfg.newest)
		{
		*Number = cfg.newest;
		}

	if (!TI()MRO.All)
		{
		m_slot Slot = indexslot(*Number);

		assert(Slot != M_SLOT_ERROR);

		while (*Number <= cfg.newest && getRoomNum(Slot) != TI()thisRoom)
			{
			(*Number)++;
			Slot++;
			}
		}
	}


static
#if !defined(AUXMEM) && !defined(WINCIT)
inline
#endif
void DoTheStep(int Direction, m_slot *Slot)
	{
	#if defined(AUXMEM) || defined(WINCIT)
		if (1 == Direction)
			{
			if (TI()MRO.All)
				{
				++(*Slot);
				}
			else
				{
				if (*Slot == M_SLOT_ERROR)
					{
					*Slot = FirstMessageInRoom[TI()thisRoom];
					}
				else
					{
					*Slot = indexslot(getNextRoomMsg(*Slot));
					}
				}
			}
		else
			{
			if (TI()MRO.All)
				{
				--(*Slot);
				}
			else
				{
				if (*Slot == M_SLOT_ERROR)
					{
					*Slot = LastMessageInRoom[TI()thisRoom];
					}
				else
					{
					*Slot = indexslot(getPrevRoomMsg(*Slot));
					}
				}
			}
	#else
		if (1 == Direction)
			{
			++(*Slot);
			}
		else
			{
			--(*Slot);
			}
	#endif
	}

static Bool ShouldShowMessage(m_slot TableEntry)
	{
	return (	// It's a good message and...
				TableEntry != M_SLOT_ERROR &&

				// not reading by mail, or it is mail, and...
				(
				!TI()rf.Mail ||
				getFlags(TableEntry)->IsMail() ||
				getFlags(TableEntry)->IsMassemail()
				) &&

				// not reading by group, or it is group, and...
				(
				!TI()rf.Group ||
				getFlags(TableEntry)->IsLimited() ||
				getFlags(TableEntry)->IsMassemail()
				) &&

				// Not reading public, or it is public, and...
				(
				!TI()rf.Public ||
					(
					!getFlags(TableEntry)->IsLimited() &&
					!getFlags(TableEntry)->IsMail() &&
					!getFlags(TableEntry)->IsMassemail()
					)
				) &&

				// Not reading local, or it is local.
				(
				!TI()rf.Local ||
				!getFlags(TableEntry)->IsNetworked()
				)
			);
	}

// --------------------------------------------------------------------------
// stepMessage(): Find the next message in Direction.
//
// Input:
//	ulong *at: Message to start at
//	int Direction: 1 for forward; -1 for backwards
//
// Output:
//	ulong *at: Where we ended up
//
// Return value:
//	TRUE: We were able to find another message
//	FALSE: We were not able to find another message

static Bool stepMessage(ulong *at, int Direction)
	{
	m_slot i, tablesize;

	#if defined(AUXMEM) || defined(WINCIT)
		if (!TI()MRO.All && *at == M_SLOT_ERROR)
			{
			if (1 == Direction)
				{
				*at = FirstMessageInRoom[TI()thisRoom];
				}
			else
				{
				*at = LastMessageInRoom[TI()thisRoom];
				}

			return (*at != M_SLOT_ERROR);
			}
	#endif

	if (*at < cfg.mtoldest)
		{
		if (1 == Direction)
			{
			*at = cfg.mtoldest;
			}
		else
			{
			return (FALSE);
			}
		}

	i = indexslot(*at);
	tablesize = sizetable();

	for (DoTheStep(Direction, &i);
			i != M_SLOT_ERROR && i < tablesize;
			DoTheStep(Direction, &i))
		{
		#if !defined(AUXMEM) && !defined(WINCIT)
			// skip messages not in this room: already done in DoTheStep()
			// for Windows and Auxmem versions.

			if (!TI()MRO.All && getRoomNum(i) != TI()thisRoom)
				{
				continue;
				}
		#endif

		if (!ShouldShowMessage(i))
			{
			continue;
			}

		const MaySeeMsgType MaySeeIt = MaySeeIndexMsg(i);

		if (MaySeeIt == MSM_GOOD)
			{
			*at = (ulong) (cfg.mtoldest + i);
			return (TRUE);
			}
		else
			{
			if (debug)
				{
				ShowWhyMayNotSeeMsg(MaySeeIt, i + cfg.mtoldest);
				}
			}
		}

	return (FALSE);
	}

static void SuspendJournalFile(FILE **save)
	{
	if (journalfl)
		{
		*save = journalfl;
		journalfl = NULL;
		}
	else
		{
		*save = NULL;
		}
	}

static void RestoreJournalFile(FILE *saved)
	{
	journalfl = saved;
	}

static Bool SetMessageReadingOptions(void)
	{
	// Get message number from user.
	if (TI()MRO.Number)
		{
		Bool good;
		FILE *tmp;
		char prompt[128];

		SuspendJournalFile(&tmp);

		do
			{
			good = TRUE;

			label Oldest;
			strcpy(Oldest, ltoac(cfg.mtoldest));

			CRmPrintfCR(getmsg(1008), Oldest, ltoac(cfg.newest));

			sprintf(prompt, getmsg(1009), cfg.Lmsg_nym);
			TI()MRO.MessageNumber = getNumber(prompt, 0, LONG_MAX, -1, FALSE, NULL);

			if (TI()MRO.MessageNumber < 0)
				{
				TI()MRO.MessageNumber = 0;
				}

			if (!TI()MRO.MessageNumber)
				{
				TI()MRO.Number = FALSE;
				}
			else
				{
				if ((TI()MRO.MessageNumber < cfg.mtoldest) ||
						(TI()MRO.MessageNumber > cfg.newest))
					{
					mPrintfCR(getmsg(1010));
					good = FALSE;
					}
				}
			} while (!good);

		if (tmp)
			{
			mPrintf(getmsg(368), cfg.Lmsgs_nym);
			RestoreJournalFile(tmp);
			}
		}

	// Get message date from user.
	if (TI()MRO.Date)
		{
		char prompt[128];
		label usrIn;
		datestruct ds;
		struct date m_date;
		struct time m_time;
		label tmp;
		Bool first = TRUE;

		do
			{
			if (!HaveConnectionToUser())
				{
				return (FALSE);
				}

			if (!first)
				{
				mPrintfCR(getmsg(1080));
				}

			first = FALSE;

			if (!GetStringWithBlurb(getmsg(1011), usrIn, LABELSIZE, ns,
					B_DATESET))
				{
				return (FALSE);
				}

			gdate(usrIn, &ds);
			} while (!ds.Date);

		m_date.da_year = ds.Year + 1900;	// i hate dostounix
		m_date.da_mon = ds.Month + 1;		// i hate dostounix
		m_date.da_day = ds.Date;

		m_time.ti_min = 0;
		m_time.ti_hour = 0;
		m_time.ti_sec = 0;
		m_time.ti_hund = 0;

		TI()MRO.CheckDate = dostounix(&m_date, &m_time);
		strftime(tmp, LABELSIZE, getmsg(1431), TI()MRO.CheckDate);

		sprintf(prompt, getmsg(1014), cfg.Lmsgs_nym, tmp);

		int Res = GetOneKey(prompt, getmsg(499), getmsg(499)[0], B_BA,
				getmsg(539), getmsg(540));

		if (Res == CERROR)
			{
			return (FALSE);
			}

		TI()MRO.DatePrevious = !Res;
		}

	return (ProcessReadFilter(&TI()rf, FALSE));
	}

static Bool ShouldAskToRelease(const Message *Msg)
	{
	if (	// Problem/Moderated message
			*Msg->GetX() &&

			// And we saw it
			TI()seen &&

			// And it hasn't already been released
			!Msg->IsMadeVisible() &&

			// And this message is not in Aide) or Dump>
			Msg->GetOriginalRoom() != AIDEROOM &&
			Msg->GetOriginalRoom() != DUMP &&

			// And we haven't stopped or nexted the output
			TI()UserControl.CanOutput() &&

			// And we can moderate in this message's room
			TI()CurrentUser->CanModerateRoom(Msg->GetOriginalRoom())
			)
		{
		return (TRUE);
		}
	else
		{
		return (FALSE);
		}
	}

static Bool IsRecipientOfMail(const Message *Msg)
	{
	return (// Logged in
			TI()loggedIn &&

			// And it is mail
			*Msg->GetToUser() &&

			// And it is not mail bound for elsewhere
			!(Msg->IsNetworkedMail() && !Msg->IsNetworkedMailForHere()) &&

			// And it is to us, because of one of the following...
				(
				// mail to us
				SameString(Msg->GetToUser(), TI()CurrentUser->GetName()) ||

				// mail to Aide and we are aide
				(SameString(Msg->GetToUser(), getmsg(1019)) &&
						TI()CurrentUser->IsAide()) ||

				// mail to Sysop and we are sysop
				(SameString(Msg->GetToUser(), getmsg(1020)) &&
						TI()CurrentUser->IsSysop()) ||

				// forwarded to us
				SameString(Msg->GetForward(), TI()CurrentUser->GetName()) ||

				// mass e-mail
				Msg->IsMassEMail()
				)
			);
	}

static Bool ShouldAskToReply(const Message *Msg, OldNewPick whichMess)
	{
	return (!TI()MRO.Headerscan &&

				(	// We asked to reply to it
					(TI()MRO.DotoMessage == REPLY) ||

					(
					// Only when reading new
					(whichMess == NewOnly) &&

					// and we are the recipient
					IsRecipientOfMail(Msg)
					)
				)

			&& !Msg->IsAnonymous()
			);
	}


// --------------------------------------------------------------------------
// showMessages(): Print roomful of msgs
//
// Input:
//	OldNewPick whichMess: Which ones to read
//	Bool revOrder: TRUE to read them backwards; FALSE for forward
//	long howMany: How many to read. (0 for all?)

#if defined(AUXMEM) || defined(WINCIT)
	#define GetNewest() 	(TI()MRO.All ? cfg.newest : 	\
							LastMessageInRoom[TI()thisRoom])
	#define GetOldest() 	(TI()MRO.All ? cfg.mtoldest :	\
							FirstMessageInRoom[TI()thisRoom])
#else
	#define GetNewest() 	(cfg.newest)
	#define GetOldest() 	(cfg.mtoldest)
#endif

void showMessages(OldNewPick whichMess, Bool revOrder, long howMany)
	{
	ulong	lowLimit;				// The oldest message to display
	ulong	highLimit;				// The newest message to display
	ulong	msgNo;					// The message being displayed

	Bool	pausemsg;				// Should we pause message
	int 	increment;				// +1 or -1: how to get to next msg
	Bool	done;					// Done with all messages.

	Bool	*roomCheck = NULL;		// For .Read All...

	if (!SetMessageReadingOptions())
		{
		return;
		}

	TI()UserControl.SetOutFlag(OUTOK);

	ShowHelpMessage(577);

	#if defined(AUXMEM) || defined(WINCIT)
		if (!TI()MRO.All && FirstMessageInRoom[TI()thisRoom] == M_SLOT_ERROR)
			{
			return;
			}
	#endif

	// Set the highLimit and lowLimit.
	switch (whichMess)
		{
		case NewOnly:
			{
			highLimit = GetNewest();

			if (TI()loggedIn)
				{
				lowLimit = TI()CurrentUser->GetRoomNewPointer(TI()thisRoom) + 1;
				if (lowLimit < GetOldest())
					{
					lowLimit = GetOldest();
					}
				}
			else
				{
				lowLimit = GetOldest();
				}

			AdjustDownForRoom(&highLimit);
			AdjustUpForRoom(&lowLimit);

			// Go back one more if Last old on New is turned on.
			if (!revOrder && TI()CurrentUser->IsOldToo() &&
					(highLimit >= lowLimit))
				{
				stepMessage(&lowLimit, -1);
				}

			// Adjust lowLimit if specifying how many to read.
			if (howMany)
				{
				ulong l, hmll;

				hmll = GetNewest();

				AdjustDownForRoom(&hmll);

				for (l = 1; l < howMany; l++)
					{
					if (!stepMessage(&hmll, -1))
						{
						break;
						}
					}

				// Don't read any old, or more than howMany says to read.
				lowLimit = max((long) lowLimit, (long) hmll);
				}

			break;
			}

		case OldAndNew:
			{
			highLimit = GetNewest();

			AdjustDownForRoom(&highLimit);

			if (howMany)
				{
				long l;

				lowLimit = GetNewest();

				AdjustUpForRoom(&lowLimit);

				// Go back howMany messages.
				for (l = 1; l < howMany; l++)
					{
					if (!stepMessage(&lowLimit, -1))
						{
						break;
						}
					}
				}
			else
				{
				lowLimit = GetOldest();

				AdjustUpForRoom(&lowLimit);
				}

			break;
			}

		case OldOnly:
			{
			lowLimit = GetOldest();

			AdjustUpForRoom(&lowLimit);

			if (TI()loggedIn)
				{
				highLimit = TI()CurrentUser->GetRoomNewPointer(TI()thisRoom);
				if (highLimit > GetNewest())
					{
					highLimit = GetNewest();
					}
				}
			else
				{
				highLimit = GetNewest();
				}

			AdjustDownForRoom(&highLimit);

			if (howMany)
				{
				ulong l, hmll;

				hmll = highLimit;

				AdjustDownForRoom(&hmll);

				for (l = 1; l < howMany; l++)
					{
					if (!stepMessage(&hmll, -1))
						{
						break;
						}
					}

				lowLimit = max((long) lowLimit, (long) hmll);
				}

			break;
			}
		}

	// Allow for reverse retrieval
	if (revOrder)
		{
		msgNo = TI()MRO.Number ? TI()MRO.MessageNumber : highLimit;
		increment = -1;

		if (TI()MRO.Number)
			{
			AdjustDownForRoom(&msgNo);
			}
		}
	else
		{
		msgNo = TI()MRO.Number ? TI()MRO.MessageNumber : lowLimit;
		increment = 1;

		if (TI()MRO.Number)
			{
			AdjustUpForRoom(&msgNo);
			}
		}

	if (TI()MRO.All)
		{
		roomCheck = new Bool[cfg.maxrooms];

		if (roomCheck)
			{
			theOtherAlgorithm(roomCheck, TI()thisHall, FALSE);
			}
		else
			{
			mPrintf(getmsg(188), getmsg(152));
			return;
			}
		}

	doEvent(EVT_READMSGS);

	time_t LastMessageReadTime = time(NULL);

	for (	done = ShouldShowMessage(indexslot(msgNo)) ?
				FALSE : !stepMessage(&msgNo, increment);

			!done && msgNo >= lowLimit && msgNo <= highLimit &&
				HaveConnectionToUser();

			done = !stepMessage(&msgNo, increment))
		{
		// all this trouble just to find out if this is going to be the last
		// message we're displaying

		if (TI()CurrentUser->IsPauseBetweenMessages())
			{
			ulong dummymsgno = msgNo;

			const Bool dummydone = !stepMessage(&dummymsgno, increment);

			if (!dummydone && dummymsgno >= lowLimit &&
					dummymsgno <= highLimit && HaveConnectionToUser())
				{
				pausemsg = TRUE;
				}
			}

		// Allow interruptions
		if (BBSCharReady())
			{
			TI()UserControl.CheckInput(FALSE);
			}

		CheckTimeOnSystem();

		if (TI()UserControl.GetOutFlag() != OUTOK)
			{
			if (TI()UserControl.GetOutFlag() == OUTNEXT ||
					TI()UserControl.GetOutFlag() == OUTPARAGRAPH)
				{
				TI()UserControl.SetOutFlag(OUTOK);
				}
			else if (TI()UserControl.GetOutFlag() == OUTSKIP)
				{
				TI()OC.Echo = BOTH;
				setio();

				resetMsgfilter(FALSE);
				pausemsg = FALSE;

				delete [] roomCheck;
				return;
				}
			}

		if (roomCheck && !roomCheck[getRoomNum(indexslot(msgNo))])
			{
			continue;
			}

		TI()seen = FALSE;

		SetDoWhat(READMESSAGE);
		TI()displayedmessage = FALSE;

		Message *Msg = LoadMessageByID(msgNo, TRUE, TRUE);

		SetDoWhat(DUNO);

		if (TI()displayedmessage)
			{
			LastMessageReadTime = time(NULL);
			}
		else if (Msg && (LastMessageReadTime + SEARCHUPDATETIME <= time(NULL)))
			{
			char DateStr[128];

			strftime(DateStr, sizeof(DateStr) - 1, TI()loggedIn ?
					TI()CurrentUser->GetVerboseDateStamp() : cfg.vdatestamp,
					atol(Msg->GetCreationTime()));

			mPrintfCR(getmsg(1587), cfg.Lmsg_nym,
					ltoac(atol(Msg->GetLocalID())), DateStr);

			LastMessageReadTime = time(NULL);
			}

		if ((TI()UserControl.GetOutFlag() != OUTSKIP) &&
				TI()displayedmessage && Msg)
			{
			if (TI()MS.AutoMark)
				TI()MRO.DotoMessage = MARK_IT;

			if (TI()MS.AutoKill)
				TI()MRO.DotoMessage = PULL_IT;

			if (TI()MS.AutoCensor)
				TI()MRO.DotoMessage = CENSOR_IT;

			if (TI()UserControl.GetOutFlag() == OUTOK && TI()CurrentUser->IsPauseBetweenMessages() &&
					pausemsg &&
					(!TI()OC.justdidpause || !TI()CurrentUser->GetLinesPerScreen()) &&

					// Won't be asked to pull, mark, or censor
					(TI()MRO.DotoMessage != PULL_IT) &&
					(TI()MRO.DotoMessage != MARK_IT) &&
					(TI()MRO.DotoMessage != CENSOR_IT) &&

					// Won't be asked to release
					!ShouldAskToRelease(Msg) &&

					// Won't be asked to respond
					!ShouldAskToReply(Msg, whichMess))
				{
				TI()UserControl.CheckInput(TRUE);
				TI()OC.justdidpause = TRUE;
				TI()numLines = 0;
				pausemsg = FALSE;
				}

			if (journalfl)
				{
				FILE *tmp;

				SuspendJournalFile(&tmp);
				mPrintf(pctss, ltoac(msgNo));
				RestoreJournalFile(tmp);
				}
			else
				{
				switch (TI()MRO.DotoMessage)
					{
					case PULL_IT:
						{
						#if defined(AUXMEM) || defined(WINCIT)
							// To keep from reading Dump> after the kill
							ulong TmpMsgNo;

							if (!TI()MRO.All)
								{
								if (1 == increment)
									{
									TmpMsgNo = getPrevRoomMsg(indexslot(msgNo));
									}
								else
									{
									TmpMsgNo = getNextRoomMsg(indexslot(msgNo));
									}
								}
						#endif

						if (pullIt(Msg))
							{
							#if defined(AUXMEM) || defined(WINCIT)
								if (!TI()MRO.All)
									{
									msgNo = TmpMsgNo;
									}
							#endif
							}

						break;
						}

					case MARK_IT:
						{
						markIt(Msg);
						break;
						}

					case CENSOR_IT:
						{
						censorIt(Msg);
						break;
						}

					case REVERSE_READ:
						{
						TI()UserControl.SetOutFlag(OUTOK);

						increment = -increment;
						CRmPrintfCR(getmsg(1017), (increment == 1) ? '+' : '-');
						lowLimit = cfg.oldest;
						highLimit= cfg.newest;	// reevaluate for Livia
						break;
						}

					default:
						{
						if (TI()UserControl.GetOutFlag() == OUTNEXT)
							{
							break;
							}

						if (ShouldAskToRelease(Msg))
							{
							// Release?
							int Res = GetOneKey(getmsg(1018), getmsg(541),
									getmsg(541)[1], B_YNK, getmsg(1101),
									getmsg(1102), getmsg(542));

							if (Res == 0)	// Yes
								{
								markmsg(Msg);
								TI()UserControl.SetOutFlag(OUTOK);
								}
							else if (Res == 2)	// Kill
								{
								#if defined(AUXMEM) || defined(WINCIT)
									// To keep from reading Dump> after the kill
									ulong TmpMsgNo;

									if (!TI()MRO.All)
										{
										if (1 == increment)
											{
											TmpMsgNo = getPrevRoomMsg(indexslot(msgNo));
											}
										else
											{
											TmpMsgNo = getNextRoomMsg(indexslot(msgNo));
											}
										}
								#endif

								if (pullIt(Msg))
									{
									#if defined(AUXMEM) || defined(WINCIT)
										if (!TI()MRO.All)
											{
											msgNo = TmpMsgNo;
											}
									#endif
									}
								}
							}

						// reply to mail
						if (ShouldAskToReply(Msg, whichMess))
							{
							TI()UserControl.SetOutFlag(OUTOK);

							doCR();

							if (getYesNo(getmsg(1021), 1))
								{
								Message *NewMsg = new Message;

								if (NewMsg)
									{
									// First, check for mailing list
									long Idx, Num = Msg->GetCommentCount();
									const char *Cmt;

									for (Idx = 1; Idx <= Num; Idx++)
										{
										Cmt = Msg->GetComment(Idx);

										if (strncmpi(Cmt, getmsg(1350),
												strlen(getmsg(1350))) ==
												SAMESTRING)
											{
											break;
											}
										}

									if (Idx <= Num)
										{
										CRmPrintf(getmsg(1385));
										const int i = toupper(iCharNE());

										if (i == 'M')
											{
											char listname[LABELSIZE+LABELSIZE];
											strcpy(listname, Cmt + strlen(getmsg(1350)));
											char *ptr = strrchr(listname, '@');
											if (ptr)
												{
												ptr[-1] = '\0';     // strip " @ nodename"
												}

											Msg->SetAuthor(listname);	// hack
											mPrintfCR(getmsg(1386)); 
											}
										else if (i == 'U' || i == 'M' || i == CR || i == LF || i == 'Y')
											{
											mPrintfCR(pcts, cfg.Uuser_nym);
											}
										else
											{
											mPrintfCR(getmsg(653));
											continue;
											}
										}

									if (!(SameString(Msg->GetToUser(), getmsg(1019)) ||
											SameString(Msg->GetToUser(), getmsg(1020))))
										{
										if (TI()whichIO != CONSOLE)
											{
											TI()OC.Echo = CALLER;
											setio();
											}
										}

									if (makeMessage(NewMsg, Msg, Msg->GetRoomNumber()))
										{
										if (*Msg->GetToUser())
											{
											Msg->SetOriginalAttribute(Msg->GetOriginalAttribute() | ATTR_REPLY);
											changeheader(Msg->GetOriginalID(),
													INT_MAX, Msg->GetOriginalAttribute());
											}
										}

									// Restore privacy zapped by make...
									if (TI()whichIO != CONSOLE)
										{
										TI()OC.Echo = BOTH;
										}

									TI()UserControl.SetOutFlag(OUTOK);

									// If the mail scrolled messages off
									if (cfg.oldest > lowLimit)
										{
										lowLimit = cfg.oldest;

										if (msgNo < lowLimit)
											{
											msgNo = lowLimit;
											}
										}

									delete NewMsg;
									}
								else
									{
									mPrintf(getmsg(188), getmsg(152));
									}
								}
							}

						break;
						}
					}
				}
			}

		if (Msg)
			{
			delete Msg;
			}
		}

	TI()OC.Echo = BOTH;
	setio();

	resetMsgfilter(FALSE);
	delete [] roomCheck;
	}


// --------------------------------------------------------------------------
// printheader(): Prints current message header.

void LogExtensions::ShowTagsForUser(const char *User) const
	{
	assert(this);
	const pairedStrings *theTag;

	for (theTag = le_tuser; theTag;
			theTag = (pairedStrings *) getNextLL(theTag))
		{
		if (SameString(theTag->string1, User))
			{
			mPrintf(getmsg(156), theTag->string2);
			}
		}
	}

static void printheader(Message *Msg)
	{
	char dtstr[80];
	int madeRec = FALSE;

	if (TI()UserControl.GetOutFlag() == OUTSKIP)
		{
		return;
		}

	if (TI()UserControl.GetOutFlag() == OUTNEXT)
		{
		TI()UserControl.SetOutFlag(OUTOK);
		}

	if (TI()MRO.Verbose)
		{
		// print any unknowns...
		for (unkLst *lul = Msg->GetFirstUnknown(); lul;
				lul = (unkLst *) getNextLL(lul))
			{
			DebugOut(getdbmsg(36), lul->whatField, lul->theValue);
			}
		}

	if (*Msg->GetCreationTime())
		{
		const long timestamp = atol(Msg->GetCreationTime());

		if (TI()MRO.Verbose)
			{
			strftime(dtstr, 79, (TI()loggedIn) ?
					TI()CurrentUser->GetVerboseDateStamp() : cfg.vdatestamp,
					timestamp);
			}
		else
			{
			strftime(dtstr, 79, (TI()loggedIn) ?
					TI()CurrentUser->GetDateStamp() : cfg.datestamp, timestamp);
			}
		}

	if (	// verbose
			TI()MRO.Verbose &&

				(
				// and is a sysop
				TI()CurrentUser->IsSysop() ||

				// or not anonymous
				!(IsRoomAnonymous(Msg->GetRoomNumber()) ||
						Msg->IsAnonymous())
				)
			)
		{
		termCap(TERM_BOLD);
		label LocalID;
		strcpy(LocalID, ltoac(atol(Msg->GetLocalID())));

		CRmPrintf(getmsg(1023), LocalID, ltoac(cfg.newest));

		DebugOut(getdbmsg(37), ltoac(Msg->GetOriginalRoom()));

#ifndef WINCIT
		if (Msg->WasEverCompressed())
			{
			label Orig;
			strcpy(Orig, ltoac(hufOrig));

			DebugOut(getdbmsg(38), Orig, ltoac(hufNew));
			}
#endif

		if (Msg->IsViewDuplicate() && TI()CurrentUser->IsAide())
			{
			mPrintf(getmsg(1026), ltoac(Msg->GetOriginalID()));
			}

		if (*Msg->GetSourceID())
			{
			CRmPrintf(getmsg(1027), ltoac(atol(Msg->GetSourceID())));
			}

		if (*Msg->GetSourceRoomName())
			{
			CRmPrintf(getmsg(448), Msg->GetSourceRoomName());
			}

		if (*Msg->GetFileLink() && TI()CurrentUser->IsSysop())
			{
			CRmPrintf(getmsg(1028), Msg->GetFileLink());
			}

		if (*Msg->GetLinkedApplication() && TI()CurrentUser->IsSysop())
			{
			CRmPrintf(getmsg(1393), Msg->GetLinkedApplication());
			}

		if (*Msg->GetFromPath())
			{
			char lastlocID[5];
			CRmPrintf(getmsg(1029), Msg->GetFromPath());

			// Get last region, this stuff probably not necessary
			int i, j;

			for (i = strlen(Msg->GetFromPath());
					i && Msg->GetFromPath()[i] != '.'; i--);

			for (j = 0, i++;
					Msg->GetFromPath()[i] && Msg->GetFromPath()[i] != '!' &&
					j < 4; i++, j++)
				{
				lastlocID[j] = Msg->GetFromPath()[i];
				}
			lastlocID[j] = 0;

			// if last region was same as current region just use alias
			if (SameString(lastlocID, cfg.locID))
				{
				mPrintf(getmsg(1556), cfg.alias);
				}
			else
				{
				mPrintf(getmsg(1556), cfg.Address);
				}
			}

		if (*Msg->GetOriginSoftware())
			{
			CRmPrintf(getmsg(1030), Msg->GetOriginSoftware());
			}

		if (*Msg->GetToPath())
			{
			CRmPrintf(getmsg(1031), Msg->GetToPath());
			}
		}

	termCap(TERM_BOLD);

	if (IsRoomAnonymous(TI()thisRoom) || Msg->IsAnonymous())
		{
		CRmPrintf(getmsg(1486));
		}
	else
		{
		CRmPrintf(getmsg(1576), dtstr);
		}

	if (*Msg->GetAuthor())
		{
		mPrintf(getmsg(1032));

		if (!IsRoomAnonymous(Msg->GetRoomNumber()) &&
				!Msg->IsAnonymous())
			{
			if (*Msg->GetTitle() && TI()CurrentUser->IsViewTitleSurname() &&
					((cfg.titles && !(*Msg->GetOriginNodeName())) || cfg.nettitles))
				{
				mPrintf(getmsg(846), Msg->GetTitle());
				}

			termCap(TERM_UNDERLINE);
			mPrintf(pcts, Msg->GetAuthor());
			termCap(TERM_NORMAL);

			TI()CurrentUser->ShowTagsForUser(Msg->GetAuthor());

			termCap(TERM_BOLD);

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

	termCap(TERM_BOLD);

	if (Msg->GetOriginNodeName()[0] &&
			(!SameString(Msg->GetOriginNodeName(), cfg.nodeTitle) ||
			!SameString(Msg->GetOriginRegion(), cfg.nodeRegion)) &&
			!SameString(Msg->GetAuthor(), Msg->GetOriginNodeName()))
		{
		mPrintf(getmsg(211), Msg->GetOriginNodeName());
		}

	if (Msg->GetOriginRegion()[0] &&
			!SameString(Msg->GetOriginRegion(), cfg.nodeRegion))
		{
		mPrintf(getmsg(229), Msg->GetOriginRegion());

		if (TI()MRO.Verbose && *Msg->GetTwitRegion())
			{
			mPrintf(getmsg(289), Msg->GetTwitRegion());
			}
		}

	if (Msg->GetOriginCountry()[0] && TI()MRO.Verbose &&
			!SameString(Msg->GetOriginCountry(), cfg.nodeCountry))
		{
		mPrintf(getmsg(229), Msg->GetOriginCountry());

		if (TI()MRO.Verbose && *Msg->GetTwitCountry())
			{
			mPrintf(getmsg(289), Msg->GetTwitCountry());
			}
		}

	if (*Msg->GetCit86Country() && TI()MRO.Verbose)
		{
		mPrintf(getmsg(229), Msg->GetCit86Country());
		}

	if (Msg->GetOriginPhoneNumber()[0] && TI()MRO.Verbose)
		{
		mPrintf(getmsg(229), Msg->GetOriginPhoneNumber());
		}

	if (Msg->GetToUser()[0])
		{
		// !mass e-mail
		if (!Msg->GetGroup()[0])
			{
			mPrintf(getmsg(1033), Msg->GetToUser());
			}
		else
			{
			mPrintf(getmsg(1033), TI()CurrentUser->GetName());
			}

		if (*Msg->GetForward())
			{
			mPrintf(getmsg(1034), Msg->GetForward());
			}

		if (Msg->IsNetworkedMail() && !Msg->IsNetworkedMailForHere())
			{
			mPrintf(getmsg(211), Msg->GetToNodeName());
			}

		if (*Msg->GetToRegion() &&
				!SameString(Msg->GetToRegion(), cfg.nodeRegion))
			{
			mPrintf(getmsg(229), Msg->GetToRegion());
			}

		if (*Msg->GetReplyToMessage())
			{
			if (TI()MRO.Verbose)
				{
				mPrintf(getmsg(1035), Msg->GetReplyToMessage());
				}
			else
				{
				mPrintf(getmsg(1036));
				}
			}

		if (Msg->IsReceived())
			{
			mPrintf(getmsg(1037));
			}

		if (Msg->IsRepliedTo())
			{
			mPrintf(getmsg(1038));
			}

		if (*Msg->GetToUser() && IsRecipientOfMail(Msg))
			{
			if (!(Msg->GetOriginalAttribute() & ATTR_RECEIVED) && !TI()MRO.Headerscan)
				{
				Msg->SetOriginalAttribute(Msg->GetOriginalAttribute() |
						ATTR_RECEIVED);

				changeheader(Msg->GetOriginalID(), INT_MAX,
						Msg->GetOriginalAttribute());

				madeRec = TRUE;
				}
			}
		}

	if (Msg->GetCreationRoom()[0] && (cfg.showmoved || TI()MRO.Verbose) &&
			!SameString(Msg->GetCreationRoom(), GetRoomName(TI()thisRoom)))
		{
		mPrintf(getmsg(1039), Msg->GetCreationRoom());
		}

	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->GetX()[0] && TI()CurrentUser->CanModerateRoom(Msg->GetOriginalRoom()))
		{
		if (!Msg->IsMadeVisible())
			{
			if (Msg->GetX()[0] == 'Y')
				{
				mPrintf(getmsg(1041), cfg.Luser_nym);
				}
			else
				{
				mPrintf(getmsg(1042));
				}
			}
		else
			{
			mPrintf(getmsg(1043), Msg->GetX()[0] == 'Y' ?
					getmsg(1044) : getmsg(1045), Msg->GetX()[0] == 'Y' ?
					cfg.Luser_nym : ns);
			}
		}

	if (*Msg->GetFileLink() && TI()CurrentUser->IsAide())
		{
		mPrintf(getmsg(1046));
		}

	if (Msg->IsCensored())
		{
		mPrintf(getmsg(1047));
		}

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

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

	long Idx, NumCmt = Msg->GetCommentCount();
	for (Idx = 1; Idx <= NumCmt; Idx++)
		{
		const char *Cmt = Msg->GetComment(Idx);

		CRmPrintf(getmsg(1577), Cmt);
		}

	termCap(TERM_NORMAL);
	doCR();

	if (madeRec && Msg->IsReceiptConfirmationRequested())
		{
		Message *NewMsg;
		Bool good = TRUE;

		CRmPrintf(getmsg(991));

		NewMsg = new Message;

		if (NewMsg)
			{
			NewMsg->SetCreationRoom(Msg->GetCreationRoom());

			NewMsg->SetRoomNumber(Msg->GetRoomNumber());

			NewMsg->SetToUser(Msg->GetAuthor());
			NewMsg->SetAuthor(cfg.nodeTitle);

			NewMsg->SetReplyToMessage(*Msg->GetSourceID() ?
					Msg->GetSourceID() : Msg->GetLocalID());

			if (*Msg->GetSourceID())
				{
				NewMsg->SetToNodeName(Msg->GetOriginNodeName());
				NewMsg->SetToRegion(Msg->GetOriginRegion());
				NewMsg->SetToCountry(Msg->GetOriginCountry());
				NewMsg->SetDestinationAddress(oaddress(Msg));
				}

			NewMsg->SetSubject(getmsg(986));

			if (*Msg->GetSourceID())
				{
				NewMsg->SetTextWithFormat(getmsg(988),
						ltoac(atol(Msg->GetSourceID())),
						Msg->GetAuthor(), Msg->GetOriginNodeName(),
						Msg->GetToUser(), cfg.nodeTitle);

				NewMsg->AppendText(bn);
				NewMsg->AppendText(getmsg(990));
				NewMsg->AppendText(Msg->GetFromPath());
				}
			else
				{
				NewMsg->SetTextWithFormat(getmsg(989),
						ltoac(atol(Msg->GetLocalID())),
						Msg->GetAuthor(), Msg->GetToUser());
				}

			if (*NewMsg->GetToNodeName())
				{
				if (!save_mail(NewMsg, FALSE))
					{
					good = FALSE;
					CRmPrintfCR(getmsg(987));
					}
				}

			if (good)
				{
				putAndNoteMessage(NewMsg, FALSE);
				doCR();
				}

			delete NewMsg;
			}
		else
			{
			CRmPrintfCR(getmsg(987));
			}

		doCR();
		}

	if (*Msg->GetToUser() && TI()whichIO != CONSOLE)
		{
		if (!(SameString(Msg->GetToUser(), getmsg(1019)) ||
				SameString(Msg->GetToUser(), getmsg(1020)) ||
				SameString(Msg->GetAuthor(), getmsg(1019)) ||
				SameString(Msg->GetAuthor(), getmsg(1020))))
			{
			TI()OC.Echo = CALLER;
			setio();
			}
		}

	if (TI()UserControl.GetOutFlag() == OUTPARAGRAPH)
		{
		TI()UserControl.SetOutFlag(OUTOK);
		}

	}

void roomJournal(void)
	{
	char fileName[64];

	doCR();

	SetDoWhat(SYSJOURNAL);

	getString(getmsg(1073), fileName, 63, FALSE, ECHO, ns);

	if (*fileName)
		{
		changedir(cfg.homepath);

		if ((journalfl = fopen(fileName, FO_A)) == NULL)
			{
			CRmPrintfCR(getmsg(78), fileName);
			}
		else
			{
			FILE *tmp;
			SuspendJournalFile(&tmp);
			long howmany = getNumber(getsysmsg(223), 0, LONG_MAX, 0, FALSE, NULL);
			RestoreJournalFile(tmp);

			doCR();

			TI()MRO.Verbose = TRUE;
			TI()MRO.Number = TRUE;

			showMessages(OldAndNew, FALSE, howmany);
			fclose(journalfl);

			journalfl = NULL;
			doCR();
			}
		}
	}
