/* ======== SourceMM ========
* Copyright (C) 2004-2006 Metamod:Source Development Team
* No warranties of any kind
*
* License: zlib/libpng
*
* Author(s): David "BAILOPAN" Anderson
* ============================
*/

#include <ctype.h>
#include "CSmmAPI.h"
#include "concommands.h"
#include "CPlugin.h"
#include "sh_string.h"
#include "sh_list.h"

/**
 * @brief Console Command Implementations
 * @file concommands.cpp
 */

CAlwaysRegisterableCommand g_EternalCommand;
SMConVarAccessor g_SMConVarAccessor;

bool SMConVarAccessor::RegisterConCommandBase(ConCommandBase *pCommand)
{
	// Add the FCVAR_GAMEDLL flag 
	// => No crash on exit!
	// UPDATE: Do _not_ add the FCVAR_GAMEDLL flag here, as it
	// causes the command to be unusable on listenservers until you load a map
	// We will set the FCVAR_GAMEDLL flag on all commands we have registered once we are being unloaded
	//pCommand->AddFlags(FCVAR_GAMEDLL);
	m_RegisteredCommands.push_back(pCommand);

	pCommand->SetNext( NULL );
	g_Engine.icvar->RegisterConCommandBase(pCommand);

	return true;
}

bool SMConVarAccessor::Register(ConCommandBase *pCommand)
{
	//simple, don't mark as part of sourcemm!
	pCommand->SetNext( NULL );
	g_Engine.icvar->RegisterConCommandBase(pCommand);

	return true;
}

void SMConVarAccessor::MarkCommandsAsGameDLL()
{
	for (SourceHook::List<ConCommandBase*>::iterator iter = m_RegisteredCommands.begin();
		iter != m_RegisteredCommands.end(); ++iter)
	{
		(*iter)->AddFlags(FCVAR_GAMEDLL);
	}
}

void SMConVarAccessor::Unregister(ConCommandBase *pCommand)
{
	ICvar *cv = g_Engine.icvar;
	ConCommandBase *ptr = cv->GetCommands();

	if (ptr == pCommand)
	{
		//first in list
		g_EternalCommand.BringToFront();
		g_EternalCommand.SetNext(const_cast<ConCommandBase *>(pCommand->GetNext()));
	} else {
		//find us and unregister us
		ConCommandBase *pPrev = NULL;
		while (ptr)
		{
			if (ptr == pCommand)
				break;
			pPrev = ptr;
			ptr = const_cast<ConCommandBase *>(ptr->GetNext());
		}
		if (pPrev && ptr == pCommand)
		{
			pPrev->SetNext(const_cast<ConCommandBase *>(pCommand->GetNext()));
		}
	}
}

void SMConVarAccessor::UnregisterGameDLLCommands()
{
	ConCommandBase *begin = g_Engine.icvar->GetCommands();
	ConCommandBase *iter = begin;
	ConCommandBase *prev = NULL;
	while (iter)
	{
		// watch out for the ETERNAL COMMAND!
		if (iter != &g_EternalCommand && iter->IsBitSet(FCVAR_GAMEDLL))
		{
			// Remove it!
			if (iter == begin)
			{
				g_EternalCommand.BringToFront();
				iter = const_cast<ConCommandBase*>(iter->GetNext());
				g_EternalCommand.SetNext(iter);
				prev = &g_EternalCommand;
				continue;
			}
			else
			{
				iter = const_cast<ConCommandBase*>(iter->GetNext());
				prev->SetNext(iter);
				continue;
			}
		}
		prev = iter;
		iter = const_cast<ConCommandBase*>(iter->GetNext());
	}
}

ConVar metamod_version("metamod_version", SOURCEMM_VERSION, FCVAR_REPLICATED | FCVAR_SPONLY | FCVAR_NOTIFY, "Metamod:Source Version");
#if defined WIN32 || defined _WIN32
ConVar mm_pluginsfile("mm_pluginsfile", "addons\\metamod\\metaplugins.ini", FCVAR_SPONLY, "Metamod:Source Plugins File");
#else
ConVar mm_pluginsfile("mm_pluginsfile", "addons/metamod/metaplugins.ini", FCVAR_SPONLY, "Metamod:Source Plugins File");
#endif

CON_COMMAND(meta, "Metamod:Source Menu")
{
	IVEngineServer *e = g_Engine.engine;

	int args = e->Cmd_Argc();

	if (args >= 2)
	{
		const char *command = e->Cmd_Argv(1);
		if (strcmp(command, "credits") == 0)
		{
			CONMSG("Metamod:Source was developed by:\n");
			CONMSG("  SourceHook: Pavol \"PM OnoTo\" Marko\n");
			CONMSG("  GameDLL/Plugins: David \"BAILOPAN\" Anderson\n");
			CONMSG("  GameDLL: Scott \"Damaged Soul\" Ehlert\n");
			CONMSG("For more information, see the official website\n");
			CONMSG("http://www.sourcemm.net/\n");
			
			return;
		} else if (strcmp(command, "version") == 0) {
			CONMSG("Metamod:Source version %s\n", SOURCEMM_VERSION);
			CONMSG("Compiled on: %s\n", SOURCEMM_DATE);
			CONMSG("Plugin interface version: %d:%d\n", PLAPI_VERSION, PLAPI_MIN_VERSION);
			CONMSG("SourceHook version: %d:%d\n", g_SourceHook.GetIfaceVersion(), g_SourceHook.GetImplVersion());
			CONMSG("http://www.sourcemm.net/\n");

			return;
		} else if (strcmp(command, "game") == 0) {
			CONMSG("GameDLL Information\n");
			CONMSG("  Mod path: %s\n", g_ModPath.c_str());
			CONMSG("  Dll path: %s\n", g_BinPath.c_str());

			return;
		} else if (strcmp(command, "refresh") == 0) {
			char full_path[255];
#if defined WIN32 || defined _WIN32
			snprintf(full_path, sizeof(full_path)-1, "%s\\addons\\metamod\\%s", g_ModPath.c_str(), "metaplugins.ini");
#else
			snprintf(full_path, sizeof(full_path)-1, "%s/addons/metamod/%s", g_ModPath.c_str(), "metaplugins.ini");
#endif
			LoadPluginsFromFile(full_path);

			return;
		} else if (strcmp(command, "list") == 0) {
			SourceMM::CPluginManager::CPlugin *pl;
			PluginIter i;
			const char *status="";
			const char *version=NULL;
			const char *name=NULL;
			const char *author=NULL;

			CONMSG("-Id- %-16.15s  %-8.7s  %-12.11s %-8.7s\n", "Name", "Version", "Author", "Status");
			for (i=g_PluginMngr._begin(); i!=g_PluginMngr._end(); i++)
			{
				pl = (*i);
				if (!pl)
					break;
				if (pl->m_Status == Pl_Paused)
				{
					status = "PAUSE";
				} else if (pl->m_Status == Pl_Running) {
					if (pl->m_API && pl->m_API->QueryRunning(NULL, 0))
						status = "RUN";
					else
						status = "STOPPED";
				} else if (pl->m_Status == Pl_Refused) {
					status = "FAIL";
				} else if (pl->m_Status == Pl_Error) {
					status = "ERROR";
				} else if (pl->m_Status == Pl_NotFound) {
					status = "NOFILE";
				}

				if (pl->m_API)
				{
					version = pl->m_API->GetVersion();
					author = pl->m_API->GetAuthor();
					name = pl->m_API->GetName();
				} else {
					version = "-";
					author = "-";
					name = "-";
				}

				if (!version)
					version = "-";
				if (!author)
					author = "-";
				if (!name)
					name = pl->m_File.c_str();


				CONMSG("[%02d] %-16.15s  %-8.7s  %-12.11s %-8.7s\n", pl->m_Id, name, version, author, status);
			}

			//CONMSG("\n");

			return;
		} else if (strcmp(command, "cmds") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));
				SourceMM::CPluginManager::CPlugin *pl = g_PluginMngr.FindById(id);

				if (!pl)
				{
					CONMSG("Plugin %d not found.\n", id);
					return;
				}

				if (!pl->m_API)
				{
					CONMSG("Plugin %d is not loaded.\n", id);
				} else {
					CONMSG("Console commands for %s:\n", pl->m_API->GetName());
					SourceHook::List<ConCommandBase *>::iterator ci;
					size_t count = 0;

					for (ci=pl->m_Cmds.begin(); ci!=pl->m_Cmds.end(); ci++)
					{
						count++;
						CONMSG(" [%5d] %-s\n", count, (*ci)->GetName());
					}
				}
			} else {
				CONMSG("Usage: meta cmds <id>\n");
			}

			return;
		} else if (strcmp(command, "cvars") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));
				SourceMM::CPluginManager::CPlugin *pl = g_PluginMngr.FindById(id);

				if (!pl)
				{
					CONMSG("Plugin %d not found.\n", id);
					return;
				}

				if (!pl->m_API)
				{
					CONMSG("Plugin %d is not loaded.\n", id);
				} else {
					CONMSG("Registered cvars for %s:\n", pl->m_API->GetName());
					SourceHook::List<ConCommandBase *>::iterator ci;
					size_t count = 0;

					for (ci=pl->m_Cvars.begin(); ci!=pl->m_Cvars.end(); ci++)
					{
						count++;
						CONMSG(" [%5d] %-s\n", count, (*ci)->GetName());
					}
				}
			} else {
				CONMSG("Usage: meta cvars <id>\n");
			}

			return;
		} else if (strcmp(command, "info") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));
				SourceMM::CPluginManager::CPlugin *pl = g_PluginMngr.FindById(id);
				if (!pl)
				{
					CONMSG("Plugin %d not found.\n", id);
					return;
				}

				if (!pl->m_API)
				{
					CONMSG("Plugin %d is not loaded.\n", id);
				} else {
					if (pl->m_Status == Pl_Paused)
					{
						CONMSG("Plugin %d is paused.\n", id);
					} else if (pl->m_Status == Pl_Running) {
						char run_msg[255];
						bool run = false;
						if (pl->m_API && pl->m_API->QueryRunning(run_msg, sizeof(run_msg)-1))
							run = true;
						if (run)
						{
							CONMSG("Plugin %d is running.\n", id);
						} else {
							CONMSG("Plugin %d is stopped: %s\n", id, run_msg);
						}
					}
					CONMSG("  Name: \"%s\" by %s\n", pl->m_API->GetName(), pl->m_API->GetAuthor());
					CONMSG("  Version: %s\n", pl->m_API->GetVersion());
					CONMSG("  Description: %s\n", pl->m_API->GetDescription());
					CONMSG("  License: %s\n", pl->m_API->GetLicense());
					CONMSG("  URL: %s\n", pl->m_API->GetURL());
					CONMSG("  Details: API %03d, Date: %s\n", pl->m_API->GetApiVersion(), pl->m_API->GetDate());
				}
				CONMSG("File: %s\n\n", pl->m_File.c_str());

				return;
			} else {
				CONMSG("Usage: meta info <id>\n");

				return;
			}
		} else if (strcmp(command, "pause") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));

				char error[255];

				if (!g_PluginMngr.Pause(id, error, sizeof(error)-1))
				{
					CONMSG("Pause failed: %s\n", error);
					return;
				}
				
				CONMSG("Plugin %d has been paused.\n", id);

				return;
			} else {
				CONMSG("Usage: meta pause <id>\n");

				return;
			}
		} else if (strcmp(command, "unpause") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));
				char error[255];

				if (!g_PluginMngr.Unpause(id, error, sizeof(error)-1))
				{
					CONMSG("Unpause failed: %s\n", error);
					return;
				}

				CONMSG("Plugin %d has been unpaused.\n", id);

				return;
			} else {
				CONMSG("Usage: meta unpause <id>\n");

				return;
			}
		} else if (strcmp(command, "load") == 0) {
			if (args >= 3)
			{
				const char *file = e->Cmd_Argv(2);
				char full_path[255];

				if (file[0] == '/' || strcmp(&(file[1]), ":\\") == 0)
				{
					snprintf(full_path, sizeof(full_path)-1, "%s", file);
				} else {
					const char *ext = UTIL_GetExtension(file);
#if defined WIN32 || defined _WIN32
					ext = ext ? "" : ".dll";
					snprintf(full_path, sizeof(full_path)-1, "%s\\%s%s", g_ModPath.c_str(), file, ext);
#else
					ext = ext ? "" : "_i486.so";
					snprintf(full_path, sizeof(full_path)-1, "%s/%s%s", g_ModPath.c_str(), file, ext);
#endif
				}

				char error[255]={0};
				bool already;
				SourceMM::CPluginManager::CPlugin *pl;

				PluginId id = g_PluginMngr.Load(full_path, Pl_Console, already, error, sizeof(error)-1);
				pl = g_PluginMngr.FindById(id);
				if (!pl || id < Pl_MinId || (pl->m_Status < Pl_Paused))
				{
					CONMSG("Failed to load plugin %s (%s).\n", file, error);
					return;
				}

				if (!already)
				{
					CONMSG("Plugin \"%s\" loaded with id %d.\n", pl->m_API->GetName(), pl->m_Id);
				} else {
					CONMSG("Plugin \"%s\" is already loaded as %d.\n", pl->m_API->GetName(), pl->m_Id);
				}
				
				return;
			} else {
				CONMSG("Usage: meta load <path>\n");

				return;
			}
		} else if (strcmp(command, "unload") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));
				char error[255]={0};

				if (!g_PluginMngr.Unload(id, false, error, sizeof(error)-1))
				{
					CONMSG("Unload failed: %s\n", error);
					return;
				}

				CONMSG("Plugin %d unloaded.\n", id);

				return;
			} else {
				CONMSG("Usage: meta unload <id>\n");

				return;
			}
		} else if (strcmp(command, "force_unload") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));
				char error[255]={0};

				if (!g_PluginMngr.Unload(id, false, error, sizeof(error)-1))
				{
					CONMSG("Force unload failed: %s\n", error);
					return;
				}

				CONMSG("Plugin %d force unloaded.\n", id);

				return;
			} else {
				CONMSG("Usage: meta force_unload <id>\n");

				return;
			}
		} else if (strcmp(command, "clear") == 0) {
			if (!g_PluginMngr.UnloadAll())
			{
				CONMSG("One or more plugins resisted removal (cleaned anyway).\n");
				return;
			} 

			CONMSG("All plugins unloaded.\n");

			return;
		} else if (strcmp(command, "retry") == 0) {
			if (args >= 3)
			{
				int id = atoi(e->Cmd_Argv(2));
				char error[255];

				if (!g_PluginMngr.Retry(id, error, sizeof(error)-1))
				{
					CONMSG("Error reloading plugin: %s\n", error);
					return;
				}

				CONMSG("Plugin %d successfully reloaded.\n", id);

				return;
			} else {
				CONMSG("Usage: meta retry <id>\n");

				return;
			}
		}
	}

	CONMSG("Metamod:Source Menu\n");
	CONMSG("usage: meta <command> [arguments]\n");
	CONMSG("  clear        - Unload all plugins forcefully\n");
	CONMSG("  cmds         - Show plugin commands\n");
	CONMSG("  cvars        - Show plugin cvars\n");
	CONMSG("  credits      - About Metamod:Source\n");
	CONMSG("  force_unload - Forcefully unload a plugin\n");
	CONMSG("  game         - Information about GameDLL\n");
	CONMSG("  info         - Information about a plugin\n");
	CONMSG("  list         - List plugins\n");
	CONMSG("  load         - Load a plugin\n");
	CONMSG("  pause        - Pause a running plugin\n");
	CONMSG("  refresh      - Reparse plugins file\n");
	CONMSG("  retry        - Attempt to reload a plugin\n");
	CONMSG("  unload       - Unload a loaded plugin\n");
	CONMSG("  unpause      - Unpause a paused plugin\n");
	CONMSG("  version      - Version information\n");
}

CAlwaysRegisterableCommand::CAlwaysRegisterableCommand()
{
	Create("", NULL, FCVAR_UNREGISTERED|FCVAR_GAMEDLL);
	m_pICvar = NULL;
}

bool CAlwaysRegisterableCommand::IsRegistered( void ) const
{
	return false;
}

void CAlwaysRegisterableCommand::BringToFront()
{
	if (!m_pICvar)
		m_pICvar = g_Engine.icvar;

	// First, let's try to find us!
	ConCommandBase *pPtr = m_pICvar->GetCommands();

	if (pPtr == this)
	{
		// We are already at the beginning; Nothing to do
		return;
	}

	while (pPtr)
	{
		if (pPtr == this && pPtr->IsCommand() && stricmp(GetName(), pPtr->GetName()) == 0)
			break;
		ConCommandBase *pPrev = NULL;
		while (pPtr)
		{
			if (pPtr == this)
				break;
			pPrev = pPtr;
			pPtr = const_cast<ConCommandBase*>(pPtr->GetNext());
		}
		if (pPrev && pPtr == this)
		{
			pPrev->SetNext(m_pNext);		// Remove us from the list
		}
		// Now, register us
		SetNext(NULL);
		m_pICvar->RegisterConCommandBase(this);
	}
}

const char *GetPluginsFile()
{
	return mm_pluginsfile.GetString();
}

