// --------------------------------------------------------------------------
// Citadel: Init.CPP
//
// Citadel initialization code.

#include "ctdl.h"
#pragma hdrstop

#include "config.h"
#include "net.h"
#include "music.h"
#include "libovl.h"
#include "meminit.h"
#include "account.h"
#include "infofile.h"
#include "roompriv.h" // this has got to go!
#include "group.h"
#include "hall.h"
#include "log.h"
#include "scrlback.h"
#include "timer.h"
#include "tallybuf.h"
#include "mouse.h"
#include "ser.h"
#include "cwindows.h"
#include "events.h"
#include "miscovl.h"
#include "term.h"


// --------------------------------------------------------------------------
// Contents
//
// checkfiles() 	makes sure there are enough FILES in CONFIG.SYS.
// initCitadel()	Load up data files and open everything.

extern ConsoleLockC ConsoleLock;
extern Bool TimeoutChecking;


// --------------------------------------------------------------------------
// checkfiles(): Makes sure there are enough FILES in config.sys.

int FilesRequired = 13;

static void checkfiles(void)
	{
	const int i = fileHandlesLeft();

	if (i < FilesRequired)
		{
		printf(bn);
		printf(getcfgmsg(117));
		printf(bn);
		printf(getcfgmsg(118), FilesRequired - i);
		printf(bn);
		DeinitializeTimer();
		critical(FALSE);
		exit(200);
		}
	}

int fileHandlesLeft(void)
	{
	FILE *files[FOPEN_MAX];
	int i, count;

	for (i = 0; i < FOPEN_MAX; i++)
		{
		if ((files[i] = fopen(fullExePath, FO_RB)) == NULL)
			{
			count = i;
			break;
			}
		}

	for (i = 0; i < count; i++)
		{
		fclose(files[i]);
		}

	return (count);
	}

// by David Gibbs
// FidoNet: 1:115/439.0
// Internet: David.Gibbs@f439.n115.z1.fidonet.org
// Modified, of course.
#define is_DOS	0x01
#define is_OS2	0x02
#define is_DV	0x04
#define is_WINS 0x08
#define is_WIN3 0x10

static void get_os(void)
	{
	union REGS t_regs;

	OSMajor = 0;
	OSMinor = 0;
	int osType = 0;

	// test for DOS or OS/2
	if (_osmajor < 10)
		{
		OSMajor = _osmajor;
		OSMinor = _osminor;

		osType |= is_DOS;
		}
	else
		{
		OSMajor = _osmajor / 10;
		OSMinor = _osminor;

		osType |= is_OS2;
		}

	// test for Windows
	t_regs.x.ax = 0x4680;
	int86(0x2F, &t_regs, &t_regs);

	if (t_regs.x.ax == 0x0000)
		{
		OSMajor = 3;
		OSMinor = 0;

		osType |= is_WINS;
		}
	else
		{
		t_regs.x.ax = 0x1600;
		int86(0x2F, &t_regs, &t_regs);

		switch (t_regs.h.al)
			{
			case 0x00:
			case 0x80:
			case 0x01:
			case 0xFF:
				{
				break;
				}

			default:
				{
				OSMajor = t_regs.h.al;
				OSMinor = t_regs.h.ah;
				osType |= is_WIN3;
				break;
				}
			}
		}

	// Test for DESQview
	t_regs.x.cx = 0x4445;	// load incorrect date
	t_regs.x.dx = 0x5351;
	t_regs.x.ax = 0x2B01;	// DV set up call

	intdos(&t_regs, &t_regs);
	if (t_regs.h.al != 0xFF)
		{
		OSMajor = t_regs.h.bh;
		OSMinor = t_regs.h.bl;

		osType |= is_DV;
		}

	if (osType & is_DOS)
		{
		OSType = OS_DOS;
		}

	if (osType & is_WINS)
		{
		OSType = OS_WINS;
		}

	if (osType & is_WIN3)
		{
		OSType = OS_WIN3;
		}

	if (osType & is_DV)
		{
		OSType = OS_DV;
		}

	if (osType & is_OS2)
		{
		OSType = OS_OS2;
		}
	}


// --------------------------------------------------------------------------
// initCitadel(): Load up data files and open everything.

static Bool initCitadel(void)
	{
	if (!read_cfg_messages())
		{
		printf(getmsg(188), getmsg(748));
		return (FALSE);
		}

	checkfiles();
	initExtDrivers();
	get_os();

	static char prompt[92];
	static char citadel[92];
	char *envprompt;
	char *citprompt;
	char read_it;

	cfg.battr = 0xff;

	setscreen();
	logo(TRUE);

	init_internal_sound();
	initMouseHandler();
	hideCounter = 1;

	if (time(NULL) < 700000000L)
		{
#ifdef WINCIT
		MessageBox(NULL, getcfgmsg(119), NULL,
				MB_ICONSTOP | MB_OK);
#else
		doccr();
		doccr();
		cPrintf(getcfgmsg(119));
		doccr();
#endif
		dump_cfg_messages();
		return (FALSE);
		}

	TI()whichIO = CONSOLE;
	TI()UserControl.SetOutFlag(OUTOK);
	TI()OC.Echo = BOTH;
	setio();

	envprompt = getenv(getcfgmsg(120));
	citprompt = getenv(getcfgmsg(121));
	if (citprompt)
		{
		sprintf(prompt, getcfgmsg(122), citprompt);
		}
	else if (envprompt)
		{
		sprintf(prompt, getcfgmsg(123), envprompt);
		}
	else
		{
		strcpy(prompt, getcfgmsg(124));
		}
	putenv(prompt);

	sprintf(citadel, getcfgmsg(125), programName, version);
	putenv(citadel);

	cfg.version = cfgver;	// initialize it for comparison later

#ifdef FLOPPY
	doccr(); doccr(); doccr(); doccr(); doccr(); doccr();
	cPrintf("Insert data disk and hit almost any key.");

	while ((*statcon)()) (*getcon)();
	(*getcon)();
	while ((*statcon)()) (*getcon)();

	doccr(); doccr();
#endif

	VerifyHeap();

	if (!(read_it = readTables()) || (cfg.version != cfgver))
		{
#ifndef WINCIT
		if ((reconfig || (cfg.version != cfgver)) || batchmode)
			{
#endif
			cfg.attr = 7;

#ifndef WINCIT
			pause(200);
			cls(SCROLL_SAVE);
#endif

			if (read_it && (cfg.version != cfgver))
				{
				freeTables();
				}

#ifndef WINCIT
			cCPrintf(getcfgmsg(126));
			doccr();
#endif

			if (!configcit())
				{
#ifdef WINCIT
				MessageBox(NULL, getcfgmsg(127), NULL,
						MB_ICONSTOP | MB_OK);
#else
				doccr();
				doccr();
				cPrintf(getcfgmsg(127));
				doccr();
#endif
				dump_cfg_messages();
				return (FALSE);
				}

			setdefaultTerm(2);
			TI()CurrentUser->SetWidth(80);
			Cron.ReadCronCit();
#ifndef WINCIT
			}
		else
			{
			doccr();

			discardable *d;

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

				for (i = 0; ((char **) d->next->aux)[i][0] != '#'; i++)
					{
					cPrintf(pcts, ((char **) d->next->aux)[i]);
					doccr();
					}

				doccr();

				discardData(d);
				}
			else
				{
				cPrintf(getmsg(188), getmsg(748));
				}

			DeinitializeTimer();
			critical(FALSE);
			exit(1);
			}
#endif
		}
	else
		{
#ifndef WINCIT
		if (!CreateScrollBackBuffer())
			{
			cPrintf(getcfgmsg(60));
			doccr();
			}
#endif
		if (readconfigcit)	// forced to read in config.cit
			{
			if (!readconfig(1))
				{
#ifdef WINCIT
				MessageBox(NULL, getcfgmsg(129), NULL,
						MB_ICONSTOP | MB_OK);
#else
				doccr();
				doccr();
				cPrintf(getcfgmsg(129));
				doccr();
#endif
				dump_cfg_messages();
				return (FALSE);
				}
			}
		}

	VerifyHeap();

	destroyBorders();
	makeBorders();
	readBordersDat();

	if (cmd_nobells)
		{
		cfg.noBells = 2;
		}

	if (cmd_nochat)
		{
		cfg.noChat = TRUE;
		}

	if (cmd_mdata != CERROR)
		{
		cfg.mdata = cmd_mdata;
		}

	if (*cfg.f6pass)
		{
		if (SameString(cfg.f6pass, getmsg(779)))
			{
			ConsoleLock.LockF6();
			}
		else
			{
			ConsoleLock.Lock();
			}
		}

#if !defined(FLOPPY) && !defined(WINCIT)
	if (cfg.ovrEms)
		{
		if (_OvrInitEms(0, 0, 0))
			{
			cPrintf(getcfgmsg(130));
			doccr();
			pause(200);
			}
		}

	if (cfg.ovrExt)
		{
		if (_OvrInitExt(0, 0))
			{
			cPrintf(getcfgmsg(131));
			doccr();
			pause(200);
			}
		}
#endif

	TI()SerialPort.Init();
	setscreen();
	logo(TRUE);
	StatusLine.Update();

	if (cfg.msgpath[(strlen(cfg.msgpath) - 1)] == '\\')
		{
		cfg.msgpath[(strlen(cfg.msgpath) - 1)] = '\0';
		}

	// move to home path
	changedir(cfg.homepath);
	char FileName[128];

	ReIndexFileInfo();	// keep fileinfo.dat nice and pretty

	// open message file
	sprintf(FileName, sbs, cfg.msgpath, msgDat);
	msgfl = bufferedOpenExist(FileName);

	// Then room file
	sprintf(FileName, sbs, cfg.homepath, roomDat);
	openFile(FileName, &roomfl);

	citOpen(cfg.trapfile, CO_A, &trapfl);
	citWinCloseTmp(&trapfl);

	initMenus();

	trap(getcfgmsg(132), T_SYSOP);

	if (!GroupData->Load())
		{
		return (FALSE);
		}

	if (!HallData->Load())
		{
		return (FALSE);
		}

	getRoomPos();

	if (cfg.accounting)
		{
		ReadGrpdataCit();
		}

	ReadExternalCit();
	ReadProtocolCit();
	ReadMdmresltCit();
	ReadMCICit(FALSE);

	getRoom(LOBBY, roomBuf);	// load Lobby>
	TI()thisRoom = LOBBY;
	checkdir();

	if (!slv_door)
		{
		CITWINDOW *w = CitWindowsMsg(NULL, getmsg(1611));

		Initport();
		Initport();

		if (w)
			{
			destroyCitWindow(w, FALSE);
			}
		}
	else
		{
		SerialPort.Enable();
		}

	TI()whichIO = MODEM;
	setio();

	// record when we put system up
	time(&uptimestamp);

	setdefaultconfig(FALSE);
	roomtalley();
	logo(FALSE);

	VerifyHeap();

	dump_cfg_messages();
	return (TRUE);
	}


// --------------------------------------------------------------------------
// critical(): Critical error handling.

void critical(int turnOn)
	{
#ifndef WINCIT
	static void interrupt (*dosHandler)(...);

	if (turnOn && !dosHandler)
		{
		// turn it on
		dosHandler = getvect(0x24);
		setvect(0x24, critical_error);
		}
	else if (!turnOn && dosHandler)
		{
		// turn if off
		setvect(0x24, dosHandler);
		dosHandler = NULL;
		}
#endif
	}


// --------------------------------------------------------------------------
// startUp(): Very low-level initialization.

// Borland C++ 4.00 uses exceptions for handling out of memory, not returning
// NULL like we want. This tells it to return NULL.
#if __BORLANDC__ == 1106 || __BORLANDC__ == 1120
	#include <new.h>
#endif

void startUp(int argc, const char *argv[])
	{
#if __BORLANDC__ == 1106 || __BORLANDC__ == 1120
	set_new_handler(0);
#endif

#ifndef WINCIT
	strcpy(fullExePath, argv[0]);

	fseek(stdin, 0, SEEK_CUR);	// Borland says to fseek() before setbuf()
	setbuf(stdin, NULL);
#endif

#ifdef MULTI
	if (!initTaskInfo(&ti))
#else
	if (!initFakeTaskInfo())
#endif
		{
		printf("Could not create initial task information structure.\n");
		exit(200);
		}

	tzset();

	critical(TRUE);

#ifndef WINCIT
	if (checkDataVer() != NumericVer)
		{
		printf("Missing or wrong CTDL.DAT.\n");
		critical(FALSE);
		exit(200);
		}

	if (!read_messages())
		{
		printf("Could not read messages from CTDL.DAT\n");
		critical(FALSE);
		exit(200);
		}

	InitializeTimer();
#else
	if (checkDataVer() != datver)
		{
		MessageBox(NULL, "Missing or wrong CTDL.DAT.", NULL,
				MB_ICONSTOP | MB_OK);
		return (FALSE);
		}

	if (!read_messages())
		{
		MessageBox(NULL, "Could not read messages from CTDL.DAT.", NULL,
				MB_ICONSTOP | MB_OK);
		return (FALSE);
		}

	MSG msg;
	WNDCLASS wndclass;

	// define and register the main window class
	wndclass.style = 0; // CS_HREDRAW | CS_VREDRAW
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(hInstance, "CITADEL");
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = "Citadel";
	wndclass.lpszClassName = "Citadel";
	RegisterClass(&wndclass);

	// define and register the logo window class
	wndclass.lpszMenuName = NULL;
	wndclass.lpfnWndProc = logoWndProc;
	wndclass.lpszClassName = "Citadel Logo";
	RegisterClass(&wndclass);

	// define and register the error window class
	wndclass.style = CS_VREDRAW;
	wndclass.lpfnWndProc = errorWndProc;
	wndclass.lpszClassName = "Citadel Error";
	RegisterClass(&wndclass);

	// define and register the trap window class
	wndclass.lpfnWndProc = trapWndProc;
	wndclass.lpszClassName = "Citadel Trap";
	RegisterClass(&wndclass);

	// define and register the trap window class
	wndclass.lpfnWndProc = msgWndProc;
	wndclass.lpszClassName = "Citadel Message";
	RegisterClass(&wndclass);
#endif

	// Start using BIOS
	charattr = bioschar;
	stringattr = biosstring;

	// initialize all drivers to internal functions

	// com
	initrs			= InitRS;
	deinitrs		= DeInitRS;
	ringstatrs		= RingStatRS;
	carrstatrs		= CarrStatRS;
	statrs			= StatRS;
	flushrs 		= FlushRS;
	getrs			= GetRS;
	putrs			= PutRS;
	dtrrs			= DtrRS;
	flushoutrs		= nullFunc;

	// kbd
	initkbd 		= nullFunc;
	deinitkbd		= nullFunc;
	statcon 		= StatCON;
	getcon			= GetCON;
	sp_press		= special_pressed;

	// snd
	init_sound		= (int (cdecl *)(void)) nullFunc;
	close_sound 	= (int (cdecl *)(void)) nullFunc;
	get_version 	= (int (cdecl *)(void)) nullFunc;
	query_drivers	= (int (cdecl *)(void)) nullFunc;
	query_status	= (int (cdecl *)(void)) nullFunc;
	start_snd_src	= (int (cdecl *)(int, const void *)) nullFunc;
	play_sound		= (int (cdecl *)(int)) nullFunc;
	stop_sound		= (int (cdecl *)(int)) nullFunc;
	pause_sound 	= (int (cdecl *)(int)) nullFunc;
	resume_sound	= (int (cdecl *)(int)) nullFunc;
	read_snd_stat	= (int (cdecl *)(int)) nullFunc;
	set_midi_map	= (int (cdecl *)(int)) nullFunc;
	get_src_vol 	= (int (cdecl *)(int)) nullFunc;
	set_src_vol 	= (int (cdecl *)(int, int)) nullFunc;
	set_fade_pan	= (int (cdecl *)(void *)) nullFunc;
	strt_fade_pan	= (int (cdecl *)(void)) nullFunc;
	stop_fade_pan	= (int (cdecl *)(int)) nullFunc;
	pse_fade_pan	= (int (cdecl *)(void)) nullFunc;
	res_fade_pan	= (int (cdecl *)(void)) nullFunc;
	read_fade_pan	= (int (cdecl *)(int)) nullFunc;
	get_pan_pos 	= (int (cdecl *)(int)) nullFunc;
	set_pan_pos 	= (int (cdecl *)(int, int)) nullFunc;
	say_ascii		= (int (cdecl *)(const char *, int)) nullFunc;

	parseArgs(argc, argv);

	if (!cfg.bios)
		{
		charattr = directchar;
		stringattr = directstring;
		}

	if (!cmdLine[1])
		{
		uchar *ptr = (uchar *) MK_FP(_psp, 128);

		if (ptr[0])
			{
			memcpy(cmdLine, ptr + 1, max(128, ptr[0]));

			cmdLine[ptr[0]] = 0;
			}
		}

	TI()whichIO = CONSOLE;

	if (!initCitadel())
		{
		DeinitializeTimer();
		critical(FALSE);
		exit(200);
		}

	ScreenSaver.Update();

	if (BoardNameHash && hash(cfg.nodeTitle) != BoardNameHash)
		{
		crashout(getmsg(751));
		}

	// Set system to a known state
	TI()OC.Echo = BOTH;
	TI()UserControl.SetOutFlag(IMPERVIOUS);
	TI()modStat = FALSE;
	TI()whichIO = CONSOLE;

	setio();

#ifdef WINCIT
	hwndMain = CreateWindow("Citadel", programName, WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL,
			NULL, hInstance, NULL);

	ShowWindow(hwndMain, nCmdShow);
	UpdateWindow(hwndMain);

	hAccel = LoadAccelerators(hInstance, "Citadel");
#endif

	if (!(login_pw || login_user || (slv_door && cfg.forcelogin)))
		{
		TI()SerialPort.Flush();
		greeting();
		}

	Cron.ResetTimer();

	if (slv_net)
		{
		doccr();

		cPrintf(getmsg(29), slv_node);

		if (net_callout(slv_node))
			{
			did_net(slv_node);
			}

		TI()ExitToMsdos = TRUE;
		}

	if (slv_door) // set according to carrier
		{
		// set baud rate even if carrier not present
		if (slv_baud != PS_ERROR)
			{
			TI()SerialPort.SetSpeed(slv_baud);
			}
		else
			{
			TI()SerialPort.SetSpeed(cfg.initbaud);
			}

		for (ModemSpeedE i = MS_300; i < MS_NUM; i = (ModemSpeedE) (i +1))
			{
			if (connectbauds[i] == bauds[TI()SerialPort.GetSpeed()])
				{
				TI()ModemSpeed = i;
				break;
				}
			}

		if (SerialPort.HaveCarrier())
			{
			carrdetect();

			TI()whichIO = MODEM;
			setio();
			}
		else
			{
			TI()whichIO = CONSOLE;
			setio();
			}
		}

	setdefaultTerm(2); // Ansi-CLR

	StatusLine.Toggle();		// Turns it on (starts life off).

	ScreenSaver.SetMayTurnOn(TRUE);

	time(&TI()LastActiveTime);
	TimeoutChecking = TRUE;

	if (*cmd_script)
		{
		runScript(cmd_script);
		}

	doEvent(EVT_STARTUP);
	}
