// --------------------------------------------------------------------------
// Citadel: MsgMake.CPP
//
// Message making code.

#include "ctdl.h"
#pragma hdrstop

#include "room.h"
#include "roompriv.h"
#include "tallybuf.h"
#include "log.h"
#include "msg.h"
#include "net.h"
#include "events.h"
#include "filerdwr.h"


// --------------------------------------------------------------------------
// Contents
//
// systemMessage()	save messages from node
// putMessage() 	stores a message to disk
// putMsgChar() 	writes character to message file
// crunchmsgTab()	obliterates slots at the beginning of table
// overwrite()		checks for any overwriting of old messages
// putMsgStr()		writes a string to the message file
// noteMessage()	puts message in mesgBuf into message index
// SaveStub()		Saves Msg as a stub


static void noteMessage(Message *Msg);
static void putMsgStr(const char *string);


// --------------------------------------------------------------------------
// SaveStub(): Saves Msg as a stub.

void SaveStub(Message *Msg, const char *TypeStr, Bool PoopDebug)
	{
	Message *Stub = new Message;

	if (Stub)
		{
		if (PoopDebug)
			{
			*Stub = *Msg;

			char ToString[81];
			sprintf(ToString, getmsg(101), Msg->GetToUser(),
					Msg->GetToNodeName());

			Stub->AddComment(ToString);

			Stub->SetToUser(ns);
			Stub->SetToNodeName(ns);
			}
		else
			{
			// this is SODA
			Stub->SetAuthor(deansi(Msg->GetAuthor()));
			Stub->SetCreationTime(Msg->GetCreationTime());
			Stub->SetOriginNodeName(deansi(Msg->GetOriginNodeName()));
			Stub->SetSourceID(Msg->GetSourceID());

			Stub->SetTextWithFormat(getmsg(1355), TypeStr);
			}

		// set room# and attribute byte for message
		Stub->SetRoomNumber(DUMP);
		Stub->SetAttribute(0);

		// Save the stub
		Stub->Store(SMC_NORMAL, NULL);
		noteMessage(Stub);

		delete Stub;
		}
	else
		{
		mPrintf(getmsg(188), getmsg(1356));
		}
	}


// --------------------------------------------------------------------------
// putMessage(): Stores a message to disk.

static void writeCharToFile(FILE *File, char Character)
	{
	fputc(Character, File);
	}

static void writeCharToMsgDat(FILE *, char Character)
	{
	overwrite(1);
	putMsgChar(Character);
	}

static void writeStringToFile(FILE *File, const char *Buffer)
	{
	PutStr(File, Buffer);
	}

static void writeStringToMsgDat(FILE *, const char *Buffer)
	{
	putMsgStr(Buffer);
	}

void Message::Store(SaveMessageCode HowToSave, FILE *SaveFile)
	{
	void (*WriteFileChar)(FILE *File, char Character);
	void (*WriteFileString)(FILE *File, const char *Buffer);

	assert((HowToSave == SMC_NORMAL || HowToSave == SMC_RESIZE) || SaveFile);
	assert((HowToSave == SMC_NETWORK || HowToSave == SMC_LESAVE) || !SaveFile);

	switch (HowToSave)
		{
		case SMC_NETWORK:
			{
			KBReady();
			WriteFileChar = writeCharToFile;
			WriteFileString = writeStringToFile;
			break;
			}

		case SMC_NORMAL:
			label Buffer;
			SetLocalID(ltoa(cfg.newest + 1, Buffer, 10));

			if (!GetOriginalID())
				{
				SetOriginalID(cfg.newest + 1);		// For censoring
				}

			// Fall through

		case SMC_RESIZE:
			{
			// record start of message to be noted
			SetHeadLoc(cfg.catLoc);

			bufferedWinReOpen(msgfl);

			// tell putMsgChar where to write
			bufferedSeek(msgfl, cfg.catLoc);

			WriteFileChar = writeCharToMsgDat;
			WriteFileString = writeStringToMsgDat;
			break;
			}

		case SMC_LESAVE:
			{
			WriteFileChar = writeCharToFile;
			WriteFileString = writeStringToFile;
			break;
			}

		default:
			{
			crashout(getmsg(352));
			}
		}

	(*WriteFileChar)(SaveFile, 0xFF);

	if (*GetEncryptionKey())
		{
		if (encrypt(GetTextPointer(), GetEncryptionKey()))
			{
			SetEncrypted(TRUE);
			}
		else
			{
			SetEncryptionKey(ns);
			}
		}

#ifndef WINCIT
	// try compressing the message if it isn't already
	if (HowToSave == SMC_NORMAL || HowToSave == SMC_RESIZE)
		{
		Compress();
		}
#endif

	if (HowToSave == SMC_NETWORK)
		{
		// networked messages can only be received or replied to.
		Attribute = Attribute & (ATTR_RECEIVED | ATTR_REPLY);
		}

	(*WriteFileChar)(SaveFile, Attribute);

	if (Attribute & ATTR_MORE)
		{
		(*WriteFileChar)(SaveFile, Attribute2);
		}

	if (HowToSave == SMC_NORMAL || HowToSave == SMC_RESIZE)
		{
		// write room #
		if (IsBigRoom())
			{
			uchar roomno_lo;
			uchar roomno_hi;

			roomno_lo = int_LO(GetRoomNumber() * 2);
			roomno_hi = int_HI(GetRoomNumber() * 2);

			(*WriteFileChar)(SaveFile, roomno_lo);
			(*WriteFileChar)(SaveFile, roomno_hi);
			}
		else
			{
			(*WriteFileChar)(SaveFile, GetRoomNumber());
			}
		}

	// write message ID; LESAVEd messages have no ID; fake one.
	if (HowToSave == SMC_LESAVE)
		{
		(*WriteFileString)(SaveFile, "0");
		}
	else
		{
		(*WriteFileString)(SaveFile, GetLocalID());
		}

	if (HowToSave == SMC_NORMAL)
		{
		// setup time/datestamp
		if (!GetCopyOfMessage()[0])
			{
			if (!*GetCreationTime())
				{
				label Buffer;
				SetCreationTime(ltoa(time(NULL), Buffer, 10));
				}
			}
		else
			{
			SetCreationTime(ns);
			}

		// write room name out
		if (!GetCopyOfMessage()[0])
			{
			SetCreationRoom(GetRoomName(GetRoomNumber()));
			}
		else
			{
			SetCreationRoom(ns);
			}
		}

	if (HowToSave == SMC_NETWORK)
		{
		if (!GetSourceID()[0])
			{
			SetOriginNodeName(cfg.nodeTitle);
			SetOriginRegion(cfg.nodeRegion);
			SetOriginCountry(cfg.nodeCountry);
			SetSourceID(GetLocalID());
			SetSourceRoomName(GetCreationRoom());

			label Buffer;
			strcpy(Buffer, programName);
			strcat(Buffer, version);
			SetOriginSoftware(Buffer);

			SetTwitRegion(cfg.twitRegion);
			SetTwitCountry(cfg.twitCountry);

			SetCit86Country(cfg.Cit86Country);

			SetOriginPhoneNumber(cfg.nodephone);
			}

		if (GetFromPath()[0])
			{
			char TempPath[PATHSIZE + 1];
			int i, j;
			label lastlocID;

			strcpy(TempPath, GetFromPath());
			strcat(TempPath, getmsg(1485));

			// Get last region
			for (i = strlen(TempPath); i && TempPath[i] != '.'; i--);

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

			lastlocID[j] = '\0';

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

			SetFromPath(TempPath);
			}
		else
			{
			SetFromPath(cfg.Address);
			}

		if (*GetEZCreationTime())
			{
			label Buffer;
			SetCreationTime(ltoa(net69_time(GetEZCreationTime()), Buffer, 10));
			}

		if (!GetCreationTime()[0])
			{
			label Buffer;
			SetCreationTime(ltoa(time(NULL), Buffer, 10));
			}

		// kludge.
		if (TI()node && TI()node->IsGateway() && !GetEZCreationTime()[0])
			{
			label Buffer;
			SetEZCreationTime(net69_gateway_time(atol(GetCreationTime()), Buffer));
			}
		}

#define SaveField(f,l)						\
	{										\
	if (*f())								\
		{									\
		(*WriteFileChar)(SaveFile, l);		\
		(*WriteFileString)(SaveFile, f());	\
		}									\
	}


	SaveField(GetAuthor,				'A');
	SaveField(GetCreationTime,			'D');
	SaveField(GetOriginNodeName,		'O');
	SaveField(GetSourceID,				'S');
	// above four fields (SODA) for net double checking speed

	// 'a' - MavenAnon poop             'a'

	SaveField(GetSubject,				'B');

	if (HowToSave == SMC_NETWORK)
		{
		SaveField(GetEZCreationTime,	'd');
		}

	if (HowToSave != SMC_NETWORK)
		{
		SaveField(GetCopyOfMessage, 	'C');
		}

	SaveField(GetForward,				'F');
	SaveField(GetGroup, 				'G');
	SaveField(GetReplyToMessage,		'I');
	SaveField(GetTwitRegion,			'J');
	SaveField(GetTwitCountry,			'j');

	if (HowToSave != SMC_NETWORK)
		{
		SaveField(GetFileLink,			'L');
		SaveField(GetLinkedApplication, 'l');
		}

	SaveField(GetTitle, 				'N');
	SaveField(GetSurname,				'n');
	SaveField(GetOriginRegion,			'o');
	SaveField(GetFromPath,				'P');
	SaveField(GetToPath,				'p');
	SaveField(GetOriginCountry, 		'Q');
	SaveField(GetToCountry, 			'q');
	SaveField(GetCreationRoom,			'R');
	SaveField(GetSourceRoomName,		'r');
	SaveField(GetOriginSoftware,		's');
	SaveField(GetToUser,				'T');
	SaveField(GetPublicToUser,			't');
	SaveField(GetCit86Country,			'U');

	if (HowToSave != SMC_NETWORK)
		{
		SaveField(GetX, 				'X');
		}

	SaveField(GetToNodeName,			'Z');
	SaveField(GetToRegion,				'z');
	SaveField(GetSignature, 			'.');
	SaveField(GetUserSignature, 		'_');
	SaveField(GetRealName,				'!');
	SaveField(GetOriginPhoneNumber, 	'H');
	SaveField(GetToPhoneNumber, 		'h');
	SaveField(GetDestinationAddress,	'>');

	SaveField(GetMoreFlags, 			'#');

	// put any comments...
	for (strList *sl = Comments; sl;
			sl = (strList *) getNextLL(sl))
		{
		(*WriteFileChar)(SaveFile,		'%');
		(*WriteFileString)(SaveFile, sl->string);
		}

	// put any unknowns...
	for (unkLst *lul = firstUnk; lul;
			lul = (unkLst *) getNextLL(lul))
		{
		(*WriteFileChar)(SaveFile, lul->whatField);
		(*WriteFileString)(SaveFile, lul->theValue);
		}

	// The actual message text is last
	(*WriteFileChar)(SaveFile,			'M');
	(*WriteFileString)(SaveFile, GetText());

	// now finish writing
	if (HowToSave == SMC_NORMAL || HowToSave == SMC_RESIZE)
		{
		bufferedFlush(msgfl);

		// record where to begin writing next message
		cfg.catLoc = bufferedTell(msgfl);
		}

	if (HowToSave == SMC_NORMAL)
		{
		IncrementRoomTotalMessages(GetRoomNumber());

		if (MaySeeMsg(this) == MSM_GOOD)
			{
			IncrementRoomMessages(GetRoomNumber());
			IncrementRoomNewMessages(GetRoomNumber());
			}
		}

	if (HowToSave == SMC_NORMAL || HowToSave == SMC_RESIZE)
		{
		bufferedWinCloseTmp(msgfl);
		}
	}


// --------------------------------------------------------------------------
// systemMessage(): Save messages from node.

void systemMessage(Message *Msg)
	{
	Msg->SetAuthor(cfg.nodeTitle);
	Msg->SetLocal(TRUE);
	putAndNoteMessage(Msg, TRUE);
	}


// --------------------------------------------------------------------------
// putMsgChar(): Writes character to message file.

void putMsgChar(char c)
	{
	if (!bufferedPutc(c, msgfl))
		{
		bufferedSeek(msgfl, 0l);
		bufferedPutc(c, msgfl);
		}
	}


#ifndef AUXMEM
#ifdef WINCIT
// --------------------------------------------------------------------------
// crunchmsgTab(): Obliterates slots at the beginning of table.
//	Really slow Windows version.

void crunchmsgTab(ulong howmany)
	{
	ulong i;
	int room;

	for (i = 0; i < howmany; ++i)
		{
		if (msgTabWin[i].mtmsgflags.INUSE)
			{
			room = msgTabWin[i].mtroomno;

			DecrementRoomTotalMessages(room);

			if (MaySeeIndexMsg(i) == MSM_GOOD)
				{
				DecrementRoomMessages(room);

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

			// remove from our list of rooms
			assert(msgTabWin[i].PrevRoomMsg == M_SLOT_ERROR);
			FirstMessageInRoom[room] = msgTabWin[i].NextRoomMsg;

			if (FirstMessageInRoom[room] == M_SLOT_ERROR)
				{
				// no more messages in this room
				LastMessageInRoom[room] = M_SLOT_ERROR;
				}
			else
				{
				assert(indexslot(FirstMessageInRoom[room]) != M_SLOT_ERROR);
				msgTabWin[indexslot(FirstMessageInRoom[room])].PrevRoomMsg = M_SLOT_ERROR;
				}
			}
		}

	for (i = 0; i < cfg.nmessages - howmany; i++)
		{
		memcpy (&(msgTabWin[i]), &(msgTabWin[i + howmany]), sizeof(messagetable));
		}

	cfg.mtoldest += howmany;
	}

#else


// --------------------------------------------------------------------------
// crunchmsgTab(): Obliterates slots at the beginning of table.
//	Regular version.

void crunchmsgTab(ulong cowmany)
	{
	int i;
	int room;
	int howmany = (int) cowmany;
	uint total = (uint)(cfg.nmessages - howmany);

	for (i = 0; i < howmany; ++i)
		{
		if (msgTab_mtmsgflags[i].IsInuse())
			{
			room = msgTab_mtroomno[i];

			DecrementRoomTotalMessages(room);

			if (MaySeeIndexMsg(i) == MSM_GOOD)
				{
				DecrementRoomMessages(room);

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

	memmove(msgTab_mtmsgflags, &(msgTab_mtmsgflags[howmany]),
			(total * sizeof(*msgTab_mtmsgflags)));

	memmove(msgTab_mtmsgLocLO, &(msgTab_mtmsgLocLO[howmany]),
			(total * sizeof(*msgTab_mtmsgLocLO)));

	memmove(msgTab_mtmsgLocHI, &(msgTab_mtmsgLocHI[howmany]),
			(total * sizeof(*msgTab_mtmsgLocHI)));

	memmove(msgTab_mtroomno, &(msgTab_mtroomno[howmany]),
			(total * sizeof(*msgTab_mtroomno)));

	memmove(msgTab_mttohash, &(msgTab_mttohash[howmany]),
			(total * sizeof(*msgTab_mttohash)));

	memmove(msgTab_mtauthhash, &(msgTab_mtauthhash[howmany]),
			(total * sizeof(*msgTab_mtauthhash)));

	memmove(msgTab_mtomesg, &(msgTab_mtomesg[howmany]),
			(total * sizeof(*msgTab_mtomesg)));

	cfg.mtoldest += howmany;
	}
#endif

#else


// --------------------------------------------------------------------------
// crunchmsgTab(): Obliterates slots at the beginning of table.
//	Auxmem version.

void crunchmsgTab(m_slot howmany)
	{
	m_slot i;
	m_slot total = cfg.nmessages - howmany;
	m_slot total2 = sizetable() - howmany;

	while (howmany > MSGTABPERPAGE)
		{
		crunchmsgTab(MSGTABPERPAGE);
		howmany -= MSGTABPERPAGE;
		}

	for (i = 0; i < howmany; ++i)
		{
		if (getFlags(i)->IsInuse())
			{
			const r_slot room = getRoomNum(i);

			DecrementRoomTotalMessages(room);

			if (MaySeeIndexMsg(i) == MSM_GOOD)
				{
				DecrementRoomMessages(room);

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

			// remove from our list of rooms
			assert(getPrevRoomMsg(i) == M_SLOT_ERROR);
			FirstMessageInRoom[room] = getNextRoomMsg(i);

			if (FirstMessageInRoom[room] == M_SLOT_ERROR)
				{
				// no more messages in this room
				LastMessageInRoom[room] = M_SLOT_ERROR;
				}
			else
				{
				assert(indexslot(FirstMessageInRoom[room]) != M_SLOT_ERROR);
				getMsgTab(indexslot(FirstMessageInRoom[room]))->PrevRoomMsg =
						M_SLOT_ERROR;
				}
			}
		}

	for (i = 0; i < total2; i += MSGTABPERPAGE)
		{
		memmove(getMsgTab(i), getMsgTab(i + howmany),
				sizeof(messagetable) * (MSGTABPERPAGE - (uint) howmany));

		if (i + MSGTABPERPAGE < total)
			{
			memcpy(getMsgTab(i + MSGTABPERPAGE - howmany),
					getMsgTab(i + MSGTABPERPAGE),
					sizeof(messagetable) * (uint) howmany);
			}
		}

	cfg.mtoldest += howmany;
	}
#endif


// --------------------------------------------------------------------------
// overwrite(): Checks for any overwriting of old messages.

void overwrite(int bytes)
	{
	long pos;
	int i;

	pos = bufferedTell(msgfl);

	for (i = 0; i < bytes; ++i)
		{
		if (getMsgChar() == 0xFF) // obliterating a message
			{
			++cfg.oldest;
			}
		}

	bufferedSeek(msgfl, pos);
	}


// --------------------------------------------------------------------------
// putMsgStr(): Writes a string to the message file.

static void putMsgStr(const char *string)
	{
	const char *s;

	// check for obliterated messages
	overwrite(strlen(string) + 1); // the '+1' is for the null

	for (s = string; *s; s++)
		{
		putMsgChar(*s);
		}

	putMsgChar(0);
	}


// --------------------------------------------------------------------------
// noteMessage(): Puts passed message into message index.

static void noteMessage(Message *Msg)
	{
	ulong id;
	int crunch = 0;

	++cfg.newest;

	id = atol(Msg->GetLocalID());

	assert(id == cfg.newest);

	// mush up any obliterated messages
	if (cfg.mtoldest < cfg.oldest)
		{
		crunch = ((ushort) (cfg.oldest - cfg.mtoldest));
		}

	// scroll index at #nmessages mark
	if ((id - cfg.mtoldest) >= cfg.nmessages)
		{
		crunch++;
		}

	if (crunch)
		{
		crunchmsgTab(crunch);
		}

	// now record message info in index
	indexmessage(Msg);
	}

// because putMessage and noteMessage are different. (silly)
static void checkAutoArchive(Message *Msg)
	{
	if (IsRoomArchive(Msg->GetRoomNumber()) && !Msg->GetToUser()[0])
		{
		aRoom rBuf;

		getRoom(Msg->GetRoomNumber(), &rBuf);

		if (rBuf.archive[0])
			{
			if (!journalfl)
				{
				char PathToJournal[DOSPATHSIZE + sizeof(rBuf.archive) + 1];
				sprintf(PathToJournal, sbs, cfg.homepath, rBuf.archive);

				if ((journalfl = fopen(PathToJournal, FO_A)) != NULL)
					{
					TI()MRO.Verbose = TRUE;
					const int savecrt = TI()OC.CrtColumn;

					Message *LoadedMsg = LoadMessageByID(atol(Msg->GetLocalID()),
							FALSE, FALSE);

					if (LoadedMsg)
						{
						LoadedMsg->Decompress();
						ActuallyDisplayMessage(LoadedMsg, TRUE, TRUE, TRUE);

						delete LoadedMsg;
						}

					TI()OC.CrtColumn = savecrt;
					fclose(journalfl);

					journalfl = NULL;

					// LoadedMsg is still NULL or old valid address. Check
					// out of memory here; need to have no journalfl first.
					if (!LoadedMsg)
						{
						mPrintf(getmsg(188), getmsg(1693));
						}
					}
				else
					{
					mPrintf(getmsg(1144));
					}
				}
			else
				{
				DebugOut(getdbmsg(50));
				}
			}
		else
			{
			DebugOut(getdbmsg(51));
			}
		}
	}


Bool putAndNoteMessage(Message *Msg, Bool censor)
	{
	PrepareMsgScriptInit(Msg);
	doEvent(EVT_STOREMESSAGE, InitMsgScript);

	if (CheckMsgScript(Msg))
		{
		Bool ret = FALSE;

		Msg->Store(SMC_NORMAL, NULL);
		noteMessage(Msg);

		if (censor)
			{
			ret = censor_message(Msg);
			}

		checkAutoArchive(Msg);

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

void msgtosysop(Message *Msg)
	{
	char string[64];

	if (!HaveConnectionToUser() || TI()CurrentUser->IsMainSysop() ||
			TI()CurrentUser->IsNode())
		{
		return;
		}

	sprintf(string, getmsg(1252), cfg.Lmsg_nym);

	if (getYesNo(string, 1))
		{
		Msg->SetToUser(getmsg(1020));

		makeMessage(Msg, NULL);
		}
	}
