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

// joe: ReadDir()'s stuff
#ifndef _WIN32
#include <dirent.h>
#include <sys/stat.h>
#endif

#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 = strlen (text);

	if (cmd_text.cursize + l >= cmd_text.maxsize)
	{
		Con_Printf ("Cbuf_AddText: overflow\n");
		return;
	}

	SZ_Write (&cmd_text, text, 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
	if ((templen = cmd_text.cursize))
	{
		temp = Z_Malloc (templen);
		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, quotes;
	char	*text, line[1024];
	
	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;
			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, s;
	char	*text, *build, c;

// 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 += 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
		strcat (text, com_argv[i]);
		if (i != com_argc-1)
			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;

			strcat (build, text + i);
			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, name[MAX_OSPATH];
	int	mark;

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

	Q_strcpy (name, Cmd_Argv(1));
	mark = Hunk_LowMark ();
	if (!(f = (char *)COM_LoadHunkFile(name)))
	{
		char	*p;

		p = COM_SkipPath (name);
		if (!strchr(p, '.'))
		{	// no extension, so try the default (.cfg)
			strcat (name, ".cfg");
			f = (char *)COM_LoadHunkFile (name);
		}

		if (!f)
		{
			Con_Printf ("couldn't exec %s\n", name);
			return;
		}
	}
	Con_Printf ("execing %s\n",name);

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

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

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

// joe: legacy commands, from FuhQuake
typedef struct legacycmd_s
{
	char	*oldname, *newname;
	struct	legacycmd_s *next;
} legacycmd_t;

static	legacycmd_t	*legacycmds = NULL;

void Cmd_AddLegacyCommand (char *oldname, char *newname)
{
	legacycmd_t	*cmd;

	cmd = (legacycmd_t *)Q_Malloc (sizeof(legacycmd_t));
	cmd->next = legacycmds;
	legacycmds = cmd;

	cmd->oldname = CopyString (oldname);
	cmd->newname = CopyString (newname);
}

static qboolean Cmd_LegacyCommand (void)
{
	qboolean	recursive = false;
	legacycmd_t	*cmd;
	char		text[1024];

	for (cmd = legacycmds ; cmd ; cmd = cmd->next)
		if (!Q_strcasecmp(cmd->oldname, Cmd_Argv(0)))
			break;

	if (!cmd)
		return false;

	if (!cmd->newname[0])
		return true;		// just ignore this command

	// build new command string
	Q_strncpyz (text, cmd->newname, sizeof(text) - 1);
	strcat (text, " ");
	strncat (text, Cmd_Args(), sizeof(text) - strlen(text) - 1);

	assert (!recursive);
	recursive = true;
	Cmd_ExecuteString (text, src_command);
	recursive = false;

	return true;
}

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

				COMMAND EXECUTION

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

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

static	cmd_function_t	*cmd_functions;		// possible commands to execute

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

/*
============
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)
{
	if (!cmd_args)
		return "";
	return cmd_args;
}


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

Parses the given string into command line tokens.
============
*/
void Cmd_TokenizeString (char *text)
{
	int		idx;
	static	char	argv_buf[1024];
	
	idx = 0;

	cmd_argc = 0;
	cmd_args = NULL;
	
	while (1)
	{
	// skip whitespace up to a /n
		while (*text == ' ' || *text == '\t' || *text == '\r')
			text++;
		
		if (*text == '\n')
		{	// a newline seperates commands in the buffer
			text++;
			break;
		}

		if (!*text)
			return;
	
		if (cmd_argc == 1)
			 cmd_args = text;
			
		if (!(text = COM_Parse(text)))
			return;

		if (cmd_argc < MAX_ARGS)
		{
			cmd_argv[cmd_argc] = argv_buf + idx;
			strcpy (cmd_argv[cmd_argc], com_token);
			idx += strlen(com_token) + 1;
			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
	if (Cmd_Exists(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 (!strcmp(cmd_name, cmd->name))
			return true;
	}

	return false;
}


/*
============
Cmd_CompleteCommand
============
*/
char *Cmd_CompleteCommand (char *partial)
{
	cmd_function_t	*cmd;
	legacycmd_t	*lcmd;
	int		len;

	if (!(len = strlen(partial)))
		return NULL;

// check functions
	for (cmd = cmd_functions ; cmd ; cmd = cmd->next)
		if (!Q_strncasecmp(partial, cmd->name, len))
			return cmd->name;

	for (lcmd = legacycmds ; lcmd ; lcmd = lcmd->next)
		if (!Q_strncasecmp(partial, lcmd->oldname, len))
			return lcmd->oldname;

	return NULL;
}

/*
============
Cmd_CompleteCountPossible
============
*/
int Cmd_CompleteCountPossible (char *partial)
{
	cmd_function_t	*cmd;
	legacycmd_t	*lcmd;
	int		len, c = 0;

	if (!(len = strlen(partial)))
		return 0;

	for (cmd = cmd_functions ; cmd ; cmd = cmd->next)
		if (!Q_strncasecmp(partial, cmd->name, len))
			c++;

// joe: check for legacy commands also
	for (lcmd = legacycmds ; lcmd ; lcmd = lcmd->next)
		if (!Q_strncasecmp(partial, lcmd->oldname, len))
			c++;

	return c;
}

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

int		RDFlags = 0;
direntry_t	*filelist = NULL;

static	char	filetype[8] = "file";

static	char	compl_common[MAX_FILELENGTH];
static	int	compl_len;
static	int	compl_clen;

static void FindCommonSubString (char *s)
{
	if (!compl_clen)
	{
		Q_strncpyz (compl_common, s, sizeof(compl_common));
		compl_clen = strlen (compl_common);
	} 
	else
	{
		while (compl_clen > compl_len && Q_strncasecmp(s, compl_common, compl_clen))
			compl_clen--;
	}
}

static void CompareParams (void)
{
	int	i;

	compl_clen = 0;

	for (i=0 ; i<num_files ; i++)
		FindCommonSubString (filelist[i].name);

	if (compl_clen)
		compl_common[compl_clen] = 0;
}

static void PrintEntries (void);
extern	char	key_lines[32][MAXCMDLINE];
extern	int	edit_line;
extern	int	key_linepos;

/*
============
Cmd_CompleteParameter	-- by joe

parameter completion for various commands
============
*/
void Cmd_CompleteParameter (char *partial, char *attachment)
{
	char	*s, *param, stay[MAX_QPATH], subdir[MAX_QPATH];

	Q_strcpy (stay, partial);

// we don't need "<command> + space(s)" included
	param = strrchr (stay, ' ') + 1;
	if (!*param)		// no parameter was written in, so quit
		return;

	compl_len = strlen (param);
	strcat (param, attachment);

// map requires maps/ dir
	if (strstr(attachment, "*.bsp"))
	{
		sprintf (subdir, "maps/%s", param);
		param = subdir;
		RDFlags |= (FOR_COMPLETION | NoPrint);
		ReadDir (param);
		RDFlags |= FOR_ID1;
	}
// sky requires env/ dir
// FIXME: what about crosshair pics?
	else if (strstr(attachment, "*.tga"))
	{
		sprintf (subdir, "env/%s", param);
		param = subdir;
		RDFlags |= (FOR_COMPLETION | NoPrint | FOR_SKYBOX);
		ReadDir (param);
		RDFlags |= (FOR_ID1 | FOR_SKYBOX);
	}

	RDFlags |= (FOR_COMPLETION | NoPrint);	// to avoid printing .EXT and "No such file"
	ReadDir (param);
	FindFilesInPak (param);
	if (!filelist)
		return;
	s = strchr (partial, ' ') + 1;
// just made this to avoid printing the filename
// twice when there's only one match
	if (num_files == 1)
	{
		*s = '\0';
		strcat (partial, filelist[0].name);
		key_linepos = strlen(partial) + 1;
		key_lines[edit_line][key_linepos] = 0;
		return;
	}

	if (*s == '*')
		Q_strcpy (compl_common, s);
	else
		CompareParams ();

	Con_Printf ("]%s\n", partial);
	PrintEntries ();

	*s = '\0';
	strcat (partial, compl_common);
	key_linepos = strlen(partial) + 1;
	key_lines[edit_line][key_linepos] = 0;
}

/*
============
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())
		return;

// joe: check legacy commands
	if (Cmd_LegacyCommand())
		return;

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

/*
====================
Cmd_CmdList_f

List all console commands
====================
*/
void Cmd_CmdList_f (void)
{
	cmd_function_t	*cmd;
	int		counter;
	
	if (cmd_source != src_command)
		return;

	for (counter = 0, cmd = cmd_functions ; cmd ; cmd = cmd->next, counter++)
		Con_Printf ("%s\n", cmd->name);

	Con_Printf ("------------\n%d commands\n", counter);
}

/*
====================
Cmd_CvarList_f

List all console variables
====================
*/
void Cmd_CvarList_f (void)
{
	cvar_t	*var;
	int		counter;
	
	if (cmd_source != src_command)
		return;

	for (counter = 0, var = cvar_vars ; var ; var = var->next, counter++)
		Con_Printf ("%s\n", var->name);

	Con_Printf ("------------\n%d variables\n", counter);
}

/*
====================
Cmd_Dir_f

List all files in the mod's directory	-- by joe
====================
*/
void Cmd_Dir_f (void)
{
	char	myarg[MAX_FILELENGTH];

	if (cmd_source != src_command)
		return;

	if (!strcmp(Cmd_Argv(1), cmd_null_string))
	{
		Q_strcpy (myarg, "*.*");
		Q_strcpy (filetype, "file");
	}
	else
	{
		Q_strcpy (myarg, Cmd_Argv(1));
		// first two are exceptional cases
		if (strstr (myarg, "*.*"))
			Q_strcpy (filetype, "file");
		else if (strstr (myarg, "*.dem"))
			Q_strcpy (filetype, "demo");
		else
		{
			if (strchr (myarg, '.'))
			{
				Q_strcpy (filetype, COM_FileExtension (myarg));
				filetype[strlen(filetype)] = 0x60;	// right-shadowed apostrophe
			}
			else
			{
				strcat (myarg, "*.*");
				Q_strcpy (filetype, "file");
			}
		}
	}

	ReadDir (myarg);
	if (!filelist)
		return;

	Con_Printf ("\x02" "%ss in current folder are:\n", filetype);
	PrintEntries ();
}

static void toLower (char* str)		// for strings
{
	char	*s;
	int	i;

	i = 0;
	s = str;

	while (*s)
	{
		if (*s >= 'A' && *s <= 'Z')
			*(str + i) = *s + 32;
		i++;
		s++;
	}
}

static void AddNewEntry (char *fname, int ftype, long fsize)
{
	int	i, pos;

	filelist = Q_Realloc (filelist, (num_files + 1) * sizeof(direntry_t));

#ifdef _WIN32
	toLower (fname);
	// else don't convert, linux is case sensitive
#endif

	// inclusion sort
	for (i=0 ; i<num_files ; i++)
	{
		if (ftype < filelist[i].type)
			continue;
		else if (ftype > filelist[i].type)
			break;

		if (strcmp(fname, filelist[i].name) < 0)
			break;
	}
	pos = i;
	for (i=num_files ; i>pos ; i--)
		filelist[i] = filelist[i-1];

        filelist[i].name = strdup (fname);
	filelist[i].type = ftype;
	filelist[i].size = fsize;

	num_files++;
}

static void AddNewEntry_unsorted (char *fname, int ftype, long fsize)
{
	filelist = Q_Realloc (filelist, (num_files + 1) * sizeof(direntry_t));

#ifdef _WIN32
	toLower (fname);
	// else don't convert, linux is case sensitive
#endif
        filelist[num_files].name = strdup (fname);
	filelist[num_files].type = ftype;
	filelist[num_files].size = fsize;

	num_files++;
}

static void EraseDirEntries (void)
{
	if (filelist)
	{
		free (filelist);
		filelist = NULL;
		num_files = 0;
	}
}

static qboolean CheckEntryName (char *ename)
{
	int	i;

	for (i=0 ; i<num_files ; i++)
		if (!Q_strcasecmp(ename, filelist[i].name))
			return true;

	return false;
}

/*
=================
ReadDir			-- by joe
=================
*/
int	num_files = 0;

#ifdef _WIN32

void ReadDir (char *the_arg)
{
	HANDLE		handle;
	WIN32_FIND_DATA	fd;

	if (RDFlags & FOR_ID1)
	{
		handle = FindFirstFile (va("%s/"GAMENAME"/%s", com_basedir, the_arg), &fd);
	}
	else
	{
		EraseDirEntries ();

		handle = FindFirstFile (va("%s%s/%s", com_basedir, demodir, the_arg), &fd);
	}

	if (handle == INVALID_HANDLE_VALUE)
	{
		if (RDFlags & FOR_MENU_DEMOS)
		{
			AddNewEntry ("Error reading directory", 3, 0);
			num_files = 1;
		}
		goto end;
	}

	if (RDFlags & FOR_MENU_DEMOS && !(RDFlags & MainMenu))
	{
		AddNewEntry ("..", 2, 0);
		num_files = 1;
	}

	do {
		int	fdtype;
		long	fdsize;
		char	filename[MAX_FILELENGTH];

		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			if (!(RDFlags & FOR_MENU_DEMOS) || !strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, ".."))
				continue;

			fdtype = 1;
			fdsize = 0;
			Q_strcpy (filename, fd.cFileName);
		}
		else
		{
			char	ext[MAX_FILELENGTH];

			Q_strcpy (ext, COM_FileExtension(fd.cFileName));

			if (RDFlags & FOR_MENU_DEMOS && Q_strcasecmp(ext, "dem") && Q_strcasecmp(ext, "dz"))
				continue;
			else if (RDFlags & FOR_MENU_MAPS && Q_strcasecmp(ext, "bsp"))
				continue;

			fdtype = 0;
			fdsize = fd.nFileSizeLow;
			if (Q_strcasecmp(ext, "dz") && RDFlags & (FOR_COMPLETION | FOR_DEMDIR | FOR_MENU_DEMOS | FOR_MENU_MAPS))
			{
				COM_StripExtension (fd.cFileName, filename);
				if (RDFlags & FOR_SKYBOX)
					filename[strlen(filename)-2] = 0;	// cut off skybox_ext
				if (RDFlags & (FOR_ID1 | FOR_SKYBOX) && CheckEntryName(filename))
					continue;	// file already on list
			}
			else
			{
				Q_strcpy (filename, fd.cFileName);
			}
		}

		AddNewEntry (filename, fdtype, fdsize);
	}
	while (FindNextFile(handle, &fd));

	FindClose (handle);

	if (!num_files)
	{
		if (RDFlags & FOR_MENU_DEMOS)
		{
			AddNewEntry ("[ no files ]", 3, 0);
			num_files = 1;
		}
		else if (!(RDFlags & NoPrint))
		{
			Con_Printf ("No such file\n");
		}
	}

end:
	RDFlags &= 0;
}

#else		// linux version

void ReadDir (char *the_arg)
{
	DIR		*d;
	struct	dirent	*dstruct;
	struct	stat	fileinfo;
	char		*p, dirname[MAX_QPATH];

	if ((p = strchr(the_arg, '/')))
	{
		strncpy (dirname, the_arg, strlen(the_arg) - strlen(p));
		*p++;
	}
	else
	{
		p = the_arg;
		*dirname = 0;
	}

	if (RDFlags & FOR_ID1)
	{
		d = dirname[0] ? opendir (va("%s/"GAMENAME"/%s", com_basedir, dirname)) : opendir (va("%s/"GAMENAME, com_basedir));
	}
	else
	{
		EraseDirEntries ();

		d = dirname[0] ? opendir (va("%s%s/%s", com_basedir, demodir, dirname)) : opendir (va("%s%s", com_basedir, demodir));
	}

	if (!d)
	{
		if (RDFlags & FOR_MENU_DEMOS)
		{
			AddNewEntry ("Error reading directory", 3, 0);
			num_files = 1;
		}
		goto end;
	}

	if (RDFlags & FOR_MENU_DEMOS && !(RDFlags & MainMenu))
	{
		AddNewEntry ("..", 2, 0);
		num_files = 1;
	}

	dstruct = readdir (d);
	do {
		int	fdtype;
		long	fdsize;
		char	filename[MAX_FILELENGTH];

		if (dirname[0])
			stat (va("%s%s/%s/%s", com_basedir, demodir, dirname, dstruct->d_name), &fileinfo);
		else
			stat (va("%s%s/%s", com_basedir, demodir, dstruct->d_name), &fileinfo);

		if (S_ISDIR(fileinfo.st_mode))
		{
			if (!(RDFlags & FOR_MENU_DEMOS) || !strcmp(dstruct->d_name, ".") || !strcmp(dstruct->d_name, ".."))
				continue;
			fdtype = 1;
			fdsize = 0;
			Q_strcpy (filename, dstruct->d_name);
		}
		else
		{
			char	ext[MAX_FILELENGTH];

			if (!(RDFlags & (FOR_MENU_DEMOS | FOR_MENU_MAPS)))
			{
				if (Q_strcasecmp(dstruct->d_name + strlen(dstruct->d_name)-4, p + strlen(p)-4) || 
					!(*p == '*' || !Q_strncasecmp(dstruct->d_name, p, compl_len)))
					continue;
			}
			Q_strcpy (ext, COM_FileExtension(dstruct->d_name));

			if (RDFlags & FOR_MENU_DEMOS && Q_strcasecmp(ext, "dem") && Q_strcasecmp(ext, "dz"))
				continue;
			else if (RDFlags & FOR_MENU_MAPS && Q_strcasecmp(ext, "bsp"))
				continue;

			fdtype = 0;
			fdsize = fileinfo.st_size;
			if (Q_strcasecmp(ext, "dz") && RDFlags & (FOR_COMPLETION | FOR_DEMDIR | FOR_MENU_DEMOS | FOR_MENU_MAPS))
			{
				COM_StripExtension (dstruct->d_name, filename);
				if (RDFlags & FOR_SKYBOX)
					filename[strlen(filename)-2] = 0;	// cut off skybox_ext
				if (RDFlags & (FOR_ID1 | FOR_SKYBOX) && CheckEntryName(filename))
					continue;	// file already on list
			}
			else
			{
				Q_strcpy (filename, dstruct->d_name);
			}
		}

		AddNewEntry (filename, fdtype, fdsize);
	}
	while ((dstruct = readdir(d)));

	closedir (d);

	if (!num_files)
	{
		if (RDFlags & FOR_MENU_DEMOS)
		{
			AddNewEntry ("[ no files ]", 3, 0);
			num_files = 1;
		}
		else if (!(RDFlags & NoPrint))
		{
			Con_Printf ("No such file\n");
		}
	}

end:
	RDFlags &= 0;
}

#endif

static qboolean CheckRealBSP (char *bspname)
{
	if (!strcmp(bspname, "b_batt0.bsp")	||
	    !strcmp(bspname, "b_batt1.bsp")	||
	    !strcmp(bspname, "b_bh10.bsp")	||
	    !strcmp(bspname, "b_bh100.bsp")	||
	    !strcmp(bspname, "b_bh25.bsp")	||
	    !strcmp(bspname, "b_explob.bsp")	||
	    !strcmp(bspname, "b_nail0.bsp")	||
	    !strcmp(bspname, "b_nail1.bsp")	||
	    !strcmp(bspname, "b_rock0.bsp")	||
	    !strcmp(bspname, "b_rock1.bsp")	||
	    !strcmp(bspname, "b_shell0.bsp")	||
	    !strcmp(bspname, "b_shell1.bsp")	||
	    !strcmp(bspname, "b_exbox2.bsp"))
		return false;

	return true;
}

/*
=================
FindFilesInPak

Search for files inside a PAK file		-- by joe
=================
*/
#define SLASHJMP(x, y)			\
	if (!(x = strrchr(y, '/')))	\
		x = y;			\
	else				\
		*++x

int	pak_files = 0;

void FindFilesInPak (char *the_arg)
{
	int		i;
	searchpath_t	*search;
	pack_t		*pak;
	char		*myarg;
	extern	searchpath_t	*com_searchpaths;

	pak_files = 0;
	SLASHJMP(myarg, the_arg);
	for (search = com_searchpaths ; search ; search = search->next)
	{
		if (search->pack)
		{
			char	*s, ext[8], filename[MAX_FILELENGTH];
			int	csiba;

			// look through all the pak file elements
			pak = search->pack;
			for (i=0 ; i<pak->numfiles ; i++)
			{
				SLASHJMP(s, pak->files[i].name);
				Q_strcpy (ext, COM_FileExtension(s));
				if (!Q_strcasecmp(ext, COM_FileExtension(myarg)))
				{
					if (!Q_strcasecmp(ext, "bsp") && !CheckRealBSP(s))
						continue;
				// FIXME: what if *something*.*
					csiba = (myarg[compl_len-1] == '*') ? compl_len - 1 : compl_len;
					if (!Q_strncasecmp(s, myarg, csiba) || *myarg == '*')
					{
						COM_StripExtension (s, filename);
						if (CheckEntryName(filename))
							continue;
						AddNewEntry_unsorted (filename, 0, pak->files[i].filelen);
						pak_files++;
					}
				}
			}
		}
	}
}

/*
==================
PaddedPrint
==================
*/
#define COLUMNWIDTH 20
#define MINCOLUMNWIDTH 18	// the last column may be slightly smaller

extern	int	con_x;

static void PaddedPrint (char *s)
{
	extern	int	con_linewidth;
	int		nextcolx = 0;

	if (con_x)
		nextcolx = (int)((con_x + COLUMNWIDTH) / COLUMNWIDTH) * COLUMNWIDTH;

	if (nextcolx > con_linewidth - MINCOLUMNWIDTH
		|| (con_x && nextcolx + strlen(s) >= con_linewidth))
		Con_Printf ("\n");

	if (con_x)
		Con_Printf (" ");
	while (con_x % COLUMNWIDTH)
		Con_Printf (" ");
	Con_Printf ("%s", s);
}

static void PrintEntries (void)
{
	int	i, filectr;

	filectr = (num_files + pak_files) ? (num_files - pak_files) : 0;

	for (i=0 ; i<num_files ; i++)
	{
		if (!filectr-- && pak_files)
		{
			if (con_x)
			{
				Con_Printf ("\n");
				Con_Printf ("\x02" "inside pack file:\n");
			}
			else
			{
				Con_Printf ("\x02" "inside pack file:\n");
			}
		}
		PaddedPrint (filelist[i].name);
	}

	if (con_x)
		Con_Printf ("\n");
}

/*
==================
CompleteCommand

Advanced command completion

Main body and many features imported from ZQuake	-- joe
==================
*/
void CompleteCommand (char *partial)
{
	int	c, v;
	char	*cmd;

	cmd = partial;

	if (!(compl_len = strlen(partial)))
		return;
	compl_clen = 0;

	c = Cmd_CompleteCountPossible (partial);
	v = Cvar_CompleteCountPossible (partial);

	if (c + v > 1)
	{
		if (c)
		{
			cmd_function_t	*cmd;
			legacycmd_t	*lcmd;

			Con_Printf ("\n");
			Con_Printf ("\x02" "commands:\n");

			// check commands
			for (cmd = cmd_functions ; cmd ; cmd = cmd->next)
				if (!Q_strncasecmp(partial, cmd->name, compl_len))
				{
					PaddedPrint (cmd->name);
					FindCommonSubString (cmd->name);
				}

			// joe: check for legacy commands also
			for (lcmd = legacycmds ; lcmd ; lcmd = lcmd->next)
				if (!Q_strncasecmp(partial, lcmd->oldname, compl_len))
				{
					PaddedPrint (lcmd->oldname);
					FindCommonSubString (lcmd->oldname);
				}
		}

		if (v)
		{
			cvar_t		*cvar;

			Con_Printf ("\n");
			Con_Printf ("\x02" "variables:\n");

			// check variables
			for (cvar = cvar_vars ; cvar ; cvar = cvar->next)
				if (!Q_strncasecmp(partial, cvar->name, compl_len))
				{
					PaddedPrint (cvar->name);
					FindCommonSubString (cvar->name);
				}
		}
	}

	if (con_x)
		Con_Printf ("\n");

	if (c + v == 1)
	{
		if (!(cmd = Cmd_CompleteCommand(partial)))
			cmd = Cvar_CompleteVariable (partial);
	}
	else if (compl_clen)
	{
		compl_common[compl_clen] = 0;
		cmd = compl_common;
	}

	Q_strcpy (key_lines[edit_line]+1, cmd);
	key_linepos = strlen (cmd) + 1;
	if (c + v == 1)
	{
		key_lines[edit_line][key_linepos] = ' ';
		key_linepos++;
	}
	key_lines[edit_line][key_linepos] = 0;
}

/*
====================
Cmd_DemDir_f

List all demo files		-- by joe
====================
*/
void Cmd_DemDir_f (void)
{
	char	myarg[MAX_FILELENGTH];

	if (cmd_source != src_command)
		return;

	if (!strcmp(Cmd_Argv(1), cmd_null_string))
	{
		Q_strcpy (myarg, "*.dem");
	}
	else
	{
		Q_strcpy (myarg, Cmd_Argv(1));
		if (strchr(myarg, '.'))
		{
			Con_Printf ("You needn`t use dots in demdir parameters\n");
			if (strcmp(COM_FileExtension (myarg), "dem"))
			{
				Con_Printf ("demdir is for demo files only\n");
				return;
			}
		}
		else
		{
			strcat (myarg, "*.dem");
		}
	}

	Q_strcpy (filetype, "demo");

	RDFlags |= FOR_DEMDIR;
	ReadDir (myarg);
	if (!filelist)
		return;

	Con_Printf ("\x02" "%ss in current folder are:\n", filetype);
	PrintEntries ();
}

/*
====================
AddTabs

Replaces nasty tab character with spaces	-- by joe
====================
*/
static void AddTabs (char *buf)
{
	unsigned char	*s, tmp[256];
	int		i;

	for (s = buf, i = 0 ; *s ; s++, i++)
	{
		if (*s == '\t')
		{
			strcpy (tmp, s + 1);
			while (i < 8)
			{
				*s++ = ' ';
				i++;
			}
			*s = '\0';
			strcat (buf, tmp);
			s--;
		}
		else if (*s == 0xb4 || *s == 0x27)
		{
			*s = 0x60;
		}

		if (i >= 7)
			i = -1;
	}
}

/*
====================
Cmd_PrintTxt_f

Prints a text file into the console	-- by joe
====================
*/
void Cmd_PrintTxt_f (void)
{
	char		name[MAX_FILELENGTH];
	FILE		*f;
	char		buf[256];
//	int		lines = 0;

	if (cmd_source != src_command)
		return;

	if (Cmd_Argc() != 2)
	{
		Con_Printf ("printtxt <txtfile> : prints a text file\n");
		return;
	}

	Q_strcpy (name, Cmd_Argv(1));

	COM_DefaultExtension (name, ".txt");

	Q_strcpy (buf, va("%s%s/%s", com_basedir, demodir, name));
	if (!(f = fopen (buf, "rt")))
	{
		Con_Printf ("ERROR: couldn't open %s\n", name);
		return;
	}

	Con_Printf ("\n");
	while (!feof(f))
	{
		fgets (buf, 256, f);
		AddTabs (buf);
		Con_Printf ("%s", buf);
		buf[0] = '\0';
//		lines++;
//		if (lines == ((int)scr_conlines-16)>>3) {}
	}

	Con_Printf ("\n\n");
	fclose (f);
}

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

	// by joe
	Cmd_AddCommand ("cmdlist", Cmd_CmdList_f);
	Cmd_AddCommand ("cvarlist", Cmd_CvarList_f);
	Cmd_AddCommand ("dir", Cmd_Dir_f);
	Cmd_AddCommand ("demdir", Cmd_DemDir_f);
	Cmd_AddCommand ("printtxt", Cmd_PrintTxt_f);
}

