///////////////////////////////////////////////////////////////////////////
/// @file
/// @section LICENSE
/// 
/// Copyright (C) 1996-1997 Id Software, Inc.
/// 
/// 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.
/// 
/// You should have received a copy of the GNU General Public License
/// along with this program; if not, write to the Free Software
/// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
/// 
/// @section DESCRIPTION
/// 
/// The time class represents a moment of time.
///////////////////////////////////////////////////////////////////////////

// cmd.c -- Quake script command processing module

#include "quakedef.h"

void Cmd_ForwardToServer (void);


#define	MAX_ALIAS_NAME	32

///////////////////////////////////////////////////////////////////////////
/// @brief	Console command alias. Used for creating an alias for an already
///			existing command.
///			It's also a linked list
///////////////////////////////////////////////////////////////////////////
typedef struct cmdalias_s
{
	struct cmdalias_s *next;		///< Next alias in the linked list.
	char	name[MAX_ALIAS_NAME];	///< The alias name.
	char	*value;					///< The command that this alias will run
} cmdalias_t;

/// The top of the @b alias @b linked @b list
cmdalias_t	*cmd_alias;

int trashtest;
int *trashspot;

bool cmd_wait;
//=============================================================================


///////////////////////////////////////////////////////////////////////////
/// @brief	Causes execution of the remainder of the command buffer to be
///			delayed until next frame.  This allows commands like:
///			bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
///////////////////////////////////////////////////////////////////////////
void Cmd_Wait_f (void)
{
	cmd_wait = true;
}

/*
=============================================================================

						COMMAND BUFFER

=============================================================================
*/

sizebuf_t	cmd_text;

///////////////////////////////////////////////////////////////////////////
/// @brief	Allocates an initial text buffer that will grow as needed.
///////////////////////////////////////////////////////////////////////////
void Cbuf_Init (void)
{
	SZ_Alloc (&cmd_text, 8192);		// space for commands and script files
}

///////////////////////////////////////////////////////////////////////////
/// @brief	Adds command text at the end of the buffer
///////////////////////////////////////////////////////////////////////////
void Cbuf_AddText (char *text)
{
	int l;
	
	l = Q_strlen (text);
	
	if (cmd_text.cursize + l >= cmd_text.maxsize)
	{
		Con_Printf ("Cbuf_AddText: overflow\n");
		return;
	}
	
	SZ_Write (&cmd_text, text, Q_strlen (text));
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Adds command text immediately after the current command
///			Adds a \n to the text
/// 
/// when a command wants to issue other commands immediately, the text is
/// inserted at the beginning of the buffer, before any remaining
/// unexecuted commands.
/// @param	text
/// @todo	FIXME: actually change the command buffer to do less copying
///////////////////////////////////////////////////////////////////////////
void Cbuf_InsertText (char *text)
{
	char	*temp;
	int		templen;
	
	// copy off any commands still remaining in the exec buffer
	templen = cmd_text.cursize;
	if (templen)
	{
		temp = Z_Malloc (templen);
		Q_memcpy (temp, cmd_text.data, templen);
		SZ_Clear (&cmd_text);
	}
	else
		temp = NULL;	// shut up compiler
		
	// add the entire text of the file
	Cbuf_AddText (text);
	
	// add the copied off data
	if (templen)
	{
		SZ_Write (&cmd_text, temp, templen);
		Z_Free (temp);
	}
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Pulls off \n terminated lines of text from the command buffer and sends
///			them through Cmd_ExecuteString.  Stops when the buffer is empty.
///			Normally called once per frame, but may be explicitly invoked.
///			Do not call inside a command function!
/// 
/// @todo expand @b brief
///////////////////////////////////////////////////////////////////////////
void Cbuf_Execute (void)
{
	int		i;
	char	*text;
	char	line[1024];
	int		quotes;
	
	while (cmd_text.cursize)
	{
		// find a \n or ; line break
		text = (char *) cmd_text.data;
		
		quotes = 0;
		for (i = 0 ; i < cmd_text.cursize ; i++)
		{
			if (text[i] == '"')
				quotes++;
			if (! (quotes & 1) &&  text[i] == ';')
				break;	// don't break if inside a quoted string
			if (text[i] == '\n')
				break;
		}
		
		
		memcpy (line, text, i);
		line[i] = 0;
		
		// delete the text from the command buffer and move remaining commands down
		// this is necessary because commands (exec, alias) can insert data at the
		// beginning of the text buffer

		if (i == cmd_text.cursize)
			cmd_text.cursize = 0;
		else
		{
			i++;
			cmd_text.cursize -= i;
			Q_memcpy (text, text + i, cmd_text.cursize);
		}
		
		// execute the command line
		Cmd_ExecuteString (line, src_command);
		
		if (cmd_wait)
		{
			// skip out while text still remains in buffer, leaving it
			// for next frame
			cmd_wait = false;
			break;
		}
	}
}

/*
==============================================================================

						SCRIPT COMMANDS

==============================================================================
*/

///////////////////////////////////////////////////////////////////////////
/// @brief	Adds command line parameters as script statements
///			Commands lead with a +, and continue until a - or another +
///			quake +prog jctest.qp +cmd amlev1
///			quake -nosound +cmd amlev1
///////////////////////////////////////////////////////////////////////////
void Cmd_StuffCmds_f (void)
{
	int		i, j;
	int		s;
	char	*text, *build, c;
	
	if (Cmd_Argc() != 1)
	{
		Con_Printf ("stuffcmds : execute command line parameters\n");
		return;
	}
	
// build the combined string to parse from
	s = 0;
	for (i = 1 ; i < com_argc ; i++)
	{
		if (!com_argv[i])
			continue;		// NEXTSTEP nulls out -NXHost
		s += Q_strlen (com_argv[i]) + 1;
	}
	if (!s)
		return;
		
	text = (char *)Z_Malloc (s + 1);
	text[0] = 0;
	for (i = 1 ; i < com_argc ; i++)
	{
		if (!com_argv[i])
			continue;		// NEXTSTEP nulls out -NXHost
		Q_strcat (text, com_argv[i]);
		if (i != com_argc - 1)
			Q_strcat (text, " ");
	}
	
// pull out the commands
	build = (char *)Z_Malloc (s + 1);
	build[0] = 0;
	
	for (i = 0 ; i < s - 1 ; i++)
	{
		if (text[i] == '+')
		{
			i++;
			
			for (j = i ; (text[j] != '+') && (text[j] != '-') && (text[j] != 0) ; j++)
				;
				
			c = text[j];
			text[j] = 0;
			
			Q_strcat (build, text + i);
			Q_strcat (build, "\n");
			text[j] = c;
			i = j - 1;
		}
	}
	
	if (build[0])
		Cbuf_InsertText (build);
		
	Z_Free (text);
	Z_Free (build);
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Execute files @e console @e command
///			
///			
///////////////////////////////////////////////////////////////////////////
void Cmd_Exec_f (void)
{
	char *f;
	int	 mark;
	
	if (Cmd_Argc() != 2)
	{
		Con_Printf ("exec <filename> : execute a script file\n");
		return;
	}
	
	mark = Hunk_LowMark();
	f = (char *) COM_LoadHunkFile (Cmd_Argv (1));
	if (!f)
	{
		Con_Printf ("couldn't exec %s\n", Cmd_Argv (1));
		return;
	}

	Con_Printf ("execing %s\n", Cmd_Argv (1));
	
	Cbuf_InsertText (f);
	Hunk_FreeToLowMark (mark);
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Prints the rest of the line to the console.
///////////////////////////////////////////////////////////////////////////
void Cmd_Echo_f (void)
{
	int i;
	
	for (i = 1 ; i < Cmd_Argc() ; i++)
		Con_Printf ("%s ", Cmd_Argv (i));
	Con_Printf ("\n");
}

///////////////////////////////////////////////////////////////////////////
/// @brief	Creates a new command that executes a command string
///			(possibly ; seperated)
///////////////////////////////////////////////////////////////////////////
char *CopyString (char *in)
{
	char	*out;
	
	out = Z_Malloc (strlen (in) + 1);
	strcpy (out, in);
	return out;
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Registers an alias for an already existing command.
///////////////////////////////////////////////////////////////////////////
void Cmd_Alias_f (void)
{
	cmdalias_t	*a;
	char		cmd[1024];
	int			i, c;
	char		*s;
	
	if (Cmd_Argc() == 1)
	{
		Con_Printf ("Current alias commands:\n");
		for (a = cmd_alias ; a ; a = a->next)
			Con_Printf ("%s : %s\n", a->name, a->value);
		return;
	}
	
	s = Cmd_Argv (1);
	if (strlen (s) >= MAX_ALIAS_NAME)
	{
		Con_Printf ("Alias name is too long\n");
		return;
	}
	
	// if the alias already exists, reuse it
	for (a = cmd_alias ; a ; a = a->next)
	{
		if (!strcmp (s, a->name))
		{
			Z_Free (a->value);
			break;
		}
	}
	
	if (!a)
	{
		a = (cmdalias_t *) Z_Malloc (sizeof (cmdalias_t));
		a->next = cmd_alias;
		cmd_alias = a;
	}
	strcpy (a->name, s);
	
// copy the rest of the command line
	cmd[0] = 0;		// start out with a null string
	c = Cmd_Argc();
	for (i = 2 ; i < c ; i++)
	{
		strcat (cmd, Cmd_Argv (i));
		if (i != c)
			strcat (cmd, " ");
	}
	strcat (cmd, "\n");
	
	a->value = CopyString (cmd);
}

/*
=============================================================================

					COMMAND EXECUTION

=============================================================================
*/

typedef struct cmd_function_s
{
	struct cmd_function_s	*next;
	char					*name;
	xcommand_t				function;
} cmd_function_t;

#define	MAX_ARGS		80

static	int			cmd_argc;				///< Command argument count.
static	char		*cmd_argv[MAX_ARGS];	///< Command argument vector
static	char		*cmd_null_string = "";	///< Command null string
static	char		*cmd_args = NULL;		///< Command argument ?

cmd_source_t		cmd_source;


static	cmd_function_t	*cmd_functions;		// possible commands to execute

///////////////////////////////////////////////////////////////////////////
/// @brief	Register our commands.
///////////////////////////////////////////////////////////////////////////
void Cmd_Init (void)
{
	Cmd_AddCommand ("stuffcmds", Cmd_StuffCmds_f);
	Cmd_AddCommand ("exec", Cmd_Exec_f);
	Cmd_AddCommand ("echo", Cmd_Echo_f);
	Cmd_AddCommand ("alias", Cmd_Alias_f);
	Cmd_AddCommand ("cmd", Cmd_ForwardToServer);
	Cmd_AddCommand ("wait", Cmd_Wait_f);
}

///////////////////////////////////////////////////////////////////////////
/// @brief	Returns the @e command @e argument @e count
/// @return @b cmd_argc
///////////////////////////////////////////////////////////////////////////
int Cmd_Argc (void)
{
	return cmd_argc;
}

///////////////////////////////////////////////////////////////////////////
/// @brief	Return the nth argument, or a NULL string, if @e n is bigger
///			than the amount of command arguments.
/// @param	n The nth argument to return.
/// @return	The nth argument.
/// @return A null string, if n is bigger then the amount of Command Arguments
///////////////////////////////////////////////////////////////////////////
char *Cmd_Argv (int n)
{
	if ((unsigned) n >= cmd_argc)
		return cmd_null_string;
	else
		return cmd_argv[n];
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Returns @b cmd_args
/// 
/// The functions that execute commands get their parameters with these
/// functions. Cmd_Argv () will return an empty string, not a NULL
/// if arg > argc, so string operations are allways safe
/// @return @b cmd_args
///////////////////////////////////////////////////////////////////////////
char *Cmd_Args (void)
{
	return cmd_args;
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Parses the given string into command line tokens.
///			Takes a null terminated string.  Does not need to be /n terminated.
///			breaks the string up into arg tokens.
/// @param	text The text to tokenize
/// @return void
///////////////////////////////////////////////////////////////////////////
void Cmd_TokenizeString (char *text)
{
	int i;
	
	// clear the args from the last string
	for (i = 0 ; i < cmd_argc ; i++)
		Z_Free (cmd_argv[i]);
		
	cmd_argc = 0;
	cmd_args = NULL;
	
	while (1)
	{
	// skip whitespace up to a /n
		while (*text && *text <= ' ' && *text != '\n')
		{
			text++;
		}
		
		if (*text == '\n')
		{
			// a newline seperates commands in the buffer
			text++;
			break;
		}
		
		if (!*text)
			return;
			
		if (cmd_argc == 1)
			cmd_args = text;
			
		text = COM_Parse (text);
		if (!text)
			return;
			
		if (cmd_argc < MAX_ARGS)
		{
			cmd_argv[cmd_argc] = Z_Malloc (Q_strlen (com_token) + 1);
			Q_strcpy (cmd_argv[cmd_argc], com_token);
			cmd_argc++;
		}
	}
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Called by the init functions of other parts of the program to
///			register commands and functions to call for them.
///			The cmd_name is referenced later, so it should not be in temp memory
///	@param	cmd_name 
///////////////////////////////////////////////////////////////////////////
void Cmd_AddCommand (char *cmd_name, xcommand_t function)
{
	cmd_function_t	*cmd;
	
	if (host_initialized)	// because hunk allocation would get stomped
		Sys_Error ("Cmd_AddCommand after host_initialized");
		
	// fail if the command is a variable name
	if (Cvar_VariableString (cmd_name) [0])
	{
		Con_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
		return;
	}
	
	// fail if the command already exists
	for (cmd = cmd_functions ; cmd ; cmd = cmd->next)
	{
		if (!Q_strcmp (cmd_name, cmd->name))
		{
			Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
			return;
		}
	}
	
	cmd = (cmd_function_t *) Hunk_Alloc (sizeof (cmd_function_t));
	cmd->name = cmd_name;
	cmd->function = function;
	cmd->next = cmd_functions;
	cmd_functions = cmd;
}

///////////////////////////////////////////////////////////////////////////
/// @brief	Check if a command exists
/// @param	cmd_name The command to check for
/// @return	@b True if the command exists.
/// @return	@b False if the command @b doesn't exists.
///////////////////////////////////////////////////////////////////////////
bool Cmd_Exists (char *cmd_name)
{
	cmd_function_t *cmd;
	
	for (cmd = cmd_functions ; cmd ; cmd = cmd->next)
	{
		if (!Q_strcmp (cmd_name, cmd->name))
			return true;
	}
	
	return false;
}



///////////////////////////////////////////////////////////////////////////
/// @brief	Input a part of a command, and this funtion will try to
///			autocomplete it.
/// @param	partial The command to autocomplete
/// @return	@b NULL if @b partial's length is 0.
/// @return	The completed command.
/// @return @b NULL if command not found.
///////////////////////////////////////////////////////////////////////////
char *Cmd_CompleteCommand (char *partial)
{
	cmd_function_t	*cmd;
	int				len;
	
	len = Q_strlen (partial);
	
	if (!len)
		return NULL;
		
	// check functions
	for (cmd = cmd_functions ; cmd ; cmd = cmd->next)
		if (!Q_strncmp (partial, cmd->name, len))
			return cmd->name;
			
	return NULL;
}

///////////////////////////////////////////////////////////////////////////
/// @brief	A complete command line has been parsed, so try to execute it
/// 
/// Parses a single line of text into arguments and tries to execute it.
/// The text can come from the command buffer, a remote client, or stdin.
/// @todo	FIXME: lookupnoadd the token to speed search?
///////////////////////////////////////////////////////////////////////////
void Cmd_ExecuteString (char *text, cmd_source_t src)
{
	cmd_function_t	*cmd;
	cmdalias_t		*a;
	
	cmd_source = src;
	Cmd_TokenizeString (text);
	
	// execute the command line
	if (!Cmd_Argc())
		return;		// no tokens
		
	// check functions
	for (cmd = cmd_functions ; cmd ; cmd = cmd->next)
	{
		if (!Q_strcasecmp (cmd_argv[0], cmd->name))
		{
			cmd->function();
			return;
		}
	}
	
	// check alias
	for (a = cmd_alias ; a ; a = a->next)
	{
		if (!Q_strcasecmp (cmd_argv[0], a->name))
		{
			Cbuf_InsertText (a->value);
			return;
		}
	}
	
	// check cvars
	if (!Cvar_Command())
		Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv (0));
		
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Sends the entire command line over to the server
/// 
/// Adds the current command line as a clc_stringcmd to the client message.
/// Things like godmode, noclip, etc, are commands directed to the server,
/// so when they are typed in at the console, they will need to be
/// forwarded.
///////////////////////////////////////////////////////////////////////////
void Cmd_ForwardToServer (void)
{
	if (cls.state != ca_connected)
	{
		Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv (0));
		return;
	}
	
	if (cls.demoplayback)
		return;		// not really connected
		
	MSG_WriteByte (&cls.message, clc_stringcmd);

	if (Q_strcasecmp (Cmd_Argv (0), "cmd") != 0)
	{
		SZ_Print (&cls.message, Cmd_Argv (0));
		SZ_Print (&cls.message, " ");
	}

	if (Cmd_Argc() > 1)
		SZ_Print (&cls.message, Cmd_Args());
	else
		SZ_Print (&cls.message, "\n");
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Returns the position (1 to argc-1) in the command's argument
///			list where the given parameter apears, or 0 if not present
///////////////////////////////////////////////////////////////////////////
int Cmd_CheckParm (char *parm)
{
	int i;
	
	if (!parm)
		Sys_Error ("Cmd_CheckParm: NULL");
		
	for (i = 1; i < Cmd_Argc(); i++)
		if (! Q_strcasecmp (parm, Cmd_Argv (i)))
			return i;
			
	return 0;
}
