/**
 * vim: set ts=4 sw=4 tw=99 noet :
 * =============================================================================
 * SourceMod
 * Copyright (C) 2004-2009 AlliedModders LLC.  All rights reserved.
 * =============================================================================
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 3.0, as published by the
 * Free Software Foundation.
 * 
 * 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.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * As a special exception, AlliedModders LLC gives you permission to link the
 * code of this program (as well as its derivative works) to "Half-Life 2," the
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
 * by the Valve Corporation.  You must obey the GNU General Public License in
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
 * this exception to all derivative works.  AlliedModders LLC defines further
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
 * or <http://www.sourcemod.net/license.php>.
 *
 * Version: $Id$
 */

#include "sm_srvcmds.h"
#include "sm_stringutil.h"
#include "CoreConfig.h"
#include "ConVarManager.h"
#include "logic_bridge.h"
#include <sourcemod_version.h>

RootConsoleMenu g_RootMenu;

ConVar sourcemod_version("sourcemod_version", SOURCEMOD_VERSION, FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY, "SourceMod Version");

RootConsoleMenu::RootConsoleMenu()
{
	m_CfgExecDone = false;
}

RootConsoleMenu::~RootConsoleMenu()
{
	List<ConsoleEntry *>::iterator iter;
	for (iter=m_Menu.begin(); iter!=m_Menu.end(); iter++)
	{
		delete (*iter);
	}
	m_Menu.clear();
}

void RootConsoleMenu::OnSourceModStartup(bool late)
{
#if SOURCE_ENGINE >= SE_ORANGEBOX
	g_pCVar = icvar;
#endif
	CONVAR_REGISTER(this);
	AddRootConsoleCommand("version", "Display version information", this);
	AddRootConsoleCommand("credits", "Display credits listing", this);
}

void RootConsoleMenu::OnSourceModAllInitialized()
{
	sharesys->AddInterface(NULL, this);
}

void RootConsoleMenu::OnSourceModShutdown()
{
	RemoveRootConsoleCommand("credits", this);
	RemoveRootConsoleCommand("version", this);
}

bool RootConsoleMenu::RegisterConCommandBase(ConCommandBase *pCommand)
{
	META_REGCVAR(pCommand);

	/* Override values of convars created by SourceMod convar manager if specified on command line */
	const char *cmdLineValue = icvar->GetCommandLineValue(pCommand->GetName());
	if (cmdLineValue && !pCommand->IsCommand())
	{
		static_cast<ConVar *>(pCommand)->SetValue(cmdLineValue);
	}

	return true;
}

void RootConsoleMenu::ConsolePrint(const char *fmt, ...)
{
	char buffer[512];

	va_list ap;
	va_start(ap, fmt);
	size_t len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
	va_end(ap);

	if (len >= sizeof(buffer) - 1)
	{
		buffer[510] = '\n';
		buffer[511] = '\0';
	} else {
		buffer[len++] = '\n';
		buffer[len] = '\0';
	}
	
	META_CONPRINT(buffer);
}

bool RootConsoleMenu::AddRootConsoleCommand(const char *cmd, const char *text, IRootConsoleCommand *pHandler)
{
	return _AddRootConsoleCommand(cmd, text, pHandler, false);
}

bool RootConsoleMenu::AddRootConsoleCommand2(const char *cmd, const char *text, IRootConsoleCommand *pHandler)
{
	return _AddRootConsoleCommand(cmd, text, pHandler, true);
}

bool RootConsoleMenu::_AddRootConsoleCommand(const char *cmd,
											 const char *text,
											 IRootConsoleCommand *pHandler,
											 bool version2)
{
	if (m_Commands.contains(cmd))
		return false;

	/* Sort this into the menu */
	List<ConsoleEntry *>::iterator iter = m_Menu.begin();
	ConsoleEntry *pEntry;
	bool inserted = false;
	while (iter != m_Menu.end())
	{
		pEntry = (*iter);
		if (strcmp(cmd, pEntry->command.c_str()) < 0)
		{
			ConsoleEntry *pNew = new ConsoleEntry;
			pNew->command.assign(cmd);
			pNew->description.assign(text);
			pNew->version2 = version2;
			pNew->cmd = pHandler;
			m_Commands.insert(cmd, pNew);
			m_Menu.insert(iter, pNew);
			inserted = true;
			break;
		}
		iter++;
	}

	if (!inserted)
	{
		ConsoleEntry *pNew = new ConsoleEntry;
		pNew->command.assign(cmd);
		pNew->description.assign(text);
		pNew->version2 = version2;
		pNew->cmd = pHandler;
		m_Commands.insert(cmd, pNew);
		m_Menu.push_back(pNew);
	}

	return true;
}

bool RootConsoleMenu::RemoveRootConsoleCommand(const char *cmd, IRootConsoleCommand *pHandler)
{
	m_Commands.remove(cmd);

	List<ConsoleEntry *>::iterator iter;
	ConsoleEntry *pEntry;
	for (iter=m_Menu.begin(); iter!=m_Menu.end(); iter++)
	{
		pEntry = (*iter);
		if (pEntry->command.compare(cmd) == 0)
		{
			delete pEntry;
			m_Menu.erase(iter);
			break;
		}
	}

	return true;
}

void RootConsoleMenu::DrawGenericOption(const char *cmd, const char *text)
{
	char buffer[255];
	size_t len, cmdlen = strlen(cmd);

	len = UTIL_Format(buffer, sizeof(buffer), "    %s", cmd);
	if (cmdlen < 16)
	{
		size_t num = 16 - cmdlen;
		for (size_t i = 0; i < num; i++)
		{
			buffer[len++] = ' ';
		}
		len += snprintf(&buffer[len], sizeof(buffer) - len, " - %s", text);
		ConsolePrint("%s", buffer);
	}
}

const char *RootConsoleMenu::GetInterfaceName()
{
	return SMINTERFACE_ROOTCONSOLE_NAME;
}

unsigned int RootConsoleMenu::GetInterfaceVersion()
{
	return SMINTERFACE_ROOTCONSOLE_VERSION;
}

#if SOURCE_ENGINE==SE_EPISODEONE || SOURCE_ENGINE==SE_DARKMESSIAH
class CCommandArgs : public ICommandArgs
{
public:
	CCommandArgs(const CCommand& _cmd)
	{
	}
	const char *Arg(int n) const
	{
		return engine->Cmd_Argv(n);
	}
	int ArgC() const
	{
		return engine->Cmd_Argc();
	}
	const char *ArgS() const
	{
		return engine->Cmd_Args();
	}
};
#else
class CCommandArgs : public ICommandArgs
{
	const CCommand *cmd;
public:
	CCommandArgs(const CCommand& _cmd) : cmd(&_cmd)
	{
	}
	const char *Arg(int n) const
	{
		return cmd->Arg(n);
	}
	int ArgC() const
	{
		return cmd->ArgC();
	}
	const char *ArgS() const
	{
		return cmd->ArgS();
	}
};
#endif

void RootConsoleMenu::GotRootCmd(const CCommand &cmd)
{
	unsigned int argnum = cmd.ArgC();

	if (argnum >= 2)
	{
		const char *cmdname = cmd.Arg(1);
		if (strcmp(cmdname, "internal") == 0)
		{
			if (argnum >= 3)
			{
				const char *arg = cmd.Arg(2);
				if (strcmp(arg, "1") == 0)
				{
					SM_ConfigsExecuted_Global();
				}
				else if (strcmp(arg, "2") == 0)
				{
					if (argnum >= 4)
					{
						SM_ConfigsExecuted_Plugin(atoi(cmd.Arg(3)));
					}
				}
			}
			return;
		}

		CCommandArgs ocmd(cmd);

		ConsoleEntry *entry;
		if (m_Commands.retrieve(cmdname, &entry))
		{
			if (entry->version2)
			{
				entry->cmd->OnRootConsoleCommand2(cmdname, &ocmd);
			}
			else
			{
				entry->cmd->OnRootConsoleCommand(cmdname, cmd);
			}
			return;
		}
	}

	ConsolePrint("SourceMod Menu:");
	ConsolePrint("Usage: sm <command> [arguments]");

	List<ConsoleEntry *>::iterator iter;
	ConsoleEntry *pEntry;
	for (iter=m_Menu.begin(); iter!=m_Menu.end(); iter++)
	{
		pEntry = (*iter);
		DrawGenericOption(pEntry->command.c_str(), pEntry->description.c_str());
	}
}

void RootConsoleMenu::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
{
	if (strcmp(cmdname, "credits") == 0)
	{
		ConsolePrint(" SourceMod was developed by AlliedModders, LLC.");
		ConsolePrint(" Development would not have been possible without the following people:");
		ConsolePrint("  David \"BAILOPAN\" Anderson");
		ConsolePrint("  Matt \"pRED\" Woodrow");
		ConsolePrint("  Scott \"DS\" Ehlert");
		ConsolePrint("  Fyren");
		ConsolePrint("  Nicholas \"psychonic\" Hastings");
		ConsolePrint("  Asher \"asherkin\" Baker");
		ConsolePrint("  Borja \"faluco\" Ferrer");
		ConsolePrint("  Pavol \"PM OnoTo\" Marko");
		ConsolePrint(" Special thanks to Liam, ferret, and Mani");
		ConsolePrint(" Special thanks to Viper and SteamFriends");
		ConsolePrint(" http://www.sourcemod.net/");
	}
	else if (strcmp(cmdname, "version") == 0)
	{
		ConsolePrint(" SourceMod Version Information:");
		ConsolePrint("    SourceMod Version: %s", SOURCEMOD_VERSION);
		if (g_pSourcePawn2->IsJitEnabled())
			ConsolePrint("    SourcePawn Engine: %s (build %s)", g_pSourcePawn2->GetEngineName(), g_pSourcePawn2->GetVersionString());
		else
			ConsolePrint("    SourcePawn Engine: %s (build %s NO JIT)", g_pSourcePawn2->GetEngineName(), g_pSourcePawn2->GetVersionString());
		ConsolePrint("    SourcePawn API: v1 = %d, v2 = %d", g_pSourcePawn->GetEngineAPIVersion(), g_pSourcePawn2->GetAPIVersion());
		ConsolePrint("    Compiled on: %s", SOURCEMOD_BUILD_TIME);
#if defined(SM_GENERATED_BUILD)
		ConsolePrint("    Built from: https://github.com/alliedmodders/sourcemod/commit/%s", SOURCEMOD_SHA);
		ConsolePrint("    Build ID: %s:%s", SOURCEMOD_LOCAL_REV, SOURCEMOD_SHA);
#endif
		ConsolePrint("    http://www.sourcemod.net/");
	}
}

CON_COMMAND(sm, "SourceMod Menu")
{
#if SOURCE_ENGINE <= SE_DARKMESSIAH
	CCommand args;
#endif
	g_RootMenu.GotRootCmd(args);
}

FILE *g_pHndlLog = NULL;

void write_handles_to_log(const char *fmt, ...)
{
	va_list ap;
	
	va_start(ap, fmt);
	vfprintf(g_pHndlLog, fmt, ap);
	fprintf(g_pHndlLog, "\n");
	va_end(ap);
}

void write_handles_to_game(const char *fmt, ...)
{
	size_t len;
	va_list ap;
	char buffer[1024];
	
	va_start(ap, fmt);
	len = UTIL_FormatArgs(buffer, sizeof(buffer)-2, fmt, ap);
	va_end(ap);

	buffer[len] = '\n';
	buffer[len+1] = '\0';

	engine->LogPrint(buffer);
}

CON_COMMAND(sm_dump_handles, "Dumps Handle usage to a file for finding Handle leaks")
{
#if SOURCE_ENGINE <= SE_DARKMESSIAH
	CCommand args;
#endif
	if (args.ArgC() < 2)
	{
		g_RootMenu.ConsolePrint("Usage: sm_dump_handles <file> or <log> for game logs");
		return;
	}

	if (strcmp(args.Arg(1), "log") != 0)
	{
		const char *arg = args.Arg(1);
		FILE *fp = fopen(arg, "wt");
		if (!fp)
		{
			g_RootMenu.ConsolePrint("Could not find file \"%s\"", arg);
			return;
		}

		g_pHndlLog = fp;
		logicore.DumpHandles(write_handles_to_log);
		g_pHndlLog = NULL;

		fclose(fp);
	}
	else
	{
		logicore.DumpHandles(write_handles_to_game);
	}
}

CON_COMMAND(sm_reload_translations, "Reparses all loaded translation files")
{
	translator->RebuildLanguageDatabase();
}

CON_COMMAND(sm_dump_admcache, "Dumps the admin cache for debugging")
{
	char buffer[PLATFORM_MAX_PATH];
	g_SourceMod.BuildPath(Path_SM, buffer, sizeof(buffer), "data/admin_cache_dump.txt");

	if (!logicore.DumpAdminCache(buffer))
	{
		g_RootMenu.ConsolePrint("Could not open file for writing: %s", buffer);
		return;
	}

	g_RootMenu.ConsolePrint("Admin cache dumped to: %s", buffer);
}