// --------------------------------------------------------------------------
// Citadel: Help.CPP
//
// BLB, HLP, and MNU files.

#include "ctdl.h"
#pragma hdrstop

#include "room.h"
#include "log.h"
#include "msg.h"
#include "hall.h"
#include "menus.h"
#include "blurbs.h"
#include "helpfile.h"


// --------------------------------------------------------------------------
// Contents
//
// dumpf()			does formatted dump of specified file
// goodbye()		prints random goodbye blurbs
// hello()			prints random hello blurbs
// tutorial()		dumps fomatted help files, handles wildcarding
// dispBlb()		Displays BLBs to users
// nochat() 		Prints a nochat BLB, handle rotation
// BLBRotate()		prints rotating blurbs
// ShowHelpMenu()	List available helpfiles.

static long ansi_menus[MAXMENUS];
static long norm_menus[MAXMENUS];


// --------------------------------------------------------------------------
// dumpf(): Does formatted dump of specified file.

int dumpf(const char *filename, Bool format, int special)
	{
#ifndef WINCIT
	FILE *fbuf;
	int returnval = TRUE, alloced = TRUE;
	char line[MAXWORD];
	int bufSize;
	char *buffer = NULL;
	int foundSpecial = FALSE;

	// last itteration might have been N>exted
	TI()UserControl.SetOutFlag(OUTOK);

	doCR();

	if ((fbuf = fopen(filename, FO_R)) == NULL)
		{
#endif
		if (!special)
			{
			mPrintfCR(getmsg(1050), filename);
			}

		return (CERROR);
#ifndef WINCIT
		}

	// gotos can only be within a single mFormat call; try to dump as much
	// of the file at a time if TI()OC.UseMCI. Else, don't worry about such
	// things and do it line by line, which looks smoother to the caller on
	// a slow machine. (Not a big bunch of file access at the start and at
	// any buffer breaks.

	if (TI()OC.UseMCI)
		{
		bufSize = (int) min(filelength(fileno(fbuf)), 32000l);

		compactMemory();

		while (((buffer = (char *) getMemG(bufSize, 1)) == NULL) && (bufSize > 1000))
			{
			bufSize -= 1000;
			}
		}

	if (!buffer)
		{
		buffer = line;
		bufSize = MAXWORD;
		alloced = FALSE;
		}

	TI()OC.Formatting = format;

	// looks like a kludge, but we need speed!!
	while (fgets(buffer, bufSize, fbuf) && (TI()UserControl.GetOutFlag() != OUTNEXT) &&
			(TI()UserControl.GetOutFlag() != OUTSKIP) && !TI()UserControl.CheckInput(FALSE))
		{
		if (special != 2 && !foundSpecial)
			{
			foundSpecial = buffer[0] == '#';
			}

		if (!special || (special == 1 && !foundSpecial) ||
				(special == 2 && foundSpecial))
			{
			mFormat(buffer);
			}

		if (!foundSpecial)
			{
			foundSpecial = buffer[0] == '#';
			}
		}

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

	fclose(fbuf);

	if (alloced)
		{
		freeMemG(buffer);
		}

	ResetOutputControl();
	return (returnval);
#endif
	}


// --------------------------------------------------------------------------
// dumpfc(): Formatted dump of specified file to console.

int dumpfc(const char *filename, Bool format)
	{
#ifndef WINCIT
	FILE *fbuf;
	int returnval = TRUE, oldModem, alloced = TRUE;
	char line[MAXWORD];
	int bufSize;
	char *buffer = NULL;

	// last itteration might have been N>exted
	TI()UserControl.SetOutFlag(OUTOK);

	oldModem = TI()OC.Modem;
	TI()OC.Modem = FALSE;
	doCR();

	if ((fbuf = fopen(filename, FO_R)) == NULL)
		{
		mPrintf(getmsg(1050), filename);
		TI()OC.Modem = oldModem;
		return (CERROR);
		}

	// gotos can only be within a single mFormat call; try to dump as much
	// of the file at a time if TI()OC.UseMCI. Else, don't worry about such
	// things and do it line by line, which looks smoother to the caller on
	// a slow machine. (Not a big bunch of file access at the start and at
	// any buffer breaks.

	if (TI()OC.UseMCI)
		{
		bufSize = (int) min(filelength(fileno(fbuf)), 32000l);

		compactMemory();

		while (((buffer = (char *) getMemG(bufSize, 1)) == NULL) && (bufSize > 1000))
			{
			bufSize -= 1000;
			}
		}

	if (!buffer)
		{
		buffer = line;
		bufSize = MAXWORD;
		alloced = FALSE;
		}

	// looks like a kludge, but we need speed!!
	TI()OC.Formatting = format;

	while (fgets(buffer, bufSize, fbuf) && (TI()UserControl.GetOutFlag() != OUTNEXT) &&
			(TI()UserControl.GetOutFlag() != OUTSKIP) && !TI()UserControl.CheckInput(FALSE))
		{
		mFormat(buffer);
		}

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

	fclose(fbuf);

	if (alloced)
		{
		freeMemG(buffer);
		}

	ResetOutputControl();
	TI()OC.Modem = oldModem;
	return (returnval);
#endif
	}


// --------------------------------------------------------------------------
// dispBlb(): Displays BLBs to users.

void dispBlb(BlurbFiles theBlb)
	{
	Bool shown = FALSE;
	label Blb;

	strcpy(Blb, blbnames[theBlb]);
	strcat(Blb, getmsg(1435));

	if (changedir(cfg.helppath))
		{
		label temp;

		strcpy(temp, Blb);
		temp[strlen(temp) - 1] = '@';

		if (*TI()term.bold && filexists(temp))
			{
			dumpf(temp, FALSE, FALSE);
			shown = TRUE;
			}
		else
			{
			if (filexists(Blb))
				{
				dumpf(Blb, TRUE, FALSE);
				shown = TRUE;
				}
			else
				{
				if (*cfg.helppath2)
					{
					changedir(cfg.helppath2);
					if (*TI()term.bold && filexists(temp))
						{
						dumpf(temp, FALSE, FALSE);
						shown = TRUE;
						}
					else
						{
						if (filexists(Blb))
							{
							dumpf(Blb, TRUE, FALSE);
							shown = TRUE;
							}
						}
					}
				}
			}
		}

	if (!shown)
		{
		discardable *d;

		if ((d = readData(13, theBlb, theBlb)) != NULL)
			{
			Bool first = TRUE;

			for (int i = 0; ((char **) d->aux)[i][0] != '#'; i++)
				{
				if (first)
					{
					doCR();
					first = FALSE;
					}

				mFormat(((char **) d->aux)[i]);
				mFormat(bn);
				}

			doCR();

			discardData(d);
			}
		else
			{
			CRmPrintfCR(getmsg(188), getmsg(192));
			}
		}
	else
		{
		doCR();		// like above
		}

	changedir(cfg.homepath);
	}


// --------------------------------------------------------------------------
// dispHlp(): Displays default HLPs to users.

void dispHlp(HelpFiles theHlp)
	{
	TI()UserControl.SetOutFlag(OUTOK);

	Bool shown = FALSE;
	label Hlp;

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

	strcpy(Hlp, hlpnames[theHlp]);
	strcat(Hlp, getmsg(1436));

	if (changedir(cfg.helppath))
		{
		label temp;

		strcpy(temp, Hlp);
		temp[strlen(temp) - 1] = '@';

		if (*TI()term.bold && filexists(temp))
			{
			dumpf(temp, FALSE, FALSE);
			shown = TRUE;
			}
		else
			{
			if (filexists(Hlp))
				{
				dumpf(Hlp, TRUE, FALSE);
				shown = TRUE;
				}
			else
				{
				if (*cfg.helppath2)
					{
					changedir(cfg.helppath2);
					if (*TI()term.bold && filexists(temp))
						{
						dumpf(temp, TRUE, FALSE);
						shown = TRUE;
						}
					else
						{
						if (filexists(Hlp))
							{
							dumpf(Hlp, TRUE, FALSE);
							shown = TRUE;
							}
						}
					}
				}
			}
		}

	if (!shown)
		{
		discardable *d;

		doCR();

		if ((d = readData(14, theHlp, theHlp)) != NULL)
			{
			for (int i = 0; ((char **) d->aux)[i][0] != '#'; i++)
				{
				mFormat(((char **) d->aux)[i]);
				mFormat(bn);
				}

			doCR();

			discardData(d);
			}
		else
			{
			mPrintfCR(getmsg(188), getmsg(193));
			}
		}
	else
		{
		doCR();		// like above
		}

	changedir(cfg.homepath);
	}


// --------------------------------------------------------------------------
// tutorial(): Dumps fomatted help files, handles wildcarding.

void tutorial(const char *filename, const Bool *ShownInternal)
	{
	int i;
	char temp[14];

	TI()UserControl.SetOutFlag(OUTOK);

	if (!changedir(cfg.helppath))
		{
		return;
		}

	// no bad files
	if (!IsFilenameLegal(filename, FALSE))
		{
		CRmPrintfCR(getmsg(1050), filename);
		changedir(cfg.homepath);
		return;
		}

	if (ambig(filename))
		{
		directoryinfo *files = filldirectory(filename, 1, OldAndNew, FALSE);

		if (files)
			{
			for (i = 0; files[i].name[0] ; i++)
				{
				if (ShownInternal)
					{
					label WithoutExtension;
					int j;

					strcpy(WithoutExtension, files[i].name);

					char *Period = strchr(WithoutExtension, '.');

					if (Period)
						{
						*Period = 0;
						}

					for (j = 0; j < MAXHLP; j++)
						{
						if (ShownInternal[j] && SameString(WithoutExtension,
								hlpnames[j]))
							{
							break;
							}
						}

					if (j != MAXHLP)
						{
						continue;
						}
					}

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

				if (dumpf(files[i].name, TRUE, FALSE) == CERROR)
					{
					break;
					}
				else
					{
					doCR();
					}
				}

			if (!i)
				{
				CRmPrintfCR(getmsg(1050), filename);
				}

			delete [] files;
			}
		}
	else
		{
		if (ShownInternal)
			{
			label WithoutExtension;
			int j;

			strcpy(WithoutExtension, filename);

			char *Period = strchr(WithoutExtension, '.');

			if (Period)
				{
				*Period = 0;
				}

			for (j = 0; j < MAXHLP; j++)
				{
				if (ShownInternal[j] && SameString(WithoutExtension,
						hlpnames[j]))
					{
					return;
					}
				}
			}

		strcpy(temp, filename);
		temp[strlen(temp)-1] = '@';

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

		if (*TI()term.bold && filexists(temp))
			{
			dumpf(temp, FALSE, FALSE);
			doCR();
			}
		else
			{
			if (filexists(filename))
				{
				dumpf(filename, TRUE, FALSE);
				doCR();
				}
			else
				{
				if (*cfg.helppath2)
					{
					changedir(cfg.helppath2);
					if (*TI()term.bold && filexists(temp))
						{
						dumpf(temp, TRUE, FALSE);
						doCR();
						}
					else
						{
						if (dumpf(filename, TRUE, FALSE) != CERROR)
							{
							doCR();
							}
						}
					}
				else
					{
					if (dumpf(filename, TRUE, FALSE) != CERROR)
						{
						doCR();
						}
					}
				}
			}
		}

	changedir(cfg.homepath);
	}

// --------------------------------------------------------------------------
// initMenus(): Inits the menus.

void initMenus(void)
	{
	FILE *fl;
	int i;
	char line[90];
	char *words[256];

	for (i = 0; i < MAXMENUS; i++)
		{
		norm_menus[i] = -1;
		ansi_menus[i] = -1;
		}

	if (!changedir(cfg.helppath))
		{
		return;
		}

	if ((fl = fopen(ctdlMnu, FO_R)) != NULL)
		{
		while (fgets(line, 90, fl) != NULL)
			{
			if (line[0] != '#')
				{
				continue;
				}

			parse_it(words, line);

			for (i = 0; i < MAXMENUS; i++)
				{
				if (SameString(words[0] + 1, menunames[i]))
					{
					break;
					}
				}

			if (i < MAXMENUS)
				{
				norm_menus[i] = ftell(fl);
				}
			}
		fclose(fl);
		}

	if ((fl = fopen(ctdlMna, FO_R)) != NULL)
		{
		while (fgets(line, 90, fl) != NULL)
			{
			if (line[0] != '#')
				{
				continue;
				}

			parse_it(words, line);

			for (i = 0; i < MAXMENUS; i++)
				{
				if (SameString(words[0] + 1, menunames[i]))
					{
					break;
					}
				}

			if (i < MAXMENUS)
				{
				ansi_menus[i] = ftell(fl);
				}
			}
		fclose(fl);
		}

	changedir(cfg.homepath);
	}


// --------------------------------------------------------------------------
// getMnuOff(): Gets offset to menu in CTDL.MNU.

long getMnuOff(int reqMnu, char *menuFile)
	{
	if (*TI()term.bold && ansi_menus[reqMnu] >= 0)
		{
		if (menuFile)
			{
			strcpy(menuFile, ctdlMna);
			}

		return (ansi_menus[reqMnu]);
		}
	else
		{
		if (menuFile)
			{
			strcpy(menuFile, ctdlMnu);
			}

		return (norm_menus[reqMnu]);
		}
	}


// --------------------------------------------------------------------------
// showMenu(): Shows the requested menu.

void showMenu(MenuNames reqMnu)
	{
	FILE *fl = NULL;
	long offset;
	char line[MAXWORD];

	TI()UserControl.SetOutFlag(OUTOK);

	doCR();
	doCR();

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

	if (!changedir(cfg.helppath))
		{
		return;
		}

	if (*TI()term.bold && ansi_menus[reqMnu] >= 0)
		{
		fl = fopen(ctdlMna, FO_R);
		offset = ansi_menus[reqMnu];
		}
	else
		{
		if ((offset = norm_menus[reqMnu]) >= 0)
			{
			fl = fopen(ctdlMnu, FO_R);
			}
		}

	if (fl)
		{
		fseek(fl, offset, SEEK_SET);

		while (fgets(line, MAXWORD, fl) && (TI()UserControl.GetOutFlag() != OUTNEXT) &&
				(TI()UserControl.GetOutFlag() != OUTSKIP) && !TI()UserControl.CheckInput(FALSE) && line[0] != '#')
			{
			mFormat(line);
			}

		ResetOutputControl();
		fclose(fl);
		doCR();
		}
	else
		{
		discardable *d;

		if ((d = readData(11, reqMnu, reqMnu)) != NULL)
			{
			int i;

			for (i = 0; ((char **) d->aux)[i][0] != '#'; i++)
				{
				mFormat(((char **) d->aux)[i]);
				mFormat(bn);
				}

			discardData(d);
			}
		else
			{
			mPrintfCR(getmsg(188), getmsg(194));
			}

		doCR();
		}
	}


// --------------------------------------------------------------------------
// hello(): Prints a login BLB, handle rotation.

void hello(void)
	{
	BLBRotate(getmsg(1437), getmsg(1438), &cfg.normHello, &cfg.ansiHello,
			FALSE, B_HELLO);
	}


// --------------------------------------------------------------------------
// cometochat(): Prints a login BLB, handle rotation.

void cometochat(void)
	{
	BLBRotate(getmsg(1439), getmsg(1438), &cfg.normCometoChat,
		&cfg.ansiCometoChat, FALSE, B_CHAT);
	}


// --------------------------------------------------------------------------
// goodbye(): Prints a logout BLB, handle rotation.

void goodbye(void)
	{
	BLBRotate(getmsg(1440), getmsg(1438), &cfg.normBye, &cfg.ansiBye, FALSE,
		B_LOGOUT);
	}


// --------------------------------------------------------------------------
// nochat(): Prints a nochat BLB, handle rotation.

void nochat(Bool reset)
	{
	if (reset)
		{
		TI()ansiChat = 0;
		TI()normChat = 0;
		}
	else
		{
		BLBRotate(getmsg(1441), getmsg(1438), &TI()normChat, &TI()ansiChat,
				FALSE, B_NOCHAT);
		}
	}


// --------------------------------------------------------------------------
// BLBRotate(): Prints rotating blurbs.

int BLBRotate(const char *base, const char *ext, int *reg, int *ansi,
		Bool special, BlurbFiles BlbNum)
	{
	char fn[15];
	char ext2[4];
	int *num;
	int ret;
	Bool UseANSI;

	strcpy(ext2, ext);

	if (!changedir(cfg.helppath))
		{
		return (CERROR);
		}

	// Are we doing ansi or normal
	sprintf(fn, getmsg(1442), base, ext);

	if (*TI()term.bold && filexists(fn))
		{
		ext2[2] = '@';
		num = ansi;
		UseANSI = TRUE;
		}
	else
		{
		num = reg;
		UseANSI = FALSE;
		}

	if (*num == 0)
		{
		sprintf(fn, getmsg(1443), base, ext2);

		if (filexists(fn) || BlbNum == MAXBLBS)
			{
			ret = dumpf(fn, !UseANSI, special);
			}
		else
			{
			dispBlb(BlbNum);
			ret = TRUE;
			}
		}
	else
		{
		sprintf(fn, getmsg(1444), base, *num, ext2);

		if (!filexists(fn))
			{
			sprintf(fn, getmsg(1443), base, ext2);

			if (filexists(fn) || BlbNum == MAXBLBS)
				{
				ret = dumpf(fn, !UseANSI, special);
				}
			else
				{
				dispBlb(BlbNum);
				ret = TRUE;
				}

			*num = 0;
			}
		else
			{
			ret = dumpf(fn, !UseANSI, special);
			}
		}

	if (special != 1)
		{
		(*num)++;
			}

	changedir(cfg.homepath);
	return (ret);
	}

#ifndef WINCIT
// --------------------------------------------------------------------------
// Usage(): Explain the command line switches.

void usage(void)
	{
	discardable *d;

	if ((d = readData(6)) != NULL)
		{
		int i;

		puts(ns);

		for (i = 0; ((char **) d->aux)[i][0] != '#'; i++)
			{
			puts(((char **) d->aux)[i]);
			}

		puts(ns);

		discardData(d);
		}
	else
		{
		printf(getmsg(188), getmsg(195));
		}
	}
#endif


// --------------------------------------------------------------------------
// ShowHelpMenu(): List available helpfiles.

void ShowHelpMenu(void)
	{
	char line[MAXWORD];
	int i;
	int entries = 0;
	char *ptr;

	TI()UserControl.SetOutFlag(OUTOK);

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

	if (getMnuOff(M_HELP, NULL) > 0)
		{
		showMenu(M_HELP);
		}
	else
		{
		directoryinfo *files;
		Bool extFiles[MAXHLP];

		for (i = 0; i < MAXHLP; i++)
			{
			extFiles[i] = FALSE;
			}

		// create list of actual .HLP files in #HELPPATH

		if (!changedir(cfg.helppath))
			{
			return;
			}

		// load our directory structure with *.HLP files
		files = filldirectory(getmsg(1445), 1, OldAndNew, FALSE);

		if (files)
			{
			for (i = 0; files[i].name[0]; ++i);

			entries = i;

			for (i = 0; (i < entries && (TI()UserControl.GetOutFlag() != OUTSKIP) &&
					!TI()UserControl.CheckInput(FALSE)); ++i)
				{
				int j;

				strcpy(line, files[i].name);
				ptr = strstr(line, getmsg(1446));

				if (ptr)
					{
					*ptr = '\0';
					}

				for (ptr = line; *ptr; ++ptr)
					{
					*ptr = toupper(*ptr);
					}

				mPrintfCR(getmsg(1447), line);

				for (j = 0; j < MAXHLP; j++)
					{
					if (SameString(line, hlpnames[j]))
						{
						extFiles[j] = TRUE;
						break;
						}
					}
				}

			delete [] files;

			// fake internal files
			for (i = 0; i < MAXHLP; i++)
				{
				if (!extFiles[i])
					{
					mPrintfCR(getmsg(1447), hlpnames[i]);
					}
				}
			}
		}

	changedir(cfg.homepath);
	}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
	this is the (soon to be) world famous YouAreHere!!!
					 version 6.9

	this is really stupid. if the user is lost, he should just do a known.
	perhaps even a .kw or .rh or something.

	but at least the code doesn't suck any more. it really sucked how it
	was implemented in cencit. the display was way butt-kicking ugly in
	cencit, as well.

 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void youAreHere(void)
	{
	int i;
	r_slot rps;

	CRmPrintf(getmsg(374), cfg.Uhalls_nym);
	prtList(LIST_START);

	for (i = 0; i < cfg.maxhalls; i++)
		{
		if (accesshall(i))
			{
			char str[LABELSIZE + 12];

			sprintf(str, getmsg(850), (i < TI()thisHall) ? getmsg(1448) :
					(i == TI()thisHall) ? getmsg(1449) : getmsg(1450),
					HallData->GetEntry(i)->GetName());

			prtList(str);
			}
		}

	prtList(LIST_END);

	CRmPrintf(getmsg(374), cfg.Urooms_nym);
	prtList(LIST_START);

	rps = GetRoomPosInTable(TI()thisRoom);

	for (i = 0; i < cfg.maxrooms; i++)
		{
		if (TI()CurrentUser->CanAccessRoom(RoomPosTableToRoom(i), TRUE, FALSE))
			{
			char str[LABELSIZE + 12];

			sprintf(str, getmsg(850), (i < rps) ? getmsg(1451) :
					(i == rps) ? getmsg(1449) : getmsg(1452),
					GetRoomName(RoomPosTableToRoom(i)));

			prtList(str);
			}
		}

	prtList(LIST_END);
	}
