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

*/
// cvar.c -- dynamic variable tracking
// 2001-09-18 New cvar system by Maddes
//            completly new file

#include "globaldef.h"

cvar_t	*cvar_list = NULL;
cvar_t	*cvar_last = NULL;

char	new_valstring[CVAR_MAX_VALSTRING];

/*
*********************************************************************
   L O C A L   F U N C T I O N S
*********************************************************************
*/

/*
============
Cvar_PutInList
============
*/
void Cvar_PutInList (cvar_t *var)
{
	if (!(cvar_list))
	{
		cvar_list = var;
	}

	if (cvar_last)
	{
		cvar_last->next = var;
		var->prev = cvar_last;
	}

	cvar_last = var;
}

/*
=========
Cvar_RemoveFromList
=========
*/
void Cvar_RemoveFromList (cvar_t *var)
{
	// take out of list
	if (var->prev)
	{
		var->prev->next = var->next;
	}
	if (var->next)
	{
		var->next->prev = var->prev;
	}

	// special case: first var of list
	if (cvar_list == var)
	{
		cvar_list = var->next;
	}

	// special case: last var of list
	if (cvar_last == var)
	{
		cvar_last = var->prev;
	}
}

/*
============
Cvar_Display

same cvar display for all cvar commands
============
*/
void Cvar_Display (cvar_t *var, qboolean long_display)
{
	Con_Printf ("%c%c%c%c%c%c%c ",
			(var->flags & CVAR_ORIGINAL) ? 'O' : (var->flags & CVAR_PROGS_CREATED) ? 'P' : (var->flags & CVAR_USER_CREATED) ? 'U' : ' ',
			(var->flags & CVAR_ARCHIVE) ? 'A' : ' ',
			(var->flags & CVAR_ROM) ? 'R' : ' ',
			(var->flags & CVAR_NOTIFY) ? 'N' : ' ',
			(var->flags & CVAR_SERVERINFO) ? 'S' : (var->flags & CVAR_USERINFO) ? 'U' : ' ',
			(var->rangecheck) ? 'C' : ' ',
			(var->description) ? 'D' : ' ');

	Con_Printf ("\"%s\" is \"%s\"", var->name, var->string);

	if ((long_display) && (var->rangecheck))
	{
		char	val[32];
		int		i;

		if (var->minvalue == (int)var->minvalue)
		{
			sprintf (val, "%i", (int)var->minvalue);
		}
		else
		{
			sprintf (val, "%1f", var->minvalue);
			for (i=strlen(val)-1 ; i>0 && val[i]=='0' && val[i-1]!='.' ; i--)
			{
				val[i] = 0;
			}
		}
		Con_Printf (" (%s-", val);

		if (var->maxvalue == (int)var->maxvalue)
		{
			sprintf (val, "%i", (int)var->maxvalue);
		}
		else
		{
			sprintf (val, "%1f", var->maxvalue);
			for (i=strlen(val)-1 ; i>0 && val[i]=='0' && val[i-1]!='.' ; i--)
			{
				val[i] = 0;
			}
		}
		Con_Printf ("%s)", val);
	}

	Con_Printf ("\n");
}

// 2000-01-09 CvarList command by Maddes  start
/*
=========
Cvar_List_f
=========
*/
void Cvar_List_f (void)
{
	cvar_t	*var;
	int		i;
	char 	*partial;
	int		len;
	int		count;

	if (Cmd_Argc() > 1)
	{
		partial = Cmd_Argv (1);
		len = strlen(partial);
	}
	else
	{
		partial = NULL;
		len = 0;
	}

	count=0;
	for (var=cvar_list, i=0 ; var ; var=var->next, i++)
	{
		if (partial && Q_strncasecmp (partial, var->name, len))
		{
			continue;
		}
		Cvar_Display (var, false);
		count++;
	}

	Con_Printf ("------------\n");
	if (partial)
	{
		Con_Printf ("%i beginning with \"%s\" out of ", count, partial);
	}
	Con_Printf ("%i variables\n", i);
}
// 2000-01-09 CvarList command by Maddes  end

/*
=========
Cvar_Set_f

used for "set" and "seta" command
=========
*/
void Cvar_Set_f (void)
{
	cvar_t	*var;
	int		flags;

	if (Cmd_Argc() < 2 || Cmd_Argc() > 3)
	{
		Con_Printf ("Syntax: %s <cvar> [value]\n", Cmd_Argv(0));
		return;
	}

	var = Cvar_FindVar (Cmd_Argv(1));

	if (Cmd_Argc() == 2)	// just display the cvar
	{
		if (!var)
		{
			Con_Printf ("Variable \"%s\" does not exist\n", Cmd_Argv(1));
			return;
		}
		Cvar_Display (var, true);
		return;
	}

	flags = CVAR_NONE;
	if (!Q_strcasecmp (Cmd_Argv(0), "seta"))
	{
		flags |= CVAR_ARCHIVE;
	}

	if (!var)	// create a user cvar
	{
		flags |= CVAR_USER_CREATED;
		var = Cvar_Get (Cmd_Argv(1), Cmd_Argv(2), flags);
		return;
	}

	if (var->flags & CVAR_ROM)	// check for user-protected cvar
	{
		Con_Printf ("Variable \"%s\" is read-only\n", var->name);
		return;
	}

	Cvar_Set (var, Cmd_Argv(2));
	var->flags |= flags;
}

/*
=========
Cvar_UnSet_f

used for "unset" command
=========
*/
void Cvar_UnSet_f (void)
{
	cvar_t	*var;

	if (Cmd_Argc() != 2)
	{
		Con_Printf ("Syntax: %s <cvar>\n", Cmd_Argv(0));
		return;
	}

	var = Cvar_FindVar (Cmd_Argv(1));
	if (!var)
	{
		Con_Printf ("Variable \"%s\" does not exist\n", Cmd_Argv(1));
		return;
	}

	if (!(var->flags & CVAR_USER_CREATED))
	{
		Con_Printf ("Variable \"%s\" is not user created\n", var->name);
		return;
	}

	var = Cvar_Free (var);
}

/*
============
Cvar_Help_f
============
*/
void Cvar_Help_f (void)
{
	cvar_t	*var;
	char	*dummy = NULL;

	if (Cmd_Argc() != 2)
	{
		Con_Printf ("Syntax: %s <cvar>\n", Cmd_Argv(0));

		Con_Printf ("\nOutput:\n");
		Con_Printf ("1234567 \"cvarname\" is \"value\"\n");
		Con_Printf ("\\------ (O)riginal Quake, engine( ), (P)ROGS.DAT or (U)ser cvar\n");
		Con_Printf (" \\----- (A)rchived in config.cfg\n");
		Con_Printf ("  \\---- Read-only\n");
		Con_Printf ("   \\--- (N)otification send to all clients on change\n");
		Con_Printf ("    \\-- (S)erverinfo or (U)serinfo\n");
		Con_Printf ("     \\- Range (c)hecked\n");
		Con_Printf ("      \\ Description available (use CVARHELP)\n");

		return;
	}

	var = Cvar_FindVar (Cmd_Argv(1));
	if (!var)
	{
		Con_Printf ("Variable \"%s\" does not exist\n", Cmd_Argv(1));
		return;
	}

	Cvar_Display (var, false);

	if (var->description)
	{
		Con_Printf("Desc: %s\n", var->description);
	}
	else
	{
		Con_Printf("Desc: No information available.\n");
	}

	if (var->rangecheck)
	{
		var->rangecheck (var, dummy, true);
	}
}

/*
*********************************************************************
   G L O B A L   F U N C T I O N S
*********************************************************************
*/

/*
============
Cvar_Init
============
*/
void Cvar_Init (void)
{
	Cmd_AddCommand ("set", Cvar_Set_f);
	Cmd_AddCommand ("seta", Cvar_Set_f);
	Cmd_AddCommand ("unset", Cvar_UnSet_f);
	Cmd_AddCommand ("cvarlist", Cvar_List_f);	// 2000-01-09 CvarList command by Maddes
	Cmd_AddCommand ("cvarhelp", Cvar_Help_f);
}

/*
============
Cvar_Get
============
*/
cvar_t *Cvar_Get (char *name, char *string, int flags)
{
	cvar_t	*var;

	var = Cvar_FindVar (name);
	if (!var)	// Var does not exist, create it
	{
		var = Z_Malloc (mainzone, sizeof(cvar_t));	// 2001-09-20 Enhanced zone handling by Maddes
		var->name = Z_Malloc (mainzone, strlen (name) + 1);	// 2001-09-20 Enhanced zone handling by Maddes
		strcpy (var->name, name);
		var->string = NULL;		// no value yet
		var->value = 0;
		var->flags = CVAR_NONE;
		var->prev = NULL;
		var->next = NULL;

		var->callback = NULL;

		var->rangecheck = NULL;
		var->minvalue = 0;
		var->maxvalue = 0;

		var->description = NULL;

		Cvar_PutInList (var);

		Cvar_Set (var, string);
	}

	// always throw out flags
	if (!(flags & CVAR_ROM))	// keep ARCHIVE flag if not ROM
	{
		flags |= var->flags & CVAR_ARCHIVE;
	}
	var->flags = flags;
	return var;
}

/*
=========
Cvar_Free

used for "unset" command
=========
*/
cvar_t *Cvar_Free (cvar_t *var)
{
	Cvar_RemoveFromList (var);

	if (var->string)
	{
		Z_Free (mainzone, var->string);	// 2001-09-20 Enhanced zone handling by Maddes
	}

	if (var->name)
	{
		Z_Free (mainzone, var->name);	// 2001-09-20 Enhanced zone handling by Maddes
	}

	Z_Free (mainzone, var);	// 2001-09-20 Enhanced zone handling by Maddes

	return NULL;
}

/*
============
Cvar_FindVar
============
*/
cvar_t *Cvar_FindVar (char *name)
{
	cvar_t	*var;

	for ( var=cvar_list ; var ; var=var->next )
	{
		if (!Q_strcasecmp (name, var->name))
		{
			return var;
		}
	}

	return NULL;
}

/*
============
Cvar_Set
============
*/
void Cvar_Set (cvar_t *var, char *value)
{
	char	*newvalue;

	if (var->rangecheck)
	{
		newvalue = var->rangecheck (var, value, false);
	}
	else
	{
		newvalue = value;
	}

	if (var->string)
	{
		if (!strcmp (newvalue, var->string))
		{
			return;		// do nothing on same value
		}
		Z_Free (mainzone, var->string);	// 2001-09-20 Enhanced zone handling by Maddes
	}

	var->string = Z_Malloc (mainzone, strlen(newvalue) + 1);	// 2001-09-20 Enhanced zone handling by Maddes
	strcpy (var->string, newvalue);

	var->value = Q_atof (var->string);

	// notify clients of change
	if (var->flags & CVAR_NOTIFY)
	{
		if (sv.active)
		{
			SV_BroadcastPrintf ("\"%s\" changed to \"%s\"\n", var->name, var->string);
		}
	}

	if (var->callback)
	{
		var->callback (var);
	}
}

/*
============
Cvar_SetValue

expands value to a string and calls Cvar_Set
============
*/
void Cvar_SetValue (cvar_t *var, float value)
{
	char	valstring[32];

// 1999-09-07 weird cvar zeros fix by Maddes  start
	int	i;

	if (value == (int)value)
	{
		sprintf (valstring, "%i", (int)value);
	}
	else
	{
		sprintf (valstring, "%1f", value);	// no leading spaces
		for (i=strlen(valstring)-1 ; i>0 && valstring[i]=='0' && valstring[i-1]!='.' ; i--)	// no trailing zeroes
		{
			valstring[i] = 0;
		}
	}
// 1999-09-07 weird cvar zeros fix by Maddes  end

	Cvar_Set (var, valstring);
}

/*
============
Cvar_Command

Handles variable inspection and changing from the console
============
*/
qboolean Cvar_Command (void)
{
	cvar_t	*var;

	var = Cvar_FindVar (Cmd_Argv(0));
	if (!var)
	{
		return false;
	}

	if (Cmd_Argc() == 1)	// just display the cvar
	{
		Cvar_Display (var, true);
	}
	else
	{
		if (var->flags & CVAR_ROM)	// check for user-protected cvar
		{
			Con_Printf ("Variable \"%s\" is read-only\n", var->name);
		}
		else
		{
			Cvar_Set (var, Cmd_Argv(1));
		}
	}

	return true;
}

/*
============
Cvar_WriteVariables

Writes lines containing "set variable value" for all variables
with the archive flag set to true.
============
*/
void Cvar_WriteVariables (FILE *f, qboolean new_rc_file)
{
	cvar_t	*var;

	for (var=cvar_list ; var ; var=var->next)
	{
		if (!(var->flags & CVAR_ARCHIVE))	// only cvars that should be archived
		{
			continue;
		}

		if (new_rc_file)
		{
			fprintf (f, "seta %s \"%s\"\n", var->name, var->string);
		}
		else if (var->flags & CVAR_ORIGINAL)		// original id cvars
		{
			fprintf (f, "%s \"%s\"\n", var->name, var->string);
		}
	}
}

/*
============
Cvar_CompleteVariable

attempts to match a partial variable name for command line completion
returns NULL if nothing fits
============
*/
char *Cvar_CompleteVariable (char *partial)
{
	cvar_t	*var;
	int		len;

	len = strlen(partial);

	if (!len)
	{
		return NULL;
	}

	// check variables
	for (var=cvar_list ; var ; var=var->next)
	{
		if (!(Q_strncasecmp (partial,var->name, len)))
		{
			return var->name;
		}
	}

	return NULL;
}

/*
============
Cvar_SetRangecheck
============
*/
void Cvar_SetRangecheck (cvar_t *var, cvar_rangecheck rangecheck, float minvalue, float maxvalue)
{
	var->rangecheck = rangecheck;
	var->minvalue = minvalue;
	var->maxvalue = maxvalue;
}

/*
============
Cvar_SetCallback
============
*/
void Cvar_SetCallback (cvar_t *var, cvar_callback callback)
{
	var->callback = callback;
}

/*
============
Cvar_RangecheckBool
============
*/
char *Cvar_RangecheckBool (cvar_t *var, char *value, qboolean showinfo)
{
	float	newvalue;

	if (showinfo)
	{
		Con_Printf("Range: BOOL 0/1\n");
		return value;
	}

	newvalue = Q_atof (value);

	if (newvalue)
	{
		return "1";
	}

	return "0";
}

/*
============
Cvar_RangecheckInt
============
*/
char *Cvar_RangecheckInt (cvar_t *var, char *value, qboolean showinfo)
{
	float	newvalue;

	if (showinfo)
	{
		Con_Printf("Range: INT %i - %i\n", (int)var->minvalue, (int)var->maxvalue);
		return value;
	}

	newvalue = Q_atof (value);

	if ( (newvalue != (int)newvalue)
	     || (newvalue < var->minvalue)
	     || (newvalue > var->maxvalue) )
	{
		if (newvalue < var->minvalue)
		{
			newvalue = var->minvalue;
		}
		else if (newvalue > var->maxvalue)
		{
			newvalue = var->maxvalue;
		}

		sprintf (new_valstring, "%i", (int)newvalue);

		return new_valstring;
	}

	return value;
}

/*
============
Cvar_RangecheckFloat
============
*/
char *Cvar_RangecheckFloat (cvar_t *var, char *value, qboolean showinfo)
{
	float	newvalue;
	int		i;

	if (showinfo)
	{
		Con_Printf("Range: FLOAT %1f - %1f\n", var->minvalue, var->maxvalue);
		return value;
	}

	newvalue = Q_atof (value);

	// check limits of new value
	if ( (newvalue < var->minvalue)
	     || (newvalue > var->maxvalue) )
	{
		if (newvalue < var->minvalue)
		{
			newvalue = var->minvalue;
		}
		else
		{
			newvalue = var->maxvalue;
		}

		if (newvalue == (int)newvalue)
		{
			sprintf (new_valstring, "%i", (int)newvalue);
		}
		else
		{
			sprintf (new_valstring, "%1f", newvalue);
			for (i=strlen(new_valstring)-1 ; i>0 && new_valstring[i]=='0' && new_valstring[i-1]!='.' ; i--)
			{
				new_valstring[i] = 0;
			}
		}

		return new_valstring;
	}

	return value;
}

/*
============
Cvar_SetDescription
============
*/
void Cvar_SetDescription (cvar_t *var, char *description)
{
	var->description = description;
}

// 2001-12-15 Enhanced console command completion by Fett/Maddes  start
/*
============
Cvar_CompleteCountPossible
============
*/
int Cvar_CompleteCountPossible (char *partial)
{
	cvar_t	*var;
	int	len;
	int	h;

	h=0;

	len = strlen(partial);

	if (!len)
		return 0;

	// Loop through the cvars and count all partial matches
	for (var=cvar_list ; var ; var=var->next)
		if (!Q_strncasecmp (partial,var->name, len))
			h++;

	return h;
}

/*
============
Cvar_CompletePrintPossible
============
*/
void Cvar_CompletePrintPossible (char *partial)
{
	cvar_t	*var;
	int		len;
	int		maxcnt, cnt;
	int		con_linewidth;
	char	*sout;

	len = strlen(partial);

	// Determine the width of the console - 1
	con_linewidth = (vid.width >> 3) - 3;
	maxcnt = con_linewidth / 20;	// entries per line
	cnt = 0;

	// Loop through the cvar list and print all matches
	for (var=cvar_list ; var ; var=var->next)
	{
		if (!Q_strncasecmp (partial,var->name, len))
		{
			sout = Pad_CompletePrint(var->name);
			Con_Printf ("%s", sout);

			cnt++;
			if (cnt >= maxcnt)
			{
				cnt = 0;
				Con_Printf ("\n");
			}
		}
	}
	if (cnt)
	{
		Con_Printf ("\n");
	}
	Con_Printf ("\n");
}
// 2001-12-15 Enhanced console command completion by Fett/Maddes  end