/*
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.

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

#include "quakedef.h"

void Cmd_ForwardToServer (void);

#define MAX_ALIAS_NAME  32

typedef struct cmdalias_s
{
  struct cmdalias_s *next;
  char  name[MAX_ALIAS_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;
  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 allready exists, reuse it
  for (a = cmd_alias ; a ; a=a->next)
  {
    if (!strcmp(s, a->name))
    {
      Z_Free (a->value);
      break;
    }
  }

  if (!a)
  {
    a = 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;
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);
  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");
}


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