// --------------------------------------------------------------------------
// Citadel: Output.CPP
//
// Output routines.

#include "ctdl.h"
#pragma hdrstop

#define OUTMAIN
#include "outfilt.h"
#include "msg.h"
#include "log.h"
#include "account.h"
#include "miscovl.h"
#include "term.h"

// --------------------------------------------------------------------------
// Contents
//
// getWord()	Gets the next word from the buffer and returns it
// mFormat()	Outputs a string to modem and console with wordwrap
// putWord()	Writes one word to modem and console, with wordwrap
// doBS()		does a backspace to modem & console
// doCR()		does a return to both modem & console
// doTAB()		prints a tab to modem & console according to flag
// oChar()		is the top-level user-output function (one byte)
// updcrtpos()	updates TI()OC.CrtColumn according to character
// mPrintf()	sends formatted output to modem & console


// --------------------------------------------------------------------------
// mFormat(): Outputs a string to modem and console with wordwrap.
//

void mFormat(const char *string)
	{
	uchar wordBuf[MAXWORD + 8];
	int i;

	if (CheckIdleUserTimeout(TRUE))
		{
		TI()sleepkey = FALSE;
		time(&TI()LastActiveTime);

		UserHasTimedOut();
		return;
		}
	else
		{
		TI()sleepkey = FALSE;
		}

	if (!outSpeech(TRUE, string))
		{
		Bool saveGoto = FALSE;

		for (i = 0; string[i] && (TI()UserControl.CanOutput() || TI()UserControl.GetOutFlag() == OUTPARAGRAPH);)
			{
			letWindowsMultitask();

			if (TI()UserControl.CheckInput(FALSE))
				{
				ResetOutputControl();
				break;
				}

			i = getWord(wordBuf, (uchar *) string, i, MAXWORD);

			if (*wordBuf == CTRL_A)
				{
				termCap((char *) wordBuf + 1);
				}
			else if (*wordBuf == CTRL_B)
				{
				localTermCap((char *) wordBuf + 1);
				}
			else
				{
				putWord(wordBuf);
				}

			if (TI()OC.MCI_goto)
				{
				if (saveGoto != TI()OC.MCI_goto)
					{
					i = 0;
					}
				}

			saveGoto = TI()OC.MCI_goto;
			}
		}

	ansi(14);
	TI()OC.MCI_goto = FALSE;
	}


// --------------------------------------------------------------------------
// doBS(): Does a backspace to modem & console.

void doBS(void)
	{
	oChar('\b');
	oChar(' ');
	oChar('\b');
	}


// --------------------------------------------------------------------------
// doCR(): Does a return to both modem & console.

void doCR(void)
	{
	if (!TI()OC.Formatting)
		{
		return;
		}

	if (!(TI()UserControl.CanOutput()))
		{
		return;
		}

	TI()OC.CrtColumn = 1;

#ifndef WINCIT
	if (journalfl)
		{
		fprintf(journalfl, bn);
		TI()prevChar = ' ';
		TI()UserControl.CheckInput(FALSE);
		return;
		}
#endif

	domcr();
	doccr();

#ifndef WINCIT
	if (TI()OC.Printing)
		{
		fprintf(TI()OC.PrintFile, bn);
		}
#endif

	TI()prevChar = ' ';

	// pause on full screen
	if (TI()CurrentUser->GetLinesPerScreen() &&
			(TI()UserControl.GetOutFlag() == OUTOK ||
			TI()UserControl.GetOutFlag() == NOSTOP))
		{
		if (++TI()numLines >= TI()CurrentUser->GetLinesPerScreen() &&
				(!TI()CurrentUser->IsPauseBetweenMessages() || !TI()OC.justdidpause))
			{
			TI()numLines = 0;
			TI()UserControl.CheckInput(TRUE);

			TI()OC.justdidpause = TRUE;
			TI()numLines = 0;

			// set it again cause it gets screwed up by the <more>
			TI()prevChar = ' ';
			TI()OC.CrtColumn = 1;
			}
		else
			{
			TI()OC.justdidpause = FALSE;
			}
		}
	else
		{
		TI()numLines = 0;
		}
	}

void doBS(int Count)
	{
	while (Count--)
		{
		doBS();
		}
	}

void doCR(int Count)
	{
	while (Count--)
		{
		doCR();
		}
	}


// --------------------------------------------------------------------------
// doTAB(): Prints a tab to modem & console according to flag.

static void doTAB(void)
	{
	int column, column2;

	column = TI()OC.CrtColumn;
	column2 = TI()OC.CrtColumn;

	if (journalfl)
		{
		fputc('\t', journalfl);
		}
	else
		{
		if (TI()OC.Printing)
			{
			fputc('\t', TI()OC.PrintFile);
			}

		do
			{
			outCon(' ');
			} while ((++column % 8) != 1);

		if (TI()OC.Modem)
			{
			if (TI()CurrentUser->IsTabs())
				{
				TI()SerialPort.Output('\t');
				}
			else
				{
				do
					{
					TI()SerialPort.Output(' ');
					} while ((++column2 % 8) != 1);
				}
			}
		}

	updcrtpos('\t');
	}


// --------------------------------------------------------------------------
// echocharacter(): Echos bbs input according to global flags.

void echocharacter(const char c)
	{
	if (TI()OC.Echo == NEITHER)
		{
		return;
		}

	if (c == '\b')
		{
		doBS();
		}
	else if (c == '\n')
		{
		doCR();
		}
	else
		{
		oChar(c);
		}
	}


// --------------------------------------------------------------------------
// oChar(): the top-level user-output function (one byte)
// sends to modem port and console both
// does conversion to upper-case etc as necessary

void oChar(register char c)
	{
	int i;
	Bool wasANSI = TI()OC.inANSI;

	TI()prevChar = c;			// for end-of-paragraph code

	if (c == 1)
		{
		c = 0;
		}

	if (c == '\t')
		{
		doTAB();
		return;
		}

	// You don't want to know
	if (TI()OC.Psycho && cfg.Psycho)
		{
		if (TI()UpDoWn)
			{
			c = toupper(c);
			}
		else
			{
			c = tolower(c);
			}

		TI()UpDoWn = !TI()UpDoWn;
		}

	if (TI()CurrentUser->IsUpperOnly())
		{
		c = toupper(c);
		}

	if (c == '\n')
		{
		if (TI()OC.Formatting)
			{
			c = ' ';    // doCR() handles real newlines
			}
		else
			{
			TI()OC.Formatting = TRUE;
			doCR();
			TI()OC.Formatting = FALSE;
			TI()OC.CrtColumn = 1;
			return;
			}
		}

	if (!TI()term.ibmAnsi)
		{
		c = filt_out[(uchar)c];
		}

	if (!c)
		{
		return;
		}

	if (journalfl)
		{
		fputc(c, journalfl);
		}
	else
		{
		// show on console
		if (TI()OC.Console)
			{
			outCon(c);
			}

		// show on printer
		if (TI()OC.Printing)
			{
			fputc(c, TI()OC.PrintFile);
			}
		}

	if (!journalfl)
		{
		// send out the modem
		if (TI()OC.Modem && SerialPort.HaveCarrier() && !(wasANSI || TI()OC.inANSI))
			{
			if (TI()CurrentUser->IsOut300())
				{
				for (i = 1; i < (int) (connectbauds[TI()ModemSpeed] / 300); i++)
					{
					assert(putrs);
					(*putrs)(0);
					//TI()SerialPort.Output(0);
					//TI()transmitted--;	// don't count fake chars
					}
				}

			TI()SerialPort.Output(c);
			}
		}

	updcrtpos(c);
	}


// --------------------------------------------------------------------------
// updcrtpos(): Updates TI()OC.CrtColumn according to character.

void updcrtpos(const char c)
	{
	if (c == '\b')
		{
		TI()OC.CrtColumn--;
		}
	else if (c == '\t')
		{
		while ((++TI()OC.CrtColumn % 8) != 1);
		}
	else if ((c == '\n') || (c == '\r'))
		{
		TI()OC.CrtColumn = 1;
		}
	else
		{
		if (c != BELL)	// beep!!
			{
			TI()OC.CrtColumn++;
			}
		}
	}


// --------------------------------------------------------------------------
// mPrintf(): Sends formatted output to modem & console.

void cdecl mPrintf(const char *fmt, ...)
	{
	va_list ap;

	va_start(ap, fmt);
	vsprintf(TI()OC.prtf_buff, fmt, ap);
	va_end(ap);

	mFormat(TI()OC.prtf_buff);
	ResetOutputControl();
	}

void cdecl mPrintfCR(const char *fmt, ...)
	{
	va_list ap;

	va_start(ap, fmt);
	vsprintf(TI()OC.prtf_buff, fmt, ap);
	va_end(ap);

	mFormat(TI()OC.prtf_buff);
	doCR();
	ResetOutputControl();
	}

void cdecl CRmPrintf(const char *fmt, ...)
	{
	va_list ap;

	va_start(ap, fmt);
	vsprintf(TI()OC.prtf_buff, fmt, ap);
	va_end(ap);

	doCR();
	mFormat(TI()OC.prtf_buff);
	ResetOutputControl();
	}

void cdecl CRmPrintfCR(const char *fmt, ...)
	{
	va_list ap;

	va_start(ap, fmt);
	vsprintf(TI()OC.prtf_buff, fmt, ap);
	va_end(ap);

	doCR();
	mFormat(TI()OC.prtf_buff);
	doCR();
	ResetOutputControl();
	}

// --------------------------------------------------------------------------
// getWord(): Gets the next word from the buffer and returns it.

int getWord(uchar *dest, register const uchar *source, int offset, int lim)
	{
	register int i = offset;

	if (source[i] == '\r' || source[i] == '\n')
		{
		i++;
		}
	else if (isspace(source[i]))
		{
		// step over spaces
		for (; isspace(source[i]) &&
				!(source[i] == '\r' || source[i] == '\n') &&
				((i - offset) < lim); i++);
		}
	else if (source[i] == CTRL_A || source[i] == CTRL_B)
		{
		if (source[i + 1])
			{
			i += 2;

			if (toupper(source[i - 1]) == 'X')
				{
				while ((i - offset < lim) && source[i] > CTRL_B &&
						!isspace(source[i]) && toupper(source[i]) != 'X' &&
						source[i] != '\r' && source[i] != '\n')
					{
					i++;
					}

				if (toupper(source[i]) == 'X')
					{
					i++;
					}
				}
			}
		else
			{
			i++;
			}
		}
	else
		{
		// step over word
		for (; !(isspace(source[i])) && ((i - offset) < lim) && source[i] &&
				source[i] != CTRL_A && source[i] != CTRL_B; i++);
		}

	strncpy((char *) dest, (char *) (source + offset), i - offset);

	dest[i - offset] = '\0';

	return (i);
	}


// --------------------------------------------------------------------------
// putWord(): Writes one word to modem and console, with wordwrap.

void putWord(const uchar *st)
	{
	if (!TI()OC.MCI_goto)
		{
		const register char *s;
		register int newColumn;
		char buf[MAXWORD];

		if (!noReplace && replaceList && *st > 32)
			{
			for (pairedStrings *thisReplace = replaceList; thisReplace;
					thisReplace = (pairedStrings *) getNextLL(thisReplace))
				{
				if (SameString(thisReplace->string1,
						strip_punct((char *) st)))
					{
					break;
					}
				}

			if (thisReplace)
				{
				noReplace = TRUE;

				int i;

				for (i = 0, s = (const char *) st; ispunct(*s); s++)
					{
					buf[i++] = *s;
					}

				buf[i] = 0;

				putWord((uchar *) buf);

				for (i = 0, s = (strchr((const char *) st, 0) - 1);
						s > (const char *) st && ispunct(*s); s--)
					{
					buf[i++] = *s;
					}

				buf[i] = 0;
				strrev(buf);

				mFormat((char *) (thisReplace->string2));

				putWord((uchar *) buf);

				noReplace = FALSE;
				return;
				}
			}

		CopyStringToBuffer(buf, (const char *) st, MAXWORD - 1);

		if (TI()OC.Psycho)
			{
			if (cfg.Backout)
				{
				strrev(buf);
				}

			if (cfg.Mmmm)
				{
				for (int i = 0; buf[i]; i++)
					{
					if (isupper(buf[i]))
						{
						buf[i] = 'M';
						}
					else if (islower(buf[i]))
						{
						buf[i] = 'm';
						}
					}
				}
			}

		const char *p = buf;

		if (!TI()OC.Formatting)
			{
			for (; *p; p++)
				{
				oChar(*p);
				}

			return;
			}


// --------------------------------------------------------------------------
// ISBLANK is a macro used by getword and putword.

#define TAB '\t'
#define ISBLANK(cc) ((cc) == ' ' || (cc) == TAB)

// Experimental nifty new word wrap

		if (TI()prevChar == '\n' && ISBLANK(*p))
			{
			// end of paragraph
			if (TI()UserControl.GetOutFlag() == OUTPARAGRAPH)
				{
				TI()UserControl.SetOutFlag(OUTOK);
				}

			doCR();
			}

		Bool StartANSI = FALSE, InMusic = FALSE, InANSI = FALSE;
		for (newColumn = TI()OC.CrtColumn, s = p; *s; s++)
			{
			if (StartANSI)
				{
				StartANSI = FALSE;

				if (*s == '[')
					{
					InANSI = TRUE;
					InMusic = FALSE;
					}
				}
			else if (InANSI)
				{
				if (*s == 'M')  // Music
					{
					InMusic = TRUE;
					}
				else
					{
					if ((!InMusic && isalpha(*s)) || (InMusic && *s == 14))
						{
						InANSI = FALSE;
						InMusic = FALSE;
						}
					}
				}
			else
				{
				if (*s == ESC)
					{
					StartANSI = TRUE;
					}
				else if (*s == '\b')
					{
					newColumn--;
					}
				else if (*s == BELL)
					{
					// beeps don't do any column stuff at all
					}
				else if (*s != TAB)
					{
					++newColumn;
					}
				else
					{
					while (newColumn++ % 8);
					}
				}
			}

		if (newColumn > TI()CurrentUser->GetWidth())
			{
			// force a carriage return
			doCR();

			if (TI()OC.HangingIndent < TI()CurrentUser->GetWidth())
				{
				for (int i = TI()OC.HangingIndent; i; i--)
					{
					oChar(' ');
					}
				}

			if (isspace(*p))
				{
				return;
				}
			}

		for (; *p; p++)
			{
			// worry about words longer than a line
			if (TI()OC.CrtColumn >= TI()CurrentUser->GetWidth())
				{
				doCR();

				if (TI()OC.HangingIndent < TI()CurrentUser->GetWidth())
					{
					for (int i = TI()OC.HangingIndent; i; i--)
						{
						oChar(' ');
						}
					}
				}

			oChar(*p);
			}
		}
	}


// --------------------------------------------------------------------------
// mPrintf(): Sends formatted output to modem & console.

void cdecl DebugOut(const char *fmt, ...)
	{
	if (debug)
		{
		char Wow[256];
		va_list ap;

		va_start(ap, fmt);
		vsprintf(Wow, fmt, ap);
		va_end(ap);

		cPrintf(getdbmsg(32), Wow);
		}
	}

// Remove ^A5, ^BM, etc.
void ResetOutputControl(void)
	{
	TI()OC.Formatting = TRUE;
	TI()OC.UseMCI = cfg.mci;
	TI()OC.HangingIndent = 0;
	}
