/*
Copyright (C) 1997-2001 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.

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

#include "qcommon.h"
#include "q_list.h"

#define Cmd_Malloc( size )		        Z_TagMalloc( size, TAG_CMD )
#define Cmd_CopyString( string )		Z_TagCopyString( string, TAG_CMD )

cmdAPI_t	cmd;

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

						COMMAND BUFFER

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

byte			cmd_buffer_text[CMD_BUFFER_SIZE];
cmdbuf_t		cmd_buffer;

/*
============
Cmd_Wait_f

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"
============
*/
static void Cmd_Wait_f( void ) {
	if( Cmd_Argc() > 1 ) {
		cmd_buffer.waitCount = atoi( Cmd_Argv( 1 ) );
	} else {
		cmd_buffer.waitCount = 1;
	}
}

/*
============
Cbuf_Init
============
*/
void Cbuf_Init( void ) {
	SZ_Init( &cmd_buffer.text, cmd_buffer_text, sizeof( cmd_buffer_text ) );
	cmd_buffer.exec = Cmd_ExecuteString;
}

/*
============
Cbuf_AddText

Adds command text at the end of the buffer
============
*/
void Cbuf_AddTextEx( cmdbuf_t *buf, const char *text ) {
	int		l;
	
	l = strlen( text );

	if( buf->text.cursize + l >= buf->text.maxsize ) {
		Com_WPrintf( "Cbuf_AddText: overflow\n" );
		return;
	}
	SZ_Write( &buf->text, text, l );
}


/*
============
Cbuf_InsertText

Adds command text immediately after the current command.
Adds a \n to the text.
============
*/
void Cbuf_InsertTextEx( cmdbuf_t *buf, const char *text ) {
	char	temp[CMD_BUFFER_SIZE];
	int		templen, l;

	if( *text == 0 ) {
		return;
	}

// copy off any commands still remaining in the exec buffer
	templen = buf->text.cursize;
	if( templen ) {
		memcpy( temp, buf->text.data, templen );
		SZ_Clear( &buf->text );
	}
		
// add the entire text of the file
	l = strlen( text );
	if( templen + l >= buf->text.maxsize ) {
		Com_WPrintf( "Cbuf_InsertText: overflow\n" );
		return;
	}

	SZ_Write( &buf->text, text, l );

// add '\n' to the text
	if( text[l - 1] != '\n' ) {
		buf->text.data[buf->text.cursize++] = '\n';
	}
	
// add the copied off data
	if( templen ) {
		SZ_Write( &buf->text, temp, templen );
	}
}

/*
============
Cbuf_ExecuteText
============
*/
void Cbuf_ExecuteText( cbufExecWhen_t exec_when, const char *text ) {
	switch( exec_when ) {
	case EXEC_NOW:
		Cmd_ExecuteString( text );
		break;
	case EXEC_INSERT:
		Cbuf_InsertText( text );
		break;
	case EXEC_APPEND:
		Cbuf_AddText( text );
		break;
	default:
		Com_Error( ERR_FATAL, "Cbuf_ExecuteText: bad exec_when" );
	}
}

/*
============
Cbuf_Execute
============
*/
void Cbuf_ExecuteEx( cmdbuf_t *buf ) {
	int		i, bytes;
	char	*text;
	char	line[MAX_STRING_CHARS];
	int		quotes;

	while( buf->text.cursize ) {
		if( buf->waitCount > 0 ) {
			// skip out while text still remains in buffer, leaving it
			// for next frame
			buf->waitCount--;
            return;
		}

// find a \n or ; line break
		text = ( char * )buf->text.data;

		quotes = 0;
		for( i = 0; i < buf->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;
		}

        bytes = i;

        // strip trailing CR
        if( bytes && text[ bytes - 1 ] == '\r' ) {
            bytes--;
        }

		// check for overflow
		if( bytes > sizeof( line ) - 1 ) {
			bytes = sizeof( line ) - 1;
		}

		memcpy( line, text, bytes );
		line[bytes] = 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 == buf->text.cursize ) {
			buf->text.cursize = 0;
		} else {
			i++;
			buf->text.cursize -= i;
			memmove( text, text + i, buf->text.cursize );
		}

// execute the command line
		buf->exec( line );

	}

	buf->aliasCount = 0;		// don't allow infinite alias loops
}

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

						SCRIPT COMMANDS

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

typedef struct cmdalias_s {
	listElem_t elem[2];

	char	*value;
	char	name[1];
} cmdalias_t;

#define ALIAS_HASH_SIZE	64

static list_t		cmd_alias;
static list_t		cmd_aliasHash[ALIAS_HASH_SIZE];

/*
===============
Cmd_AliasFind
===============
*/
cmdalias_t *Cmd_AliasFind( const char *name ) {
	uint32 hash;
	cmdalias_t *alias;
	listElem_t *elem;

	hash = Com_HashString( name, ALIAS_HASH_SIZE );
	for( elem = cmd_aliasHash[hash].first; elem; elem = elem->next ) {
		alias = ( cmdalias_t * )( elem - 1 );
		if( !strcmp( name, alias->name ) ) {
			return alias;
		}
	}

	return NULL;
}

char *Cmd_AliasCommand( const char *name ) {
	cmdalias_t	*a;

	a = Cmd_AliasFind( name );
	if( !a ) {
		return NULL;
	}

	return a->value;
}

void Cmd_AliasSet( const char *name, const char *cmd ) {
	cmdalias_t	*a;
	uint32		hash;

	// if the alias already exists, reuse it
	a = Cmd_AliasFind( name );
	if( a ) {
		Z_Free( a->value );
		a->value = Cmd_CopyString( cmd );
		return;
	}

	a = Cmd_Malloc( sizeof( cmdalias_t ) + strlen( name ) );
	strcpy( a->name, name );
	a->value = Cmd_CopyString( cmd );

	List_Append( &cmd_alias, &a->elem[0] );

	hash = Com_HashString( name, ALIAS_HASH_SIZE );
	List_Append( &cmd_aliasHash[hash], &a->elem[1] );
}

/*
===============
Cmd_Alias_f

Creates a new command that executes a command string (possibly ; seperated)
===============
*/
void Cmd_Alias_f( void ) {
	cmdalias_t	*a;
	listElem_t	*elem;
	char		*s, *cmd;

	if( Cmd_Argc() < 2 ) {
		if( !cmd_alias.first ) {
			Com_Printf( "No alias commands registered\n" );
			return;
		}
		Com_Printf( "Current alias commands:\n" );
		for( elem = cmd_alias.first ; elem; elem = elem->next ) {
			a = ( cmdalias_t * )elem;
			Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value );
		}
		return;
	}

	s = Cmd_Argv( 1 );
	if( Cmd_Exists( s ) ) {
		Com_Printf( "\"%s\" already defined as a command\n", s );
		return;
	}

	if( Cvar_Exists( s ) ) {
		Com_Printf( "\"%s\" already defined as a cvar\n", s );
		return;
	}

	if( Cmd_Argc() < 3 ) {
		a = Cmd_AliasFind( s );
		if( a ) {
			Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value );
		} else {
			Com_Printf( "\"%s\" is undefined\n", s );
		}
		return;
	}

	// copy the rest of the command line
	cmd = Cmd_ArgsFrom( 2 );
	Cmd_AliasSet( s, cmd );
}

static void Cmd_UnAlias_f( void ) {
	char *s;
	cmdalias_t	*a;
	listElem_t *elem, *next;
	uint32 hash;

	if( Cmd_CheckParam( "-h", "--help" ) ) {
usage:
		Com_Printf( "Usage: %s [-h] [-a] [alias_name]\n"
			"-h|--help    : display this message\n"
			"-a|--all     : delete everything\n"
			"Either -a or alias_name should be given\n", Cmd_Argv( 0 ) );
		return;
	}

	if( Cmd_CheckParam( "a", "all" ) ) {
		for( elem = cmd_alias.first; elem; elem = next ) {
			next = elem->next;
			a = ( cmdalias_t * )elem;
			Z_Free( a->value );
			Z_Free( a );
		}
		for( hash = 0; hash < ALIAS_HASH_SIZE; hash++ ) {
			List_Clear( &cmd_aliasHash[hash] );
		}
		List_Clear( &cmd_alias );
		Com_Printf( "Removed all aliases\n" );
		return;
	}

	if( Cmd_Argc() < 2 ) {
		goto usage;
	}
	s = Cmd_Argv( 1 );
	a = Cmd_AliasFind( s );
	if( !a ) {
		Com_Printf( "\"%s\" is undefined\n", s );
		return;
	}

	Z_Free( a->value );

	List_DeleteElem( &a->elem[0] );
	List_DeleteElem( &a->elem[1] );

	Z_Free( a );
}

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

					MESSAGE TRIGGERS

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

typedef struct cmd_trigger_s {
	listElem_t	elem;

	trigChannel_t	chan;
	char		*match;
	char		*command;
} cmd_trigger_t;

static list_t	cmd_triggers;

static struct {
    const char *name;
    const char *desc;
    trigChannel_t chan;
} triggers[] = {
    { "client", "misc client events", TRIG_CLIENT_SYSTEM },
    { "chat", "client chat messages", TRIG_CLIENT_CHAT },
    { "print", "client print messages (default)", TRIG_CLIENT_PRINT },
    { "cprint", "client centerprint messages", TRIG_CLIENT_CENTERPRINT },
    { "server", "misc server events", TRIG_SERVER_SYSTEM },
    { "bprint", "game DLL broadcast messages", TRIG_SERVER_BPRINT },
    { "dprint", "game DLL console messages", TRIG_SERVER_DPRINT },
    { NULL, 0 }
};

static const char *chan2string( trigChannel_t chan ) {
    int i;

    for( i = 0; triggers[i].name; i++ ) {
        if( triggers[i].chan == chan ) {
            return triggers[i].name;
        }
    }
    return "unknown";
}

/*
============
Cmd_Trigger_f
============
*/
static void Cmd_Trigger_f( void ) {
	cmd_trigger_t *trigger;
	listElem_t *elem;
	char *command, *match, *name;
	int cmdLength, matchLength;
	trigChannel_t chan;
    int i;

	if( Cmd_Argc() == 1 ) {
		if( !cmd_triggers.first ) {
			Com_Printf( "No triggers registered\n" );
			return;
		}
		Com_Printf( "Current message triggers:\n" );
		for( elem = cmd_triggers.first; elem; elem = elem->next ) {
			trigger = ( cmd_trigger_t * )elem;
			Com_Printf( "\"%s\" = \"%s\" [%s]\n",
				trigger->match, trigger->command,
                    chan2string( trigger->chan ) );
		}
		return;
	}

	if( Cmd_Argc() < 3 ) {
usage:
		Com_Printf( "Usage: %s <command> <match> [channel]\n", Cmd_Argv( 0 ) );
		Com_Printf( "Valid channels:\n" );
		for( i = 0; triggers[i].name; i++ ) {
			Com_Printf( "%6s - %s\n", triggers[i].name, triggers[i].desc );
		}
		return;
	}

	command = Cmd_Argv( 1 );
	match = Cmd_Argv( 2 );

	chan = TRIG_CLIENT_PRINT;
	if( Cmd_Argc() > 3 ) {
		name = Cmd_Argv( 3 );
		for( i = 0; triggers[i].name; i++ ) {
			if( !strcmp( name, triggers[i].name ) ) {
                chan = triggers[i].chan;
				break;
			}
		}
		if( !triggers[i].name ) {
			Com_Printf( "Unknown channel '%s'\n", name );
            goto usage;
		}
	}

	// don't create the same trigger twice
	for( elem = cmd_triggers.first; elem; elem = elem->next ) {
		trigger = ( cmd_trigger_t * )elem;
		if( trigger->chan == chan &&
			!strcmp( trigger->command, command ) &&
			!strcmp( trigger->match, match ) )
		{
			return;
		}
	}

	cmdLength = strlen( command ) + 1;
	matchLength = strlen( match ) + 1;

	trigger = Cmd_Malloc( sizeof( cmd_trigger_t ) +
            cmdLength + matchLength );
	trigger->chan = chan;
	trigger->command = ( char * )( trigger + 1 );
	trigger->match = trigger->command + cmdLength;
	List_Append( &cmd_triggers, trigger );

	strcpy( trigger->command, command );
	strcpy( trigger->match, match );
}

/*
============
Cmd_UnTrigger_f
============
*/
static void Cmd_UnTrigger_f( void ) {
	cmd_trigger_t *trigger;
	listElem_t *elem, *next;
	char *command, *match;

	if( Cmd_CheckParam( "-h", "--help" ) ) {
usage:
		Com_Printf( "Usage: %s [-h] [-a] [-c <command>] [-m <match>]\n"
			"-h|--help    : display this message\n"
			"-a|--all     : delete everything\n"
			"-c|--command : delete by command\n"
			"-m|--match   : delete by match\n"
			"Either -a or combination of -c and -m should be given\n",
                Cmd_Argv( 0 ) );
		return;
	}

	if( Cmd_CheckParam( "a", "all" ) ) {
		for( elem = cmd_triggers.first; elem; elem = next ) {
			next = elem->next;
			Z_Free( elem );
		}
		List_Clear( &cmd_triggers );
		Com_Printf( "Removed all triggers\n" );
		return;
	}

	command = Cmd_FindParam( "c", "command" );
	match = Cmd_FindParam( "m", "match" );
	if( !command && !match ) {
		goto usage;
	}

	for( elem = cmd_triggers.first; elem; elem = next ) {
		next = elem->next;
		trigger = ( cmd_trigger_t * )elem;
		if( command && strcmp( trigger->command, command ) ) {
			continue;
		}
		if( match && strcmp( trigger->match, match ) ) {
			continue;
		}

		Com_Printf( "Removed \"%s\" = \"%s\" [%s]\n",
			trigger->match, trigger->command,
                chan2string( trigger->chan ) );

		List_DeleteElem( elem );
		Z_Free( elem );
	}
}

/*
============
Cmd_ExecTrigger
============
*/
void Cmd_ExecTrigger( trigChannel_t chan, const char *string ) {
	cmd_trigger_t *trigger;
	listElem_t *elem;
    char *text;

	// execute matching triggers
	for( elem = cmd_triggers.first; elem; elem = elem->next ) {
		trigger = ( cmd_trigger_t * )elem;
		if( trigger->chan != chan ) {
			continue;
		}
		text = Cmd_MacroExpandString( trigger->match, qfalse );
		if( text && Com_WildCmp( text, string, qtrue ) ) {
			Cbuf_AddText( trigger->command );
			Cbuf_AddText( "\n" );
		}
	}
}

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

					MACRO EXECUTION

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

typedef struct cmd_macro_s {
	struct cmd_macro_s	*next;
	struct cmd_macro_s	*hashNext;

	const char		*name;
	xmacro_t		function;
} cmd_macro_t;

#define MACRO_HASH_SIZE	64

static cmd_macro_t	*cmd_macros;
static cmd_macro_t	*cmd_macroHash[MACRO_HASH_SIZE];

/*
============
Cmd_MacroFind
============
*/
static cmd_macro_t *Cmd_MacroFind( const char *name ) {
	cmd_macro_t *macro;
	int hash;

	hash = Com_HashString( name, MACRO_HASH_SIZE );
	for( macro=cmd_macroHash[hash] ; macro ; macro=macro->hashNext ) {
		if( !strcmp( macro->name, name ) ) {
			return macro;
		}
	}

	return NULL;
}

xmacro_t Cmd_FindMacroFunction( const char *name ) {
	cmd_macro_t *macro;

	macro = Cmd_MacroFind( name );
	if( !macro ) {
		return NULL;
	}

	return macro->function;
}

/*
============
Cmd_AddMacro
============
*/
void Cmd_AddMacro( const char *name, xmacro_t function ) {
	cmd_macro_t	*macro;
	int hash;

	if( Cvar_Exists( name ) ) {
		Com_WPrintf( "Cmd_AddMacro: %s already defined as a cvar\n", name );
		return;
	}
	
// fail if the macro already exists
	if( Cmd_MacroFind( name ) ) {
		Com_WPrintf( "Cmd_AddMacro: %s already defined\n", name );
		return;
	}

	hash = Com_HashString( name, MACRO_HASH_SIZE );

	macro = Cmd_Malloc( sizeof( cmd_macro_t ) );
	macro->name = name;
	macro->function = function;
	macro->next = cmd_macros;
	cmd_macros = macro;
	macro->hashNext = cmd_macroHash[hash];
	cmd_macroHash[hash] = macro;
}




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

					COMMAND EXECUTION

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

#define CMD_HASH_SIZE	128

typedef struct cmd_function_s {
	listElem_t		elem[2];

	xcommand_t				function;
	xgenerator_t			generator;
	const char				*name;
} cmd_function_t;

static	list_t		cmd_functions;		/* possible commands to execute */
static	list_t		cmd_hash[CMD_HASH_SIZE];

static	int			cmd_argc;
static	char		*cmd_argv[MAX_STRING_TOKENS];
static	char		*cmd_null_string = "";

/* complete command string, quotes preserved */
static	char		cmd_args[MAX_STRING_CHARS];

/* offsets of individual tokens in cmd_args */
static	int			cmd_offsets[MAX_STRING_TOKENS];

/* sequence of NULL-terminated tokens, each cmd_argv[] points here */
static	char		cmd_data[MAX_STRING_CHARS];

int Cmd_ArgOffset( int arg ) {
	if( arg < 0 ) {
		return 0;
	}
	if( arg >= cmd_argc ) {
		return strlen( cmd_args );
	}
	return cmd_offsets[arg];	
}

int Cmd_FindArgForOffset( int offset ) {
	int i;
 
	for( i = 1; i < cmd_argc; i++ ) {
		if( offset < cmd_offsets[i] ) {
			return i - 1;
		}
	}
	return i - 1;	
}

/*
============
Cmd_Argc
============
*/
int Cmd_Argc( void ) {
	return cmd_argc;
}

/*
============
Cmd_Argv
============
*/
char *Cmd_Argv( int arg ) {
	if( arg < 0 || arg >= cmd_argc ) {
		return cmd_null_string;
	}
	return cmd_argv[arg];	
}

/*
============
Cmd_ArgvBuffer
============
*/
void Cmd_ArgvBuffer( int arg, char *buffer, int bufferSize ) {
	char *s;

	if( arg < 0 || arg >= cmd_argc ) {
		s = cmd_null_string;
	} else {
		s = cmd_argv[arg];
	}

	Q_strncpyz( buffer, s, bufferSize );	
}


/*
============
Cmd_Args

Returns a single string containing argv(1) to argv(argc()-1)
============
*/
char *Cmd_Args( void ) {
	static char args[MAX_STRING_CHARS];
	int i;

	if( cmd_argc < 2 ) {
		return cmd_null_string;
	}

	args[0] = 0;
	for( i = 1; i < cmd_argc - 1; i++ ) {
		strcat( args, cmd_argv[i] );
		strcat( args, " " );
	}
	strcat( args, cmd_argv[i] );

	return args;
}

char *Cmd_RawArgs( void ) {
	if( cmd_argc < 2 ) {
		return cmd_null_string;
	}
	return cmd_args + cmd_offsets[1];
}

char *Cmd_RawString( void ) {
	return cmd_args;
}



/*
============
Cmd_ArgsBuffer
============
*/
void Cmd_ArgsBuffer( char *buffer, int bufferSize ) {
	Q_strncpyz( buffer, Cmd_Args(), bufferSize );	
}

/*
============
Cmd_ArgsFrom

Returns a single string containing argv(1) to argv(from-1)
============
*/
char *Cmd_ArgsFrom( int from ) {
	static char args[MAX_STRING_CHARS];
	int i;

	if( from < 0 || from >= cmd_argc ) {
		return cmd_null_string;
	}

	args[0] = 0;
	for( i = from; i < cmd_argc - 1; i++ ) {
		strcat( args, cmd_argv[i] );
		strcat( args, " " );
	}
	strcat( args, cmd_argv[i] );

	return args;
}

char *Cmd_RawArgsFrom( int from ) {
	int offset;

	if( from < 0 || from >= cmd_argc ) {
		return cmd_null_string;
	}

	offset = cmd_offsets[from];

	return cmd_args + offset;
}

/*
============
Cmd_EnumParam
============
*/
int Cmd_EnumParam( int start, const char *sp, const char *lp ) {
	int i;
	char *s;
	
	if( start < 0 || start >= cmd_argc ) {
		return 0;
	}
	for( i = start; i < cmd_argc; i++ ) {
		if( *( s = cmd_argv[i] ) == '-' ) {
			if( *( ++s ) == '-' ) {
				if( !strcmp( s + 1, lp ) ) {
					return i;
				}
			} else if( !strcmp( s, sp ) ) {
				return i;
			}
		}
	}
	
	return 0;
}

/*
============
Cmd_CheckParam
============
*/
int Cmd_CheckParam( const char *sp, const char *lp ) {
	return Cmd_EnumParam( 1, sp, lp );
}

/*
============
Cmd_FindParam
============
*/
char *Cmd_FindParam( const char *sp, const char *lp ) {
	int i;

	if( ( i = Cmd_EnumParam( 1, sp, lp ) ) && ++i != cmd_argc ) {
		return cmd_argv[i];
	}

	return NULL;
}

/*
======================
Cmd_MacroExpandString
======================
*/
char *Cmd_MacroExpandString( const char *text, qboolean aliasHack ) {
	int		i, j, count, len;
	qboolean	inquote;
	char	*scan, *start;
	static	char	expanded[MAX_STRING_CHARS];
	char	temporary[MAX_STRING_CHARS];
	char	buffer[MAX_TOKEN_CHARS];
	char	*token;
	cmd_macro_t *macro;
	cvar_t	*var;
	qboolean	rescan;

	len = strlen( text );
	if( len >= MAX_STRING_CHARS ) {
		Com_Printf( "Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS );
		return NULL;
	}

	strcpy( expanded, text );
	scan = expanded;

	inquote = qfalse;
	count = 0;

	for( i = 0; i < len; i++ ) {
		if( !scan[i] ) {
			break;
		}
		if( scan[i] == '"' ) {
			inquote ^= 1;
		}
		if( inquote ) {
			continue;	/* don't expand inside quotes */
		}
		if( scan[i] != '$' ) {
			continue;
		}
		
		/* scan out the complete macro */
		start = scan + i + 1;

		if( !start[0] ) {
			break;	/* end of string */
		}

		/* allow escape syntax */
		if( i && scan[i-1] == '\\' ) {
			memmove( scan + i - 1, scan + i, len - i + 1 );
			i--;
			continue;
		}

		/* fix from jitspoe - skip leading spaces */
		while( *start && *start <= 32 ) {
			start++;
		}

		token = temporary;

		if( *start == '{' ) {
			/* allow ${variable} syntax */
			start++;
            if( *start == '$' ) {
                start++;
            }
			while( *start ) {
				if( *start == '}' ) {
					start++;
					break;
				}
				*token++ = *start++;
			}
		} else {
			/* parse single word */
			while( *start > 32 ) {
				*token++ = *start++;
			}
		}
		
		*token = 0;

		if( token == temporary ) {
			continue;
		}

		rescan = qfalse;
		
		if( aliasHack ) {
			/* expand positional parameters only */
            if( temporary[1]  ) {
                continue;
            }
            if( Q_isdigit( temporary[0] ) ) {
                token = Cmd_Argv( temporary[0] - '0' );
            } else if( temporary[0] == '@' ) {
                token = Cmd_Args();
            } else {
                continue;
            }
		} else {
			/* check for macros first */
			macro = Cmd_MacroFind( temporary );
			if( macro ) {
				macro->function( buffer, sizeof( buffer ) );
				token = buffer;
            } else {
				var = Cvar_FindVar( temporary );
				if( var && !( var->flags & CVAR_PRIVATE ) ) {
					token = var->string;
					rescan = qtrue;
				} else if( !strcmp( temporary, "qt" ) ) {
					token = "\"";
				} else if( !strcmp( temporary, "sc" ) ) {
					token = ";";
				} else {
					token = "";
				}
			}
		}

		j = strlen( token );
		len += j;
		if( len >= MAX_STRING_CHARS ) {
			Com_Printf( "Expanded line exceeded %i chars, discarded.\n",
                    MAX_STRING_CHARS );
			return NULL;
		}

		strncpy( temporary, scan, i );
		strcpy( temporary + i, token );
		strcpy( temporary + i + j, start );

		strcpy( expanded, temporary );
		scan = expanded;
		if( !rescan ) {
			i += j;
		}
		i--;

		if( ++count == 100 ) {
			Com_Printf( "Macro expansion loop, discarded.\n" );
			return NULL;
		}
	}

	if( inquote ) {
		Com_Printf( "Line has unmatched quote, discarded.\n" );
		return NULL;
	}

	return scan;
}

/*
============
Cmd_TokenizeString

Parses the given string into command line tokens.
$Cvars will be expanded unless they are in a quoted token
============
*/
void Cmd_TokenizeString( const char *text, qboolean macroExpand ) {
	int		i;
	char	*data, *start, *dest;

// clear the args from the last string
	for( i = 0; i < cmd_argc; i++ ) {
		cmd_argv[i] = NULL;
		cmd_offsets[i] = 0;
	}
		
	cmd_argc = 0;
	cmd_args[0] = 0;
	
    if( !text[0] ) {
        return;
    }

// macro expand the text
	if( macroExpand ) {
		text = Cmd_MacroExpandString( text, qfalse );
		if( !text ) {
			return;
		}
	}

	Q_strncpyz( cmd_args, text, sizeof( cmd_args ) );

	dest = cmd_data;
	start = data = cmd_args;
	do {
// skip whitespace up to a /n
		while( *data <= 32 ) {
			if( *data == 0 ) {
				return; // end of text
			}
			if( *data == '\n' ) {
				return; // a newline seperates commands in the buffer
			}
			data++;
		}

// skip C++ style comments
		if( data[0] == '/' && data[1] == '/' ) {
			return; // skipped to newline
		}

// add new argument
		cmd_offsets[cmd_argc] = data - start;
		cmd_argv[cmd_argc] = dest;
		cmd_argc++;

		if( *data == ';' ) {
			data++;
			*dest++ = ';';
			*dest++ = 0;
			continue;
		}

// parse quoted string
		if( *data == '\"' ) {
			data++;
			while( *data != '\"' ) {
				if( *data == 0 ) {
					return; // end of data
				}
				*dest++ = *data++;
			}
			data++;
			*dest++ = 0;
			continue;
		}

// parse reqular token
		while( *data > 32 ) {
			if( *data == '\"' ) {
				break;
			}
			if( *data == ';' ) {
				break;
			}
			if( data[0] == '/' && data[1] == '/' ) {
				break; // skipped to newline
			}
			*dest++ = *data++;
		}
		*dest++ = 0;

		if( *data == 0 ) {
			return; // end of text
		}
	} while( cmd_argc != MAX_STRING_TOKENS );
	
}

/*
============
Cmd_Find
============
*/
cmd_function_t *Cmd_Find( const char *name ) {
	cmd_function_t *cmd;
	listElem_t *elem;
	uint32 hash;

	hash = Com_HashString( name, CMD_HASH_SIZE );
	for( elem = cmd_hash[hash].first; elem; elem = elem->next ) {
		cmd = ( cmd_function_t * )( elem - 1 );
		if( !strcmp( cmd->name, name ) ) {
			return cmd;
		}
	}

	return NULL;
}

/*
============
Cmd_AddCommand
============
*/
void Cmd_AddCommandEx( const char *cmd_name, xcommand_t function,
        xgenerator_t generator )
{
	cmd_function_t	*cmd;
	uint32 hash;
	
// fail if the command is a variable name
	if( Cvar_FindVar( cmd_name ) ) {
		Com_WPrintf( "Cmd_AddCommand: %s already defined as a cvar\n",
            cmd_name );
		return;
	}
	
// fail if the command already exists
    cmd = Cmd_Find( cmd_name );
	if( cmd ) {
        Com_WPrintf( "Cmd_AddCommand: %s already defined\n", cmd_name );
        return;
	}

    cmd = Cmd_Malloc( sizeof( *cmd ) );
    cmd->name = cmd_name;
    cmd->function = function;
    cmd->generator = generator;

    List_Append( &cmd_functions, &cmd->elem[0] );

    hash = Com_HashString( cmd_name, CMD_HASH_SIZE );
    List_Append( &cmd_hash[hash], &cmd->elem[1] );

}

void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
	Cmd_AddCommandEx( cmd_name, function, NULL );
}

/*
============
Cmd_RemoveCommand

Can't just free the command since lua still may have a pointer to it.
============
*/
void Cmd_RemoveCommand( const char *name ) {
	cmd_function_t	*cmd;

	cmd = Cmd_Find( name );
	if( !cmd ) {
		Com_WPrintf( "Cmd_RemoveCommand: %s not added\n", name );
		return;
	}

    List_DeleteElem( &cmd->elem[0] );
    List_DeleteElem( &cmd->elem[1] );
    Z_Free( cmd );

}

/*
============
Cmd_Exists
============
*/
qboolean Cmd_Exists( const char *name ) {
	cmd_function_t *cmd;

    cmd = Cmd_Find( name ); 
	if( !cmd ) {
		return qfalse;
	}

	return qtrue;
}

xcommand_t Cmd_FindFunction( const char *name ) {
	cmd_function_t *cmd;

	cmd = Cmd_Find( name );
	if( !cmd ) {
		return NULL;
	}

	return cmd->function;
}

xgenerator_t Cmd_FindGenerator( const char *name ) {
	cmd_function_t *cmd;

	cmd = Cmd_Find( name );
	if( !cmd ) {
		return NULL;
	}

	return cmd->generator;
}

const char *Cmd_CommandGenerator( const char *partial, int state ) {
    static int length;
    static cmd_function_t *cmd;
    const char *name;
    
    if( !state ) {
        length = strlen( partial );
		cmd = ( cmd_function_t * )cmd_functions.first;
    }

    while( cmd ) {
        name = cmd->name;
		cmd = ( cmd_function_t * )cmd->elem[0].next;
		if( !strncmp( partial, name, length ) ) {
            return name;
        }
    }

    return NULL;
}

const char *Cmd_AliasGenerator( const char *partial, int state ) {
    static int length;
    static cmdalias_t *alias;
    const char *name;
    
    if( !state ) {
        length = strlen( partial );
		alias = ( cmdalias_t * )cmd_alias.first;
    }

    while( alias ) {
        name = alias->name;
        alias = ( cmdalias_t * )alias->elem[0].next;
		if( !strncmp( partial, name, length ) ) {
            return name;
        }
    }

    return NULL;
}

/*
============
Cmd_ExecuteString

A complete command line has been parsed, so try to execute it
============
*/
void Cmd_ExecuteString( const char *text ) {	
	cmd_function_t	*cmd;
	cmdalias_t		*a;

	Cmd_TokenizeString( text, qtrue );
			
	// execute the command line
	if( !cmd_argc ) {
		return;		// no tokens
	}

	// check functions
	cmd = Cmd_Find( cmd_argv[0] );
	if( cmd ) {
        if( cmd->function ) {
            cmd->function();
        } else {
            Cmd_ForwardToServer();
        }
		return;
	}

	// check aliases
	a = Cmd_AliasFind( cmd_argv[0] );
	if( a ) {
		if( ++cmd_buffer.aliasCount == ALIAS_LOOP_COUNT ) {
			Com_WPrintf( "Runaway alias loop\n" );
			return;
		}
		text = Cmd_MacroExpandString( a->value, qtrue );
		if( text ) {
			Cbuf_InsertText( text );
		}
		return;
	}
	
	// check cvars
	if( Cvar_Command() ) {
		return;
    }

	// send it as a server command if we are connected
	Cmd_ForwardToServer();
}

/*
===============
Cmd_Exec_f
===============
*/
static void Cmd_Exec_f( void ) {
	char	buffer[MAX_QPATH];
	char	*f, *ext;
	int		len;

	if( Cmd_Argc () != 2 ) {
		Com_Printf( "exec <filename> : execute a script file\n" );
		return;
	}

	Q_strncpyz( buffer, Cmd_Argv( 1 ), sizeof( buffer ) );

	len = FS_LoadFile( buffer, ( void ** )&f );
	if( !f ) {
		ext = COM_FileExtension( buffer );
		if( *ext == 0 ) {
			/* try with *.cfg extension */
			COM_DefaultExtension( buffer, ".cfg", sizeof( buffer ) );
			len = FS_LoadFile( buffer, ( void ** )&f );
		}

		if( !f ) {
			Com_Printf( "Couldn't exec %s\n", buffer );
			return;
		}
	}

	Com_Printf( "Execing %s\n", buffer );
	
	Cbuf_InsertText( f );

	FS_FreeFile( f );
}

static const char *Cmd_Exec_g( const char *partial, int state ) {
	return Com_FileNameGeneratorByFilter( "", "*.cfg", partial, qtrue, state );
}

/*
===============
Cmd_Echo_f

Just prints the rest of the line to the console
===============
*/
static void Cmd_Echo_f( void ) {
	Com_Printf( "%s\n", Cmd_RawArgs() );
}

static void Cmd_ColoredEcho_f( void ) {
	char buffer[MAX_STRING_CHARS];
	char *src, *dst;

	src = Cmd_RawArgs();
	dst = buffer;
	while( *src ) {
		if( src[0] == '^' && src[1] ) {
			if( src[1] == '^' ) {
				*dst++ = '^';
			} else {
				dst[0] = Q_COLOR_ESCAPE;
				dst[1] = src[1];
				dst += 2;
			}
			src += 2;
		} else {
			*dst++ = *src++;
		}
	}
	*dst = 0;
	Com_Printf( "%s\n", buffer );
}

/*
============
Cmd_List_f
============
*/
static void Cmd_List_f( void ) {
	cmd_function_t	*cmd;
	listElem_t *elem;
	int				i, total;
	char		*filter = NULL;

	if( cmd_argc > 1 ) {
		filter = cmd_argv[1];
	}

	i = total = 0;
	for( elem = cmd_functions.first; elem; elem = elem->next ) {
		cmd = ( cmd_function_t * )elem;
		total++;
		if( filter && !Com_WildCmp( filter, cmd->name, qfalse ) ) {
			continue;
		}
		Com_Printf( "%s\n", cmd->name );
		i++;
	}
	Com_Printf( "%i of %i commands\n", i, total );
}

/*
============
Cmd_MacroList_f
============
*/
static void Cmd_MacroList_f( void ) {
	cmd_macro_t	*macro;
	int				i, total;
	char		*filter = NULL;
	char		buffer[MAX_QPATH];

	if( cmd_argc > 1 ) {
		filter = cmd_argv[1];
	}

	i = 0;
	for( macro = cmd_macros, total = 0; macro; macro = macro->next, total++ ) {
		if( filter && !Com_WildCmp( filter, macro->name, qfalse ) ) {
			continue;
		}
		macro->function( buffer, sizeof( buffer ) );
		Com_Printf( "%-16s %s\n", macro->name, buffer );
		i++;
	}
	Com_Printf( "%i of %i macros\n", i, total );
}

/*
============
Cmd_FillAPI
============
*/
void Cmd_FillAPI( cmdAPI_t *api ) {
	api->AddCommand = Cmd_AddCommand;
	api->AddCommandEx = Cmd_AddCommandEx;
	api->RemoveCommand = Cmd_RemoveCommand;
	api->Argc = Cmd_Argc;
	api->Argv = Cmd_Argv;
	api->ArgsFrom = Cmd_ArgsFrom;
	api->ExecuteText = Cbuf_ExecuteText;
	api->FindFunction = Cmd_FindFunction;
	api->FindMacroFunction = Cmd_FindMacroFunction;
	api->FindGenerator = Cmd_FindGenerator;
}

/*
============
Cmd_Init
============
*/
void Cmd_Init( void ) {
//
// register our commands
//
	Cmd_AddCommand( "cmdlist", Cmd_List_f );
	Cmd_AddCommand( "macrolist", Cmd_MacroList_f );
	Cmd_AddCommandEx( "exec", Cmd_Exec_f, Cmd_Exec_g );
	Cmd_AddCommand( "echo", Cmd_Echo_f );
	Cmd_AddCommand( "_echo", Cmd_ColoredEcho_f );
	Cmd_AddCommandEx( "alias", Cmd_Alias_f, Cmd_AliasGenerator );
	Cmd_AddCommandEx( "unalias", Cmd_UnAlias_f, Cmd_AliasGenerator );
	Cmd_AddCommand( "wait", Cmd_Wait_f );
	Cmd_AddCommand( "trigger", Cmd_Trigger_f );
	Cmd_AddCommand( "untrigger", Cmd_UnTrigger_f );

	Cmd_FillAPI( &cmd );
}

