
/*
 * Copyright 2011-2022 Cuberite Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules

#include "main.h"
#include "BuildInfo.h"
#include "Logger.h"
#include "MemorySettingsRepository.h"
#include "Root.h"
#include "tclap/CmdLine.h"

#include "OSSupport/ConsoleSignalHandler.h"
#include "OSSupport/NetworkSingleton.h"
#include "OSSupport/MiniDumpWriter.h"
#include "OSSupport/SleepResolutionBooster.h"
#include "OSSupport/StartAsService.h"





bool g_ShouldLogCommIn;
bool g_ShouldLogCommOut;
bool g_RunAsService;





////////////////////////////////////////////////////////////////////////////////
// ParseArguments - Read the startup arguments and store into a settings object

static void ParseArguments(int argc, char ** argv, cMemorySettingsRepository & a_Settings)
{
	// Parse the comand line args:
	TCLAP::CmdLine cmd("Cuberite");
	TCLAP::ValueArg<int> slotsArg    ("s", "max-players",         "Maximum number of slots for the server to use, overrides setting in setting.ini", false, -1, "number", cmd);
	TCLAP::ValueArg<AString> confArg ("c", "config-file",         "Config file to use", false, "settings.ini", "string", cmd);
	TCLAP::MultiArg<int> portsArg    ("p", "port",                "The port number the server should listen to", false, "port", cmd);
	TCLAP::SwitchArg commLogArg      ("",  "log-comm",            "Log server client communications to file", cmd);
	TCLAP::SwitchArg commLogInArg    ("",  "log-comm-in",         "Log inbound server client communications to file", cmd);
	TCLAP::SwitchArg commLogOutArg   ("",  "log-comm-out",        "Log outbound server client communications to file", cmd);
	TCLAP::SwitchArg crashDumpFull   ("",  "crash-dump-full",     "Crashdumps created by the server will contain full server memory", cmd);
	TCLAP::SwitchArg crashDumpGlobals("",  "crash-dump-globals",  "Crashdumps created by the server will contain the global variables' values", cmd);
	TCLAP::SwitchArg noBufArg        ("",  "no-output-buffering", "Disable output buffering", cmd);
	TCLAP::SwitchArg noFileLogArg    ("",  "no-log-file",         "Disable logging to file", cmd);
	TCLAP::SwitchArg runAsServiceArg ("d", "service",             "Run as a service on Windows, or daemon on UNIX like systems", cmd);
	cmd.parse(argc, argv);

	// Copy the parsed args' values into a settings repository:
	if (confArg.isSet())
	{
		AString conf_file = confArg.getValue();
		a_Settings.AddValue("Server", "ConfigFile", conf_file);
	}
	if (slotsArg.isSet())
	{
		int slots = slotsArg.getValue();
		a_Settings.AddValue("Server", "MaxPlayers", static_cast<Int64>(slots));
	}
	if (portsArg.isSet())
	{
		for (auto port: portsArg.getValue())
		{
			a_Settings.AddValue("Server", "Ports", std::to_string(port));
		}
	}
	if (noFileLogArg.getValue())
	{
		a_Settings.AddValue("Server", "DisableLogFile", true);
	}
	if (commLogArg.getValue())
	{
		g_ShouldLogCommIn = true;
		g_ShouldLogCommOut = true;
	}
	else
	{
		g_ShouldLogCommIn = commLogInArg.getValue();
		g_ShouldLogCommOut = commLogOutArg.getValue();
	}
	if (noBufArg.getValue())
	{
		setvbuf(stdout, nullptr, _IONBF, 0);
	}
	a_Settings.SetReadOnly();

	if (runAsServiceArg.getValue())
	{
		g_RunAsService = true;
	}

	// Apply the CrashDump flags for platforms that support them:
	if (crashDumpGlobals.getValue())
	{
		MiniDumpWriter::AddDumpFlags(MiniDumpFlags::WithDataSegments);
	}
	if (crashDumpFull.getValue())
	{
		MiniDumpWriter::AddDumpFlags(MiniDumpFlags::WithFullMemory);
	}
}





////////////////////////////////////////////////////////////////////////////////
// UniversalMain - Main startup logic for both standard running and as a service

static int UniversalMain(int argc, char * argv[], const bool a_RunningAsService)
{
	const struct MiniDumpWriterRAII
	{
		MiniDumpWriterRAII()
		{
			// Registers a last chance exception handler to write a minidump on crash:
			MiniDumpWriter::Register();
		}

		~MiniDumpWriterRAII()
		{
			MiniDumpWriter::Unregister();
		}
	} MiniDumpWriter;

	const struct SleepResolutionBoosterRAII
	{
		SleepResolutionBoosterRAII()
		{
			// Boost timer resolution to keep TPS high:
			SleepResolutionBooster::Register();
		}

		~SleepResolutionBoosterRAII()
		{
			SleepResolutionBooster::Unregister();
		}
	} SleepResolutionBooster;

	// Register signal handlers, enabling graceful shutdown from the terminal:
	ConsoleSignalHandler::Register();

	// Initialize logging subsystem:
	cLogger::InitiateMultithreading();

	try
	{
		cMemorySettingsRepository Settings;
		ParseArguments(argc, argv, Settings);  // Make sure g_RunAsService is set correctly before checking its value.

		// Attempt to run as a service:
		if (g_RunAsService && !a_RunningAsService)
		{
			// This will either fork or call UniversalMain again:
			if (StartAsService::MakeIntoService<&UniversalMain>())
			{
				return EXIT_SUCCESS;
			}
		}

		while (true)
		{
			const struct NetworkRAII
			{
				NetworkRAII()
				{
					// Initialize LibEvent:
					cNetworkSingleton::Get().Initialise();
				}

				~NetworkRAII()
				{
					// Shutdown all of LibEvent:
					cNetworkSingleton::Get().Terminate();
				}
			} LibEvent;

			cRoot Root;
			if (!Root.Run(Settings))
			{
				break;
			}
		}

		return EXIT_SUCCESS;
	}
	catch (const fmt::format_error & Oops)
	{
		std::cerr << "Formatting exception: " << Oops.what() << '\n';
	}
	catch (const TCLAP::ArgException & Oops)
	{
		std::cerr << fmt::sprintf("Error reading command line {} for argument {}\n", Oops.error(), Oops.argId());
	}
	catch (const std::exception & Oops)
	{
		std::cerr << "Standard exception: " << Oops.what() << '\n';
	}
	catch (...)
	{
		std::cerr << "Unknown exception!\n";
	}

	return EXIT_FAILURE;
}





int main(int argc, char ** argv)
{
#if !defined(NDEBUG) && defined(_MSC_VER)
	_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	// _X: The simple built-in CRT leak finder - simply break when allocating the Nth block ({N} is listed in the leak output)
	// Only useful when the leak is in the same sequence all the time
	// _CrtSetBreakAlloc(85950);

#endif  // !NDEBUG && _MSC_VER

	return UniversalMain(argc, argv, false);
}
