// --------------------------------------------------------------------------
// Citadel: Login.CPP
//
// Code to log in a user.

#include "ctdl.h"
#pragma hdrstop

#include "room.h"
#include "tallybuf.h"
#include "viewlog.h"
#include "log.h"
#include "msg.h"
#include "account.h"
#include "hall.h"
#include "events.h"
#include "blurbs.h"
#include "menus.h"
#include "aplic.h"
#include "cwindows.h"
#include "miscovl.h"
#include "term.h"


// --------------------------------------------------------------------------
// Contents
//
// login()			is the menu-level routine to log someone in
// setroomgen() 	sets room gen# with log gen
// setlbvisit() 	sets lbvisit at log-in
// slideLTab()		crunches up slot, then frees slot at beginning,
//							it then copies information to first slot
// minibin()		minibin log-in stats
// pwslot() 		returns logtab slot password is in, else CERROR
// pwexists()		returns TRUE if password exists in logtable
// doLogin()		handles L(ogin) command


static l_slot pwslot(const char *in, const char *pw);
static void minibin(int CallersSinceLastCall, long CreditsAtLastCall);


// --------------------------------------------------------------------------
// doLogin(): The menu-level routine to log someone in.

void doLogin(Bool moreYet)
	{
	char InitPw[LABELSIZE+LABELSIZE+2];
	char password[LABELSIZE+LABELSIZE+2];
	char initials[LABELSIZE+LABELSIZE+2];
	struct date dtnow, dtthen;
	struct time tm;
	long CreditsAtLastCall;

	SetDoWhat(LOGIN);

	compactMemory(1);

	TI()SerialPort.Flush();

	if (login_user || login_pw)
		{
		// handle command line log-ins

		if (!TI()modStat && cfg.offhook)
			{
			offhook();
			}

		if (login_pw)
			{
			// login using initials and pw
			normalizepw(cmd_login, initials, password);
			login_pw = FALSE;
			}
		else
			{
			// login using name
			l_slot Slot;

			normalizeString(cmd_login);

			if ((Slot = FindPersonByName(cmd_login)) != CERROR)
				{
				LogEntry1 Log1;

				if (Log1.Load(LogTab->GetEntry(Slot)->GetLogIndex()))
					{
					strcpy(initials, Log1.GetInitials());
					strcpy(password, Log1.GetPassword());
					login_user = FALSE;
					}
				}
			}
		}
	else
		{
		// ask user for initials and password

		if (moreYet == 2)
			{
			moreYet = FALSE;
			}
		else
			{
			// do not print Login when hitting 'L' from console mode
			if (!(!moreYet && !TI()loggedIn && !TI()modStat))
				{
				mPrintf(getmsg(568));

				if (!moreYet)
					{
					doCR();
					}
				}
			}

		if (TI()loggedIn)
			{
			CRCRmPrintfCR(getmsg(569));
			return;
			}

		if (!TI()modStat && cfg.offhook)
			{
			offhook();
			}

		getNormStr(moreYet ? ns : getmsg(570), InitPw,
				LABELSIZE + LABELSIZE + 1, NO_ECHO);

		if (HaveConnectionToUser())
			{
			dospCR();
			}

		const char *semicolon = strchr(InitPw, ';');

		if (!semicolon)
			{
			strcpy(initials, InitPw);
			getNormStr(getmsg(571), password, LABELSIZE, NO_ECHO);
			dospCR();
			}
		else
			{
			normalizepw(InitPw, initials, password);
			}

		// do not allow anything over LABELSIZE characters
		initials[LABELSIZE] = '\0';

		if (!(HaveConnectionToUser()) && (*initials || *password))
			{
			char trapstr[128];

			sprintf(trapstr, getmsg(503), initials, password);
			trap(trapstr, T_HACK);
			return;
			}
		}

	TI()SerialPort.ResetTransmitted();
	TI()SerialPort.ResetReceived();

	TI()MS.Read = 0;
	TI()MS.Entered = 0;

	TI()MS.AutoMark = FALSE;
	TI()MS.AutoKill = FALSE;
	TI()MS.AutoCensor = FALSE;
	TI()MS.MarkedID = 0L;

	const l_slot FoundSlot = pwslot(initials, password);

	if (FoundSlot != CERROR && *password)
		{
		char trapstr[256];

		TI()ThisSlot = FoundSlot;
		TI()ThisLog = LogTab->GetEntry(FoundSlot)->GetLogIndex();

		if (TI()CurrentUser->Load(TI()ThisLog))
			{
			TI()loggedIn = TRUE;
			StatusLine.Update();

			// trap it
			if (!TI()CurrentUser->IsNode())
				{
				if (onConsole)
					{
					trap(getmsg(572), T_LOGIN);
					}

				doEvent(EVT_LOGIN);

				sprintf(trapstr, getmsg(573), TI()CurrentUser->GetName());
				trap(trapstr, T_LOGIN);
				}
			else
				{
				sprintf(trapstr, getmsg(574), TI()CurrentUser->GetName());
				trap(trapstr, T_NETWORK);
				addStatusText(trapstr, time(NULL));
				}
			}
		}
	else
		{
		loginNew(initials, password);
		login_user = FALSE;
		}

	if (!TI()loggedIn)
		{
		return;
		}

	if (TI()MS.AbortedMessage)
		{
		delete TI()MS.AbortedMessage;
		TI()MS.AbortedMessage = NULL;
		}

	TI()OC.Psycho = TI()CurrentUser->IsPsycho();
	setlogTerm();
	setlbvisit();

	if (TI()CurrentUser->IsPrintFile())
		{
		TI()OC.WasPrinting = TI()OC.Printing;

		if (!TI()OC.Printing)
			{
			CopyStringToBuffer(TI()OC.PrintfileName, cfg.printer,
					sizeof(TI()OC.PrintfileName) - 1);
			TI()OC.PrintFile = fopen(TI()OC.PrintfileName, FO_A);
			}

		if (TI()OC.PrintFile)
			{
			TI()OC.Printing = TRUE;
			fprintf(TI()OC.PrintFile, getmsg(545), bn, bn, cfg.Luser_nym,
					TI()CurrentUser->GetName(), bn, bn);
			}
		}

	const int CallersSinceLastCall = TI()ThisSlot;

	slideLTab(TI()ThisSlot);
	cfg.callno++;

	// User can't log in now.
	if (!TI()CurrentUser->IsVerified() && !onConsole)
		{
		dispBlb(B_VERIFIED);

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

		Hangup();

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

		return;
		}

	// User can't log in now.
	if (cfg.accounting && TI()CurrentUser->IsAccounting())
		{
		TI()CurrentUserAccount->Negotiate(TI()CurrentUser);

		CreditsAtLastCall =
				(cfg.accounting && TI()CurrentUser->IsAccounting())
				? TI()CurrentUser->GetCredits() : -1L;

		TI()CurrentUserAccount->GiveDailyIncrement();

		if (!TI()CurrentUserAccount->MayLogin())
			{
			dispBlb(B_NOLOGIN);
			TI()loggedIn = FALSE;
			TI()MRO.Verbose = FALSE;
			terminate(TRUE);

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

			Hangup();

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

			return;
			}
		}

	for (userapps *theUserapp = userAppList; theUserapp;
			theUserapp = (userapps *) getNextLL(theUserapp))
		{
		if (SameString(theUserapp->name, TI()CurrentUser->GetName()))
			{
			mPrintfCR(pcts, theUserapp->outstr);

			RunApplication(theUserapp->cmd, NULL, TRUE, TRUE);

			if (theUserapp->hangup)
				{
				CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
						CitWindowsMsg(NULL, getmsg(1643));

				Hangup();

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

				return;
				}
			}
		}

	if (TI()CurrentUser->IsNode())
		{
		time(&TI()logtimestamp);
		return;
		}

	unixtodos(time(NULL), &dtnow, &tm);
	unixtodos(TI()CurrentUser->GetCallTime(), &dtthen, &tm);

	if (!memcmp(&dtnow, &dtthen, sizeof(dtnow)))
		{
		TI()CurrentUser->SetCallsToday(TI()CurrentUser->GetCallsToday() + 1);
		}
	else
		{
		TI()CurrentUser->SetCallsToday(1);
		}

	if ((TI()CurrentUser->GetCallLimit() &&
			TI()CurrentUser->GetCallsToday() > TI()CurrentUser->GetCallLimit()) ||

			(cfg.callLimit &&
			TI()CurrentUser->GetCallsToday() > cfg.callLimit))
		{
		dispBlb(B_TOOMANY);

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

		Hangup();

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

		return;
		}

	gotodefaulthall();

	const Bool isJoke = BLBRotate(getmsg(1578), getmsg(1438), &cfg.normJoke,
			&cfg.ansiJoke, 1, MAXBLBS) != CERROR;

	if (!isJoke)
		{
		mPrintfCR(getmsg(520));
		}

	roomtalley();

	if (isJoke)
		{
		BLBRotate(getmsg(1578), getmsg(1438), &cfg.normJoke, &cfg.ansiJoke,
				2, MAXBLBS);
		}

	// reverse engineering Minibin?!?!
	if (TI()CurrentUser->IsMinibin())
		{
		minibin(CallersSinceLastCall, CreditsAtLastCall);
		}

	if (cfg.pwDays &&
			((time(NULL) - TI()CurrentUser->GetPasswordChangeTime()) /
			(24*60*60)) > cfg.pwDays)
		{
		CRmPrintfCR(getmsg(1100));
		newPW(FALSE);
		}

	if (TI()CurrentUser->GetNumUserShow())
		{
		resetMsgfilter(FALSE);
		Readlog(FALSE, OldAndNew, TI()CurrentUser->GetNumUserShow());
		}

	// reset chats
	nochat(TRUE);

	TI()MRO.Verbose = TI()CurrentUser->IsAutoVerbose();
	TI()roomsMade = 0;

	doReadBull(FALSE);

	if (TI()CurrentUser->GetMessage())
		{
		char string[128];

		dispBlb(B_RESUME);

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

		if (getYesNo(string, 0))
			{
			Message *Msg = new Message;

			if (Msg)
				{
				*Msg = *TI()CurrentUser->GetMessage();
				TI()CurrentUser->SetMessage(NULL);

				// relocate for anon
				const r_slot currentRoom = TI()thisRoom;
				TI()thisRoom = TI()CurrentUser->GetMessageRoom();

				if (*Msg->GetToUser())
					{
					TI()OC.Echo = CALLER;
					setio();
					}

				makeMessage(Msg, NULL, TI()CurrentUser->GetMessageRoom());

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

				delete Msg;

				// and come back
				TI()thisRoom = currentRoom;
				}
			else
				{
				mPrintf(getmsg(188), getmsg(768));
				}
			}
		else
			{
			doCR();

			assert(!TI()MS.AbortedMessage);
			TI()MS.AbortedMessage = new Message;

			if (TI()MS.AbortedMessage)
				{
				*TI()MS.AbortedMessage = *TI()CurrentUser->GetMessage();
				mPrintfCR(getmsg(523), cfg.Umsg_nym);
				}
			else
				{
				mPrintfCR(getmsg(188), getmsg(768));
				}
			}

		TI()CurrentUser->SetMessage(NULL);
		}

	// hmmm... where to put this
	if (IsRoomApplication(TI()thisRoom) && IsCurrentRoomAutoApp())
		{
		ExeAplic();
		}

	// why???
	if (TI()CurrentUser->IsClearScreenBetweenMessages())
		{
		doCR();
		TI()UserControl.CheckInput(TRUE);
		}

	ClearMsgReadOptions(&TI()MRO);
	resetMsgfilter(TRUE);
	showMessages(NewOnly, FALSE, 0);

	TI()MRO.Verbose = FALSE;
	if (TI()CurrentUser->IsExpert())
		{
		listRooms(K_NEW);
		}
	else
		{
		listRooms(0);
		}

	TI()UserControl.SetOutFlag(OUTOK);

	// record login time, date
	time(&TI()logtimestamp);

	storeLog();
	}


// --------------------------------------------------------------------------
// setlbvisit(): Sets lbvisit at log-in.

void setlbvisit(void)
	{
	int i;

	// see if the message base was cleared since last call
	for (i = 0; i < cfg.maxrooms; i++)
		{
		if (TI()CurrentUser->GetRoomNewPointer(i) > cfg.newest)
			{
			for (i = 0; i < cfg.maxrooms; i++)
				{
				TI()CurrentUser->SetRoomNewPointer(i, cfg.oldest);
				}

			CRmPrintfCR(getmsg(575), cfg.Umsg_nym);
			mPrintfCR(getmsg(576), cfg.Lmsg_nym);
			break;
			}
		}

	for (i = 0; i < cfg.maxrooms; i++)
		{
		SetRoomBypass(i, FALSE);
		SetRoomMessagesEntered(i, 0);
		}
	}


// --------------------------------------------------------------------------
// slideLTab(): Crunches up slot, then frees slot at beginning. It then
//	copies information to first slot.

void slideLTab(l_slot slot) // slot is current tabslot being moved
	{
	// if at the top - stay there
	if (!slot)
		{
		return;
		}

	const l_slot ourIndex = LogTab->GetEntry(slot)->GetLogIndex();

	LogTab->SlideTableDown(slot);

	TI()ThisSlot = 0;

	// insert our log info at the beginning of table
	log2tab(LogTab->GetEntry(0), TI()CurrentUser, ourIndex);
	}


// --------------------------------------------------------------------------
// minibin(): Minibin log-in stats.

static void minibin(int CallersSinceLastCall, long CreditsAtLastCall)
	{
	char dtstr[80];

	const long messages = (cfg.newest >= TI()CurrentUser->GetLastMessage()) ?
			(cfg.newest - TI()CurrentUser->GetLastMessage()) : 0;

	const long calls = (cfg.callno > TI()CurrentUser->GetCallNumber()) ?
			(cfg.callno - 1 - TI()CurrentUser->GetCallNumber()) : -1;

	if (!TI()CurrentUser->IsExpert())
		{
		CRmPrintfCR(getmsg(577));
		}

	CRmPrintf((calls > -1l) ? getmsg(578) : getmsg(579));

	if (cfg.titles && TI()CurrentUser->GetTitle()[0] &&
			TI()CurrentUser->IsViewTitleSurname())
		{
		mPrintf(getmsg(1580), TI()CurrentUser->GetTitle());
		}

	mPrintf(getmsg(1581), TI()CurrentUser->GetName());

	if (cfg.surnames && TI()CurrentUser->GetSurname()[0] &&
			TI()CurrentUser->IsViewTitleSurname())
		{
		mPrintf(getmsg(1582), TI()CurrentUser->GetSurname());
		}

	mPrintfCR(getmsg(1485));

	strftime(dtstr, 79, (TI()loggedIn) ?
			TI()CurrentUser->GetVerboseDateStamp() : cfg.vdatestamp,
			time(NULL));

	mPrintfCR(getmsg(504), dtstr);

	if (calls != -1)
		{
		strftime(dtstr, 79, (TI()loggedIn) ?
				TI()CurrentUser->GetVerboseDateStamp() : cfg.vdatestamp,
				TI()CurrentUser->GetCallTime());

		if (TI()CurrentUser->GetCallTime() < time(NULL))
			{
			mPrintfCR(getmsg(581), dtstr,
					diffstamp(TI()CurrentUser->GetCallTime()));
			}
		else
			{
			mPrintfCR(getmsg(1691), dtstr);
			}

		if (calls == 0)
			{
			mPrintfCR(getmsg(580));
			}
		else
			{
			mPrintfCR(getmsg(582), ltoac(cfg.callno));

			mPrintfCR(getmsg(583), ltoac(CallersSinceLastCall),
					CallersSinceLastCall == 1 ? getmsg(584) : getmsg(585));

			mPrintfCR(getmsg(586), ltoac(calls),
					calls == 1 ? getmsg(587) : getmsg(588));

			mPrintfCR(getmsg(589), ltoac(messages), (messages == 1) ?
					cfg.Lmsg_nym : cfg.Lmsgs_nym);
			}
		}

	Bool *roomCheck = new Bool[cfg.maxrooms];
	if (roomCheck)
		{
		ulong newMsgs = 0;

		theOtherAlgorithm(roomCheck, TI()thisHall, TRUE);

		for (r_slot rmCnt = 0; rmCnt < cfg.maxrooms; rmCnt++)
			{
			if (roomCheck[rmCnt] && !TI()CurrentUser->IsRoomExcluded(rmCnt))
				{
				newMsgs += GetRoomNewMessages(rmCnt);
				}
			}

		mPrintfCR(getmsg(505), ltoac(newMsgs),
				newMsgs == 1 ? cfg.Lmsg_nym : cfg.Lmsgs_nym);

		delete [] roomCheck;
		}

	if (cfg.accounting && TI()CurrentUser->IsAccounting())
		{
		if (!TI()CurrentUserAccount->IsSpecialTime())
			{
			const long C = TI()CurrentUser->GetCredits() / 60;

			mPrintf(getmsg(590), ltoac(C),
					(C == 1) ? cfg.Lcredit_nym : cfg.Lcredits_nym);

			const long CreditDifferenceDiv60 = C - (CreditsAtLastCall / 60);

			if (CreditDifferenceDiv60 > 0)
				{
				mPrintfCR(getmsg(1694), ltoac(CreditDifferenceDiv60));
				}
			else if (CreditDifferenceDiv60 < 0)
				{
				mPrintfCR(getmsg(1695), ltoac(-CreditDifferenceDiv60));
				}
			else
				{
				mPrintfCR(getmsg(1696));
				}
			}
		else
			{
			mPrintfCR(getmsg(591), cfg.Lcredits_nym);
			}
		}

	TI()UserControl.SetOutFlag(OUTOK);
	}


// --------------------------------------------------------------------------
// pwslot(): Returns logtab slot password is in, else CERROR.

l_slot pwslot(const char *in, const char *pw)
	{
	if (strlen(pw) < 2)
		{
		return (CERROR);	// Don't search for short pwds
		}

	const l_slot slot = FindPwInHashInTable(in, pw);

	if (slot != CERROR)
		{
		LogEntry1 Log1;

		if (Log1.Load(LogTab->GetEntry(slot)->GetLogIndex()))
			{
			if (SameString(pw, Log1.GetPassword()) &&
					SameString(in, Log1.GetInitials()))
				{
				return (slot);
				}
			}
		}

	return (CERROR);
	}


// --------------------------------------------------------------------------
l_slot FindPwInHashInTable(const char *in, const char *pw)
	{
	const int inhash = hash(in);
	const int pwhash = hash(pw);

	for (l_slot i = 0; i < cfg.MAXLOGTAB; i++)
		{
		if (pwhash == LogTab->GetEntry(i)->GetPasswordHash() &&
				inhash == LogTab->GetEntry(i)->GetInitialsHash())
			{
			return (i);
			}
		}

	return (CERROR);
	}
