// --------------------------------------------------------------------------
// Citadel: MsgMod.CPP
//
// Message modification code

#include "ctdl.h"
#pragma hdrstop

#include "tallybuf.h"
#include "room.h"
#include "auxtab.h"
#include "log.h"
#include "msg.h"

// --------------------------------------------------------------------------
// Contents
//
// copymessage()	copies specified message # into specified room
// deleteMessage()	deletes message for pullIt()
// insert() 		aide fn: to insert a message
// markIt() 		is a sysop special to mark current message
// markmsg()		marks a message for insertion and/or visibility
// pullIt() 		is a sysop special to remove a message from a room
// changeheader()	Alters room# or attr byte in message base & index

static void censormsg(Message *Msg);
static void deleteMessage(ulong id);

Bool censor_message(Message *Msg)
	{
	censor *theCensor;

	if (Msg->GetToUser()[0])
		{
		return (FALSE);
		}

	for (theCensor = censorList; theCensor;
			theCensor = (censor *) getNextLL(theCensor))
		{
		if (theCensor->what == CENSOR_TEXT)
			{
			if (	IsSubstr(Msg->GetAuthor(),			theCensor->str) ||
					IsSubstr(Msg->GetTitle(),			theCensor->str) ||
					IsSubstr(Msg->GetOriginCountry(),	theCensor->str) ||
					IsSubstr(Msg->GetToCountry(),		theCensor->str) ||
					IsSubstr(Msg->GetFromPath(),		theCensor->str) ||
					IsSubstr(Msg->GetToPath(),			theCensor->str) ||
					IsSubstr(Msg->GetForward(), 		theCensor->str) ||
					IsSubstr(Msg->GetGroup(),			theCensor->str) ||
					IsSubstr(Msg->GetOriginNodeName(),	theCensor->str) ||
					IsSubstr(Msg->GetOriginRegion(),	theCensor->str) ||
					IsSubstr(Msg->GetCreationRoom(),	theCensor->str) ||
					IsSubstr(Msg->GetToUser(),			theCensor->str) ||
					IsSubstr(Msg->GetSurname(), 		theCensor->str) ||
					IsSubstr(Msg->GetToNodeName(),		theCensor->str) ||
					IsSubstr(Msg->GetToRegion(),		theCensor->str) ||
					IsSubstr(Msg->GetUserSignature(),	theCensor->str) ||
					IsSubstr(Msg->GetSubject(), 		theCensor->str) ||
					IsSubstr(Msg->GetSignature(),		theCensor->str) ||
					IsSubstr(Msg->GetOriginSoftware(),	theCensor->str) ||
					IsSubstr(Msg->GetTwitRegion(),		theCensor->str) ||
					IsSubstr(Msg->GetTwitCountry(), 	theCensor->str) ||
					IsSubstr(Msg->GetOriginPhoneNumber(),theCensor->str)||
					IsSubstr(Msg->GetToPhoneNumber(),	theCensor->str) ||
					IsSubstr(Msg->GetText(),			theCensor->str))
				{
				censormsg(Msg);
				return (TRUE);
				}
			}

		if (theCensor->what == CENSOR_AUTHOR)
			{
			if (SameString(Msg->GetAuthor(), theCensor->str))
				{
				censormsg(Msg);
				return (TRUE);
				}
			}

		if (theCensor->what == CENSOR_NODE)
			{
			if (SameString(Msg->GetOriginNodeName(), theCensor->str))
				{
				censormsg(Msg);
				return (TRUE);
				}
			}
		}

	return (FALSE);
	}


// --------------------------------------------------------------------------
// addtomsglist(): Adds an element to message queue.

static void addtomsglist(ulong msgnumber)
	{
	messageList *thisMsg;

	compactMemory();

	thisMsg = (messageList *) addLL((void **) &TI()MS.MsgList,
			sizeof(*thisMsg));

	if (thisMsg)
		{
		thisMsg->msg = msgnumber;
		}
	else
		{
		CRmPrintfCR(getmsg(1275), cfg.Lmsg_nym);
		}
	}


// --------------------------------------------------------------------------
// markroom(): Adds all messages in a room to queue.

void markroom(void)
	{
	m_slot i;
	m_slot num;

	SetDoWhat(AIDEQUEUER);

	num = (m_slot) sizetable();

	for (i = 0; i < num; ++i)
		{
		if (getRoomNum(i) == TI()thisRoom)
			{
			if (MaySeeIndexMsg(i) == MSM_GOOD)
				{
				addtomsglist(((ulong)(cfg.mtoldest + i)));
				}
			}
		}
	}


// --------------------------------------------------------------------------
// printmsglist(): Prints out messages currently in list.

void printmsglist(void)
	{
	messageList *thisMsg;

	SetDoWhat(AIDEQUEUEL);

	TI()MRO.Headerscan = TRUE;
	TI()UserControl.SetOutFlag(OUTOK);
	doCR();

	if (TI()MS.MsgList)
		{
		for (thisMsg = TI()MS.MsgList; thisMsg; thisMsg = (messageList *) getNextLL(thisMsg))
			{
			if (TI()MRO.Verbose)
				{
				PrintMessageByID(thisMsg->msg);
				}
			else
				{
				mPrintf(getmsg(293), thisMsg->msg);
				}
			}
		}
	else
		{
		CRmPrintfCR(getmsg(1147), cfg.Lmsgs_nym);
		}

	TI()MRO.Headerscan = FALSE;
	}


// --------------------------------------------------------------------------
// insertmsglist()

void insertmsglist(void)
	{
	messageList *thisMsg;

	SetDoWhat(AIDEQUEUEI);

	TI()MRO.Headerscan = TRUE;
	TI()UserControl.SetOutFlag(OUTOK);
	doCR();

	if (TI()MS.MsgList)
		{
		for (thisMsg = TI()MS.MsgList; thisMsg && (TI()UserControl.GetOutFlag() == OUTOK);
				thisMsg = (messageList *) getNextLL(thisMsg))
			{
			if (TI()MRO.Verbose)
				{
				CRmPrintf(getmsg(1148), cfg.Lmsg_nym);
				PrintMessageByID(thisMsg->msg);
				}
			else
				{
				mPrintfCR(getmsg(1149), cfg.Lmsg_nym, ltoac(thisMsg->msg));
				}
			insert(thisMsg->msg);
			}
		}
	else
		{
		mPrintfCR(getmsg(1150), cfg.Lmsgs_nym);
		}

	TI()MRO.Headerscan = FALSE;
	}


// --------------------------------------------------------------------------
// killmsglist()

void killmsglist(void)
	{
	messageList *thisMsg;

	SetDoWhat(AIDEQUEUEK);

	TI()MRO.Headerscan = TRUE;
	TI()UserControl.SetOutFlag(OUTOK);
	doCR();

	if (TI()MS.MsgList)
		{
		for (thisMsg = TI()MS.MsgList; thisMsg && (TI()UserControl.GetOutFlag() == OUTOK);
				thisMsg = (messageList *) getNextLL(thisMsg))
			{
			if (TI()MRO.Verbose)
				{
				CRmPrintf(getmsg(1242), cfg.Lmsg_nym);
				PrintMessageByID(thisMsg->msg);
				}
			else
				{
				CRmPrintf(getmsg(1244), cfg.Lmsg_nym, ltoac(thisMsg->msg));
				}

			deleteMessage(thisMsg->msg);
			}
		}
	else
		{
		mPrintfCR(getmsg(1247), cfg.Lmsgs_nym);
		}
	TI()MRO.Headerscan = FALSE;
	}


// --------------------------------------------------------------------------
// msglistsort()

static int cdecl msglistsort(ulong *s1, ulong *s2)
	{
	if (*s1 > *s2)
		{
		return (1);
		}

	if (*s1 < (*s2))
		{
		return (-1);
		}

	return (0);
	}


// --------------------------------------------------------------------------
// revmsglistsort()

static int cdecl revmsglistsort(ulong *s1, ulong *s2)
	{
	if (*s1 > *s2)
		{
		return (-1);
		}

	if (*s1 < *s2)
		{
		return (1);
		}

	return (0);
	}


// --------------------------------------------------------------------------
// sortmsglist(): 0 Forward 1 Reverse.

void sortmsglist(char direction)
	{
	ulong *listArray, p, i = getLLCount(TI()MS.MsgList);
	messageList *thisMsg;

	if (TI()MS.MsgList)
		{
		if (i > 8150 || (listArray = new ulong[(uint) i]) == NULL)
			{
			CRmPrintf(getmsg(1248));
			}
		else
			{
			for (thisMsg = TI()MS.MsgList, p = 0; thisMsg;
					thisMsg = (messageList *) getNextLL(thisMsg), p++)
				{
				listArray[(uint) p] = thisMsg->msg;
				}
			disposeLL((void **) &TI()MS.MsgList);

			if (!direction)
				{
				qsort(listArray, (uint) p, (unsigned) sizeof(*listArray),
						(QSORT_CMP_FNP)msglistsort);
				}
			else
				{
				qsort(listArray, (uint) p, (unsigned) sizeof(*listArray),
						(QSORT_CMP_FNP)revmsglistsort);
				}

			for (i = 0; i < p; i++)
				{
				thisMsg = (messageList *) addLL((void **) &TI()MS.MsgList,
						sizeof(*TI()MS.MsgList));
				thisMsg->msg = listArray[(uint)i];
				}

			delete [] listArray;
			}
		}
	else
		{
		mPrintfCR(getmsg(1276), cfg.Lmsgs_nym);
		}
	}


// --------------------------------------------------------------------------
// clearmsglist(): Clears msg queue.

void clearmsglist(void)
	{
	SetDoWhat(AIDEQUEUEC);

	disposeLL((void **) &TI()MS.MsgList);
	}


// --------------------------------------------------------------------------
// automark(): Automatically mark or display messages.

void automark(void)
	{
	SetDoWhat(AIDEQUEUEM);

	if (TI()MS.AutoMark || TI()MS.AutoKill || TI()MS.AutoCensor)
		{
		doCR();
		CRmPrintf(getmsg(1131), cfg.Lmsg_nym);
		if (TI()MS.AutoMark)	mPrintf(getmsg(1132));
		if (TI()MS.AutoKill)	mPrintf(getmsg(1267));
		if (TI()MS.AutoCensor)	mPrintf(getmsg(1268));
		mPrintfCR(getmsg(1269));
		TI()MS.AutoMark = 0;
		TI()MS.AutoKill = 0;
		TI()MS.AutoCensor = 0;
		return;
		}

	doCR();
	TI()MS.AutoMark = getYesNo(getmsg(1270), 0);
	if (TI()MS.AutoMark) return;

	TI()MS.AutoKill = getYesNo(getmsg(1273), 0);
	if (TI()MS.AutoKill) return;

	TI()MS.AutoCensor = getYesNo(getmsg(1274), 0);
	}


// --------------------------------------------------------------------------
// copymessage(): Copies specified message # into specified room.

static void copymessage(ulong id, r_slot roomno)
	{
	if (id == 0)
		{
		return;
		}

	m_slot slot = indexslot(id);

	if (slot != M_SLOT_ERROR)
		{
		Message *Msg = new Message;

		if (Msg)
			{
			// load in message to be inserted
			bufferedSeek(msgfl, getLocation(slot));

			bufferedWinReOpen(msgfl);
			Msg->ReadHeader(RMC_NORMAL, NULL);
			bufferedWinCloseTmp(msgfl);

			// retain vital information
			uchar attr = Msg->GetAttribute();
			label copy;

			strcpy(copy, Msg->GetLocalID());

			Msg->ClearAll();

			Msg->SetCopyOfMessage(copy);
			Msg->SetAttribute(attr);
			Msg->SetRoomNumber(roomno);

			putAndNoteMessage(Msg, FALSE);

			delete Msg;
			}
		else
			{
			mPrintf(getmsg(188), getmsg(306));
			}
		}
	}


// --------------------------------------------------------------------------
// deleteMessage(): Deletes a specified message.

static void deleteMessage(ulong id)
	{
	if (!id)
		{
		return;
		}

	m_slot slot = indexslot(id);

	if (slot == M_SLOT_ERROR)
		{
		return;
		}

	ulong LocationInFile = getLocation(slot);

	if (LocationInFile == ULONG_ERROR)
		{
		return;
		}

	Message *Msg = new Message;
	if (Msg)
		{
		bufferedWinReOpen(msgfl);
		bufferedSeek(msgfl, LocationInFile);
		Msg->ReadAll(RMC_NORMAL, NULL);

		Msg->Decompress();

		bufferedWinCloseTmp(msgfl);

		r_slot room;

		#ifdef AUXMEM
			messagetable *lmt;
			lmt = getMsgTab(slot);
		#endif

		#ifdef AUXMEM
			room = lmt->mtroomno;
		#else
			#ifdef WINCIT
				room = msgTabWin[slot].mtroomno;
			#else
				room = msgTab_mtroomno[slot];
			#endif
		#endif

		changeheader(id, DUMP, UCHAR_MAX);

		// if author move into old buffer
		if (SameString(Msg->GetAuthor(), TI()CurrentUser->GetName()))
			{
			if (TI()MS.AbortedMessage)
				{
				delete TI()MS.AbortedMessage;
				}

			TI()MS.AbortedMessage = new Message;

			if (TI()MS.AbortedMessage)
				{
				*TI()MS.AbortedMessage = *Msg;

				TI()MS.AbortedMessage->SetToPath(ns);
				TI()MS.AbortedMessage->SetFromPath(ns);
				TI()MS.AbortedMessage->SetEncrypted(FALSE);
				TI()MS.AbortedMessage->SetEncryptionKey(ns);
				}
			else
				{
				mPrintf(getmsg(188), getmsg(347));
				}
			}

		// at this point, we are done with Msg, so we can now use the buffer
		// to save the Aide) room message...

		if (TI()thisRoom != AIDEROOM && TI()thisRoom != DUMP &&
				!SameString(Msg->GetAuthor(), TI()CurrentUser->GetName()))
			{
			Msg->ClearAll();

			// this bit gets the group from the original message, if there
			// was one. If we don't have the memory to load another message,
			// then don't sweat it; this is not really important.
			Message *OldMsg = new Message;
			if (OldMsg)
				{
				m_slot slot;
				ulong myid = id;
				Bool Good = TRUE;

				do
					{
					slot = indexslot(myid);

					if (slot == M_SLOT_ERROR)
						{
						Good = FALSE;
						}

					if (Good && getFlags(slot)->IsCopy())
						{
						if (getCopy(slot) <= slot)
							{
							myid -= getCopy(slot);
							}
						else
							{
							// Copied message has scrolled
							Good = FALSE;
							}
						}
					} while (Good && getFlags(slot)->IsCopy());

				if (Good)
					{
					slot = indexslot(myid);

					assert(slot != M_SLOT_ERROR);

					// load in message to be inserted
					bufferedSeek(msgfl, getLocation(slot));

					bufferedWinReOpen(msgfl);
					OldMsg->ReadHeader(RMC_NORMAL, NULL);
					bufferedWinCloseTmp(msgfl);

					Msg->SetGroup(OldMsg->GetGroup());
					}

				delete OldMsg;
				}

			// note in Aide)
			Msg->SetTextWithFormat(getmsg(1130), ltoac(id), GetRoomName(room),
					TI()CurrentUser->GetName());

			trap(Msg->GetText(), T_AIDE);

			Msg->SetTextWithFormat(getmsg(1377), cfg.Lmsg_nym, ltoac(id),
					GetRoomName(room), TI()CurrentUser->GetName());

			Msg->SetRoomNumber(AIDEROOM);
			systemMessage(Msg);

			// give our memory to copymessage() - it may need it.
			delete Msg;
			copymessage(id, AIDEROOM);
			}
		else
			{
			delete Msg;
			}
		}
	else
		{
		mPrintf(getmsg(188), getmsg(347));
		}
	}


// --------------------------------------------------------------------------
// insert(): Aide fn: to insert a specified message.

void insert(ulong id)
	{
	SetDoWhat(AIDEINSERT);

	if (TI()thisRoom == AIDEROOM)
		{
		mPrintf(getmsg(1122));
		return;
		}

	if (!id)
		{
		return;
		}

	m_slot slot = indexslot(id);

	if (slot == M_SLOT_ERROR)
		{
		return;
		}

	ulong LocationInFile = getLocation(slot);

	if (LocationInFile == ULONG_ERROR)
		{
		return;
		}

	Message *Msg = new Message;
	if (Msg)
		{
		copymessage(id, TI()thisRoom);

		// this bit gets the group from the original message, if there
		// was one. If we don't have the memory to load another message,
		// then don't sweat it; this is not really important.
		Message *OldMsg = new Message;
		if (OldMsg)
			{
			m_slot slot;
			ulong myid = id;
			Bool Good = TRUE;

			do
				{
				slot = indexslot(myid);

				if (slot == M_SLOT_ERROR)
					{
					Good = FALSE;
					}

				if (Good && getFlags(slot)->IsCopy())
					{
					if (getCopy(slot) <= slot)
						{
						myid -= getCopy(slot);
						}
					else
						{
						// Copied message has scrolled
						Good = FALSE;
						}
					}
				} while (Good && getFlags(slot)->IsCopy());

			if (Good)
				{
				slot = indexslot(myid);

				assert(slot != M_SLOT_ERROR);

				// load in message to be inserted
				bufferedSeek(msgfl, getLocation(slot));

				bufferedWinReOpen(msgfl);
				OldMsg->ReadHeader(RMC_NORMAL, NULL);
				bufferedWinCloseTmp(msgfl);

				Msg->SetGroup(OldMsg->GetGroup());
				}

			delete OldMsg;
			}

		Msg->SetTextWithFormat(getmsg(1083), ltoac(id),
				GetRoomName(TI()thisRoom), TI()CurrentUser->GetName());

		trap(Msg->GetText(), T_AIDE);

		Msg->SetTextWithFormat(getmsg(1378), cfg.Lmsg_nym, ltoac(id),
				GetRoomName(TI()thisRoom), TI()CurrentUser->GetName());

		Msg->SetRoomNumber(AIDEROOM);
		systemMessage(Msg);

		// give our memory to copymessage() - it may need it.
		delete Msg;
		copymessage(id, AIDEROOM);
		}
	else
		{
		mPrintf(getmsg(188), getmsg(348));
		}
	}


// --------------------------------------------------------------------------
// markIt(): Is a query to mark current message.

Bool markIt(Message *Msg)
	{
	int yna;

	// confirm that we're marking the right one
	if (!TI()MS.AutoMark)
		{
		Bool oldverbose;

		TI()UserControl.SetOutFlag(OUTOK);
		oldverbose = TI()MRO.Verbose;
		TI()MRO.Verbose = FALSE;
		PrintMessage(Msg);
		TI()MRO.Verbose = oldverbose;
		}

	TI()UserControl.SetOutFlag(OUTOK);

	doCR();

	yna = getYesNo(getmsg(1082), TI()MS.AutoMark ? 4 : 1);

	if (yna == 1)
		{
		markmsg(Msg);
		return (TRUE);
		}
	else if (yna == 2)
		{
		TI()UserControl.SetOutFlag(OUTSKIP);
		}

	return (FALSE);
	}


// --------------------------------------------------------------------------
// censorIt(): Is a query to censor current message.

Bool censorIt(Message *Msg)
	{
	int yna;

	// confirm that we're censoring the right one
	TI()UserControl.SetOutFlag(OUTOK);
	const Bool oldverbose = TI()MRO.Verbose;
	TI()MRO.Verbose = FALSE;
	if (!TI()MS.AutoCensor)
		{
		PrintMessage(Msg);
		}

	TI()MRO.Verbose = oldverbose;

	TI()UserControl.SetOutFlag(OUTOK);

	doCR();

	if (Msg->GetOriginalAttribute() & ATTR_CENSORED)
		{
		yna = getYesNo(getmsg(1076), TI()MS.AutoCensor ? 4 : 1);
		}
	else
		{
		yna = getYesNo(getmsg(1077), TI()MS.AutoCensor ? 4 : 1);
		}

	if (yna == 1)
		{
		censormsg(Msg);
		return (TRUE);
		}

	if (yna == 2)
		{
		TI()UserControl.SetOutFlag(OUTSKIP);
		}

	return (FALSE);
	}


// --------------------------------------------------------------------------
// censormsg(): Toggles censor bit for current message.

static void censormsg(Message *Msg)
	{
	TI()MS.MarkedID = Msg->GetOriginalID();

	const m_slot slot = indexslot(Msg->GetOriginalID());

	if (slot == M_SLOT_ERROR)
		{
		return;
		}

	// put in message queue. wonder if we should comment it out
	addtomsglist(TI()MS.MarkedID);

	Msg->SetOriginalAttribute(Msg->GetOriginalAttribute() ^ ATTR_CENSORED);

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

	const r_slot room = getRoomNum(slot);

	if (MaySeeIndexMsg(slot) != MSM_GOOD &&
			!TI()CurrentUser->IsViewCensoredMessages())
		{
		DecrementRoomMessages(room);

		if ((ulong)(cfg.mtoldest + slot) >
				TI()CurrentUser->GetRoomNewPointer(room))
			{
			DecrementRoomNewMessages(room);
			}
		}
	}


// --------------------------------------------------------------------------
// markmsg(): Marks current msg for insertion and/or visibility.

void markmsg(Message *Msg)
	{
	TI()MS.MarkedID = Msg->GetOriginalID();

	// put in message queue
	addtomsglist(TI()MS.MarkedID);

	if (Msg->GetX()[0])
		{
		Msg->SetOriginalAttribute(Msg->GetOriginalAttribute() ^ ATTR_MADEVIS);

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

		if (Msg->GetOriginalAttribute() & ATTR_MADEVIS)
			{
			copymessage(Msg->GetOriginalID(), Msg->GetOriginalRoom());
			}
		}
	}


// --------------------------------------------------------------------------
// pullIt(): Is a query to remove current msg from room.

Bool pullIt(Message *Msg)
	{
	// confirm that we're removing the right one
	TI()UserControl.SetOutFlag(OUTOK);

	const Bool oldverbose = TI()MRO.Verbose;
	TI()MRO.Verbose = FALSE;

	if (!TI()MS.AutoKill)
		{
		PrintMessage(Msg);
		}

	TI()MRO.Verbose = oldverbose;

	TI()UserControl.SetOutFlag(OUTOK);

	const int yna = getYesNo(getmsg(1075), TI()MS.AutoKill ? 3 : 0);

	if (yna == 1)
		{
		// moved up from deletemessage()
		if (!*Msg->GetX())
			{
			markmsg(Msg);	// Mark it for possible insertion elsewhere
			}

		deleteMessage(Msg->GetOriginalID());
		return (TRUE);
		}

	if (yna == 2)
		{
		TI()UserControl.SetOutFlag(OUTSKIP);
		}

	return (FALSE);
	}


// --------------------------------------------------------------------------
// changeheader(): Alters room# or attr byte in message base & index.

void changeheader(ulong id, r_slot roomno, uchar attr)
	{
	long loc;
	const m_slot slot = indexslot(id);

	if (M_SLOT_ERROR == slot)
		{
		return;
		}

	int c;
	long pos;
	int room;

	uchar roomno_lo;
	uchar roomno_hi;

	uchar old_attr;
	uchar new_attr;

	#ifdef AUXMEM
		messagetable *lmt = getMsgTab(slot);
	#endif

	loc = getLocationLMT(slot);

	if (loc == CERROR)
		{
		return;
		}

	// Change the room # for the message
	if (roomno != INT_MAX)
		{
		// determine room # of message to be changed
		room = getRoomNumLMT(slot);

		// fix the message talleys from, if from a valid room
		if (room < cfg.maxrooms)
			{
			DecrementRoomTotalMessages(room);

			if (MaySeeIndexMsg(slot) == MSM_GOOD)
				{
				DecrementRoomMessages(room);
				if	((cfg.mtoldest + slot) >
						TI()CurrentUser->GetRoomNewPointer(room))
					{
					DecrementRoomNewMessages(room);
					}
				}
			}

		// fix room talleys to
		IncrementRoomTotalMessages(roomno);

		if (MaySeeIndexMsg(slot) == MSM_GOOD)
			{
			IncrementRoomMessages(roomno);
			if ((ulong)(cfg.mtoldest + slot) >
					TI()CurrentUser->GetRoomNewPointer(roomno))
				{
				IncrementRoomNewMessages(roomno);
				}
			}

		#if defined(AUXMEM) || defined(WINCIT)
			// and fix our room list in the message table...

			// first, take it out of old room.
			if (FirstMessageInRoom[room] == id)
				{
				// this was the first message in the room. set new first to next.
				FirstMessageInRoom[room] = getNextRoomMsg(slot);

				if (FirstMessageInRoom[room] != M_SLOT_ERROR)
					{
					assert(indexslot(FirstMessageInRoom[room]) != M_SLOT_ERROR);

					getMsgTab(indexslot(FirstMessageInRoom[room]))->PrevRoomMsg =
							M_SLOT_ERROR;
					}

				// it might also have been the last...
				if (LastMessageInRoom[room] == id)
					{
					// this was the last message in the room. set new last to prev.
					LastMessageInRoom[room] = getPrevRoomMsg(slot);

					if (LastMessageInRoom[room] != M_SLOT_ERROR)
						{
						assert(indexslot(LastMessageInRoom[room]) != M_SLOT_ERROR);

						getMsgTab(indexslot(LastMessageInRoom[room]))->NextRoomMsg =
								M_SLOT_ERROR;
						}
					}
				}
			else if (LastMessageInRoom[room] == id)
				{
				// this was the last message in the room. set new last to prev.
				LastMessageInRoom[room] = getPrevRoomMsg(slot);

				if (LastMessageInRoom[room] != M_SLOT_ERROR)
					{
					assert(indexslot(LastMessageInRoom[room]) != M_SLOT_ERROR);

					getMsgTab(indexslot(LastMessageInRoom[room]))->NextRoomMsg =
							M_SLOT_ERROR;
					}
				}
			else
				{
				// just a message somewhere in between the first and last...
				assert(indexslot(getPrevRoomMsg(slot)) != M_SLOT_ERROR);
				getMsgTab(indexslot(getPrevRoomMsg(slot)))->NextRoomMsg =
						getNextRoomMsg(slot);

				assert(indexslot(getNextRoomMsg(slot)) != M_SLOT_ERROR);
				getMsgTab(indexslot(getNextRoomMsg(slot)))->PrevRoomMsg =
						getPrevRoomMsg(slot);
				}

			// now, put it in new room.
			if (FirstMessageInRoom[roomno] == M_SLOT_ERROR)
				{
				// first message in this room...
				assert(LastMessageInRoom[roomno] == M_SLOT_ERROR);

				getMsgTab(slot)->PrevRoomMsg = M_SLOT_ERROR;
				getMsgTab(slot)->NextRoomMsg = M_SLOT_ERROR;

				FirstMessageInRoom[roomno] = id;
				LastMessageInRoom[roomno] = id;
				}
			else
				{
				assert(LastMessageInRoom[roomno] != M_SLOT_ERROR);

				if (id < FirstMessageInRoom[roomno])
					{
					// this message is older then the old first message in room...
					getMsgTab(slot)->NextRoomMsg = FirstMessageInRoom[roomno];
					getMsgTab(slot)->PrevRoomMsg = M_SLOT_ERROR;

					assert(indexslot(FirstMessageInRoom[roomno]) != M_SLOT_ERROR);
					getMsgTab(indexslot(FirstMessageInRoom[roomno]))->PrevRoomMsg = id;

					FirstMessageInRoom[roomno] = id;
					}
				else if (id > LastMessageInRoom[roomno])
					{
					// this message is newer than the old last message in room...
					getMsgTab(slot)->PrevRoomMsg = LastMessageInRoom[roomno];
					getMsgTab(slot)->NextRoomMsg = M_SLOT_ERROR;

					assert(indexslot(LastMessageInRoom[roomno]) != M_SLOT_ERROR);
					getMsgTab(indexslot(LastMessageInRoom[roomno]))->NextRoomMsg = id;

					LastMessageInRoom[roomno] = id;
					}
				else
					{
					// insert it somewhere...
					m_slot CurrentMessage, LastMessage;

					for (CurrentMessage = FirstMessageInRoom[roomno];
							CurrentMessage < id;
							LastMessage = CurrentMessage,
							assert(indexslot(CurrentMessage) != M_SLOT_ERROR),
							CurrentMessage = getNextRoomMsg(indexslot(CurrentMessage)))
						{
						assert(CurrentMessage != M_SLOT_ERROR);
						}

					assert(LastMessage != id);

					assert(indexslot(CurrentMessage) != M_SLOT_ERROR);
					getMsgTab(indexslot(CurrentMessage))->PrevRoomMsg = id;

					assert(indexslot(LastMessage) != M_SLOT_ERROR);
					getMsgTab(indexslot(LastMessage))->NextRoomMsg = id;

					getMsgTab(slot)->PrevRoomMsg = LastMessage;
					getMsgTab(slot)->NextRoomMsg = CurrentMessage;
					}
				}
		#endif
		}

	bufferedWinReOpen(msgfl);

	pos = bufferedTell(msgfl);

	// find start of message
	bufferedSeek(msgfl, loc);
	do
		{
		c = getMsgChar();
		} while (c != 0xFF);

	// change attr if needed
	if (attr != UCHAR_MAX)
		{
		loc = bufferedTell(msgfl);

		old_attr = getMsgChar();

		// Create new_attr by combining the old_attr with attr

		new_attr = attr &
				(ATTR_RECEIVED | ATTR_REPLY | ATTR_MADEVIS | ATTR_CENSORED);

		new_attr |= old_attr &
				(ATTR_COMPRESSED | ATTR_BIGROOM | ATTR_MORE);

		bufferedSeek(msgfl, loc);

		// write new attribute
		overwrite(1);
		putMsgChar(new_attr);
		attr = new_attr;

		#ifdef AUXMEM
			lmt = getMsgTab(slot);
		#endif

		getFlagsLMT(slot)->SetReceived(attr & ATTR_RECEIVED);
		getFlagsLMT(slot)->SetReply(attr & ATTR_REPLY);
		getFlagsLMT(slot)->SetMadevis(attr & ATTR_MADEVIS);
		getFlagsLMT(slot)->SetCensored(attr & ATTR_CENSORED);
		}
	else
		{
		attr = getMsgChar();
		}

	if (attr & ATTR_MORE)
		{
		getMsgChar();
		}

	if (roomno != INT_MAX)
		{
		if (attr & ATTR_BIGROOM)
			{
			roomno_lo = int_LO(roomno * 2);
			roomno_hi = int_HI(roomno * 2);

			overwrite(1);
			putMsgChar(roomno_lo);

			overwrite(1);
			putMsgChar(roomno_hi);
			}
		else
			{
			overwrite(1);
			putMsgChar(roomno);
			}

		#ifdef AUXMEM
			lmt = getMsgTab(slot);

			lmt->mtroomno = roomno;
		#else
			#ifdef WINCIT
				msgTabWin[slot].mtroomno = roomno;
			#else
				msgTab_mtroomno[slot] = roomno;
			#endif
		#endif
		}

	bufferedSeek(msgfl, pos);
	bufferedWinCloseTmp(msgfl);

#if VERSION == ALPHA
	VerifyMsgTab();
#endif
	}


// --------------------------------------------------------------------------
// massdelete(): Sysop fn to kill all msgs from person.

#define MASSDELETEUPDATETIME 5
void massdelete(void)
	{
	label who;
	char string[256];

	SetDoWhat(SYSMASS);

	getNormStr(getmsg(599), who, LABELSIZE, ECHO);

	if (!(*who))
		{
		return;
		}

	sprintf(string, getsysmsg(87), cfg.Lmsgs_nym, who);

	if (getYesNo(string, 0))
		{
		time_t LastProgressTime = time(NULL);

		const int namehash = hash(who);
		int killed = 0;

		for (m_slot i = sizetable(); i != M_SLOT_ERROR; i--)
			{
			if (getAuthHash(i) == namehash) // maybe check actual msg too?
				{
				if (getRoomNum(i) != DUMP)
					{
					++killed;
					changeheader((cfg.mtoldest + i), DUMP, UCHAR_MAX);
					}
				}

			if (!(i & 15))
				{
				if (BBSCharReady())
					{
					TI()UserControl.CheckInput(FALSE);

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

				if (LastProgressTime + MASSDELETEUPDATETIME <= time(NULL))
					{
					mPrintfCR(getsysmsg(271), cfg.Lmsg_nym, ltoac(i));
					LastProgressTime = time(&TI()LastActiveTime);
					}
				}
			}

		CRCRmPrintfCR(getsysmsg(88), ltoac(killed), killed == 1 ?
				cfg.Lmsg_nym : cfg.Lmsgs_nym);

		if (killed)
			{
			sprintf(string, getsysmsg(89), who);
			trap(string, T_SYSOP);

			roomtalley();
			}
		}
	}


// --------------------------------------------------------------------------
// copymsg(): Aide fn to copy message to buffer.

void copymsg(void)
	{
	char prompt[80];

	SetDoWhat(AIDEMSGIN);

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

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

	sprintf(prompt, getmsg(381), cfg.Lmsg_nym);
	const long msgno = getNumber(prompt, 0l, LONG_MAX, -1l, FALSE, NULL);

	if (!msgno || msgno < 0)
		{
		return;
		}

	if ((msgno < cfg.mtoldest) || (msgno > cfg.newest))
		{
		mPrintfCR(getmsg(1010));
		return;
		}

	const m_slot slot = indexslot(msgno);

	assert(slot != M_SLOT_ERROR);

	if (MaySeeIndexMsg(slot) != MSM_GOOD)
		{
		mPrintf(getmsg(382), cfg.Umsg_nym);
		}
	else
		{
		if (TI()MS.AbortedMessage)
			{
			delete TI()MS.AbortedMessage;
			}

		TI()MS.AbortedMessage = new Message;

		if (TI()MS.AbortedMessage)
			{
			dowhattype oldDowhat;

			TI()MRO.Verbose = TRUE;

			TI()MRO.DotoMessage = NO_SPECIAL;

			oldDowhat = TI()DoWhat;
			SetDoWhat(READMESSAGE);
			TI()MRO.DotoMessage = NO_SPECIAL;

			TI()UserControl.SetOutFlag(OUTOK);
			PrintMessageByID(msgno);

			SetDoWhat(oldDowhat);

			bufferedWinReOpen(msgfl);
			bufferedSeek(msgfl, getLocation(slot));
			TI()MS.AbortedMessage->ReadAll(RMC_NORMAL, NULL);

			TI()MS.AbortedMessage->Decompress();

			bufferedWinCloseTmp(msgfl);

			TI()MS.AbortedMessage->ClearHeader();
			}
		else
			{
			mPrintf(getmsg(188), getmsg(349));
			}
		}

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