// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: f03c6ef1496e113c6ce7f7d2788b1630fa469627 $
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2026 by The Odamex Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// DESCRIPTION:
//	Main program, simply calls D_DoomMain high level loop.
//
//-----------------------------------------------------------------------------

#include "odamex.h"

#include <stack>
#include <iostream>

#include "win32inc.h"
#ifdef _WIN32
    #include "resource.h"
	#include "mmsystem.h"
#endif

#ifdef UNIX
#include <unistd.h>
#include <signal.h>
#endif

#include <stdlib.h>

#include "i_crash.h"
#include "m_argv.h"
#include "d_main.h"
#include "i_system.h"
#include "c_console.h"
#include "z_zone.h"
#include "i_net.h"
#include "m_fileio.h"
#include "m_consolecommandstream.h"

using namespace std;

void AddCommandString(std::string cmd);

#ifdef _WIN32
extern UINT TimerPeriod;
#endif

// functions to be called at shutdown are stored in this stack
typedef void (STACK_ARGS *term_func_t)(void);
std::stack< std::pair<term_func_t, std::string> > TermFuncs;

void addterm (void (STACK_ARGS *func) (), const char *name)
{
	TermFuncs.push(std::pair<term_func_t, std::string>(func, name));
}

void STACK_ARGS call_terms (void)
{
	while (!TermFuncs.empty())
		TermFuncs.top().first(), TermFuncs.pop();
}

#ifdef _WIN32
static HANDLE hEvent;

int ShutdownNow()
{
	return (WaitForSingleObject(hEvent, 0) == WAIT_OBJECT_0);
}

BOOL WINAPI ConsoleHandlerRoutine(DWORD dwCtrlType)
{
	SetEvent(hEvent);
	return true;
}

class PowerThrottleController
{
	public:
		void Disable(ULONG flag)
		{
			PROCESS_POWER_THROTTLING_STATE throttleCommand{};
			throttleCommand.Version     = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
			throttleCommand.ControlMask = m_toggledFlags | flag;
			throttleCommand.StateMask   = 0;

			if (SetProcessInformation(GetCurrentProcess(),
			                          ProcessPowerThrottling,
			                          & throttleCommand,
			                          sizeof(throttleCommand)))
			{
				m_toggledFlags |= flag;
			}
		}

	protected:
		// Record the successfully-toggled flags so that we can support successive calls
		// to SetProcessInformation with a potential mixture of successes and failures,
		// which is to be expected on Windows versions prior to 11.
		ULONG m_toggledFlags = 0;
};

static void DisablePowerThrottling()
{
	// Sometime during 2022, Windows 11 was updated to throttle processes' access to
	// the high-resolution timer, even if processes explicitly requested it.  Throttling
	// occurs when the process is no longer in the foreground, including when being
	// obscured by other windows.  When this happens, the server spends excessive time
	// sleeping in various system API calls, such as kbhit() in the console code and/or
	// sendto() in the network code.
	//
	// Fortunately we can disable this behavior via the SetProcessInformation API.

	PowerThrottleController throttler;

	throttler.Disable(PROCESS_POWER_THROTTLING_EXECUTION_SPEED);
	throttler.Disable(PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION);
}

int __cdecl main(int argc, char *argv[])
{
	// [AM] Set crash callbacks, so we get something useful from crashes.
#ifdef NDEBUG
	I_SetCrashCallbacks();
#endif

	DisablePowerThrottling();

	try
	{
		// Handle close box, shutdown and logoff events
		if (!(hEvent = CreateEvent(NULL, false, false, NULL)))
			throw CDoomError("Could not create console control event!\n");

		if (!SetConsoleCtrlHandler(ConsoleHandlerRoutine, true))
			throw CDoomError("Could not set console control handler!\n");

		// Disable QuickEdit mode as any text selection will cause all functions
		// that use stdout (printf etc) to block

		DWORD consoleMode = 0;

		if (!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &consoleMode))
			throw CDoomError("GetConsoleMode failed!\n");

		consoleMode &= ~ENABLE_QUICK_EDIT_MODE;
		consoleMode |= ENABLE_EXTENDED_FLAGS;

		if (!SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), consoleMode))
			throw CDoomError("SetConsoleMode failed!\n");

		// Fixes icon not showing in titlebar and alt-tab menu under windows 7
		HANDLE hIcon;

		hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));

		if(hIcon)
		{
			SendMessage(GetConsoleWindow(), WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
			SendMessage(GetConsoleWindow(), WM_SETICON, ICON_BIG, (LPARAM)hIcon);
		}

		// [ML] 2007/9/3: From Eternity (originally chocolate Doom) Thanks SoM & fraggle!
		::Args.SetArgs(argc, argv);

		if (::Args.CheckParm("--version"))
		{
			fmt::print("Odamex {}\n", NiceVersion());
			exit(EXIT_SUCCESS);
		}

		const char* crashdir = ::Args.CheckValue("-crashdir");
		if (crashdir)
		{
			I_SetCrashDir(crashdir);
		}
		else
		{
			std::string writedir = M_GetWriteDir();
			I_SetCrashDir(writedir.c_str());
		}

		M_InitConsoleInputFile(Args.CheckValue("-confile"));

		// Set the timer to be as accurate as possible
		TIMECAPS tc;
		if (timeGetDevCaps (&tc, sizeof(tc) != TIMERR_NOERROR))
			TimerPeriod = 1;    // Assume minimum resolution of 1 ms
		else
			TimerPeriod = tc.wPeriodMin;

		timeBeginPeriod (TimerPeriod);

		// Don't call this on windows!
		//atexit (call_terms);

		Z_Init();

		atterm (I_Quit);
		atterm (DObject::StaticShutdown);

		D_DoomMain();
	}
	catch (CDoomError& error)
	{
		if (LOG.is_open())
		{
			LOG << "=== ERROR: " << error.GetMsg() << " ===\n\n";
		}

		fmt::print(stderr, "=== ERROR: {} ===\n\n", error.GetMsg());

		call_terms();
		exit(EXIT_FAILURE);
	}
	catch (...)
	{
		call_terms();
		throw;
	}
	return 0;
}
#else

//
// daemon_init
//
void daemon_init(void)
{
    int     pid;
    FILE   *fpid;
    string  pidfile;

    PrintFmt(PRINT_HIGH, "Launched into the background\n");

    if ((pid = fork()) != 0)
    {
    	call_terms();
    	exit(EXIT_SUCCESS);
    }

	const char *forkargs = Args.CheckValue("-fork");
	if (forkargs)
		pidfile = string(forkargs);

    if(!pidfile.size() || pidfile[0] == '-')
    	pidfile = "doomsv.pid";

    pid = getpid();
    fpid = fopen(pidfile.c_str(), "w");
    fmt::print(fpid, "{}\n", pid);
    fclose(fpid);
}

int main (int argc, char **argv)
{
	// [AM] Set crash callbacks, so we get something useful from crashes.
#ifdef NDEBUG
	I_SetCrashCallbacks();
#endif

    try
    {
		if(!getuid() || !geteuid())
			I_FatalError("root user detected, quitting odamex immediately");

		int r_euid = seteuid (getuid ());

		if(r_euid < 0)
			perror(NULL);

		::Args.SetArgs(argc, argv);

		if (::Args.CheckParm("--version"))
		{
			fmt::print("Odamex {}\n", NiceVersion());
			exit(EXIT_SUCCESS);
		}

		const char* crashdir = ::Args.CheckValue("-crashdir");
		if (crashdir)
		{
			I_SetCrashDir(crashdir);
		}
		else
		{
			std::string writedir = M_GetWriteDir();
			I_SetCrashDir(writedir.c_str());
		}

		M_InitConsoleInputFile(Args.CheckValue("-confile"));

		/*
		  killough 1/98:

		  This fixes some problems with exit handling
		  during abnormal situations.

		  The old code called I_Quit() to end program,
		  while now I_Quit() is installed as an exit
		  handler and exit() is called to exit, either
		  normally or abnormally. Seg faults are caught
		  and the error handler is used, to prevent
		  being left in graphics mode or having very
		  loud SFX noise because the sound card is
		  left in an unstable state.
		*/

        // Don't use this on other platforms either
		//atexit (call_terms);
		Z_Init();					// 1/18/98 killough: start up memory stuff first

		atterm (I_Quit);
		atterm (DObject::StaticShutdown);

		// [AM] There used to be a signal handler here that attempted to
		//      shut the server off gracefully.  I'm not sure masking the
		//      signal is a good idea, and it stomped over the crashlog handler
		//      I set earlier.

		D_DoomMain();
	}
	catch (CDoomError& error)
	{
		if (LOG.is_open())
		{
			LOG << "=== ERROR: " << error.GetMsg() << " ===\n\n";
		}

		fmt::print(stderr, "=== ERROR: {} ===\n\n", error.GetMsg());

		call_terms();
		exit(EXIT_FAILURE);
	}
	catch (...)
	{
		call_terms();
		throw;
	}
	return 0;
}

#endif

VERSION_CONTROL (i_main_cpp, "$Id: f03c6ef1496e113c6ce7f7d2788b1630fa469627 $")
