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

#include "quakedef.h"

void Cmd_ForwardToServer(void);

typedef struct cmdalias_s {
    struct cmdalias_s *next;
    char *name;
    char *value;
} cmdalias_t;

cmdalias_t *cmd_alias;

int trashtest;
int *trashspot;

qboolean cmd_wait;

//=============================================================================

/*
============
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"
============
*/
void Cmd_Wait_f(void)
{
    cmd_wait = true;
}

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

						COMMAND BUFFER

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

sizebuf_t cmd_text;

/*
============
Cbuf_Init
============
*/
void Cbuf_Init(void)
{
    SZ_Alloc(&cmd_text, 8192);	// space for commands and script files
}

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

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));
}


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

Adds command text immediately after the current command
Adds a \n to the text
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);
    }
}

/*
============
Cbuf_Execute
============
*/
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

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

/*
===============
Cmd_StuffCmds_f

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 = 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 = 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);
}


/*
===============
Cmd_Exec_f
===============
*/
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);
}


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

Just 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");
}

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

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;
}

void Cmd_Alias_f(void)
{
    cmdalias_t *a;
    char cmd[1024];
    int i, c;

    a = Z_Malloc(sizeof(cmdalias_t));
    a->next = cmd_alias;
    cmd_alias = a;

    a->name = CopyString(Cmd_Argv(1));

// 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;
static char *cmd_argv[MAX_ARGS];
static char *cmd_null_string = "";
static char *cmd_args = NULL;

cmd_source_t cmd_source;


static cmd_function_t *cmd_functions;	// possible commands to execute

/*
============
Cmd_Init
============
*/
void Cmd_Init(void)
{
//
// register our commands
//
    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);
}

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

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

/*
============
Cmd_Args
============
*/
char *Cmd_Args(void)
{
    return cmd_args;
}


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

Parses the given string into command line tokens.
============
*/
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++;
	}
    }

}


/*
============
Cmd_AddCommand
============
*/
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 = Hunk_Alloc(sizeof(cmd_function_t));
    cmd->name = cmd_name;
    cmd->function = function;
    cmd->next = cmd_functions;
    cmd_functions = cmd;
}

/*
============
Cmd_Exists
============
*/
qboolean 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;
}



/*
============
Cmd_CompleteCommand
============
*/
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;
}

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

A complete command line has been parsed, so try to execute it
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));

}


/*
===================
Cmd_ForwardToServer

Sends the entire command line over to the server
===================
*/
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);
    SZ_Print(&cls.message, Cmd_Argv(0));
    if (Cmd_Argc() > 1) {
	SZ_Print(&cls.message, " ");
	SZ_Print(&cls.message, Cmd_Args());
    }
}


/*
================
Cmd_CheckParm

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;
}
