/*
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
 * It is copyright by its individual contributors, as recorded in the
 * project's Git history.  See COPYING.txt at the top level for license
 * terms and a link to the Git history.
 *
 */
/*
 *
 * Console variables
 *
 */

#include <cstdarg>
#include <map>
#include <stdlib.h>
#include <physfs.h>

#include "cmd.h"
#include "console.h"
#include "cvar.h"
#include "dxxerror.h"
#include "strutil.h"
#include "u_mem.h"
#include "game.h"
#include "physfsx.h"

#include "dxxsconf.h"
#include "compiler-range_for.h"

#define CVAR_MAX_LENGTH 1024

/* The list of cvars */
typedef std::map<const char *, std::reference_wrapper<cvar_t>> cvar_list_type;
static cvar_list_type cvar_list;

const char *cvar_t::operator=(const char *s)
{
	cvar_set_cvar(this, s);
	return s;
}

int         cvar_t::operator=(int i)         { cvar_set_cvarf(this, "%d", i);  return this->intval; }

void cvar_cmd_set(unsigned long argc, const char *const *const argv)
{
	char buf[CVAR_MAX_LENGTH];
	int ret;

	if (argc == 2) {
		cvar_t *ptr;
		
		if ((ptr = cvar_find(argv[1])))
			con_printf(CON_NORMAL, "%s: %s", ptr->name, ptr->string.c_str());
		else
			con_printf(CON_NORMAL, "set: variable %s not found", argv[1]);
		return;
	}
	
	if (argc == 1) {
		range_for (const auto &i, cvar_list)
			con_printf(CON_NORMAL, "%s: %s", i.first, i.second.get().string.c_str());
		return;
	}
	
	ret = snprintf(buf, sizeof(buf), "%s", argv[2]);
	if (ret >= CVAR_MAX_LENGTH) {
		con_printf(CON_CRITICAL, "set: value too long (max %d characters)", CVAR_MAX_LENGTH);
		return;
	}
	
	size_t position = ret;
	for (int i = 3; i < argc; i++) {
		ret = snprintf(&buf[position], CVAR_MAX_LENGTH - position, " %s", argv[i]);
		position += ret;
		if (position >= CVAR_MAX_LENGTH)
		{
			con_printf(CON_CRITICAL, "set: value too long (max %d characters)", CVAR_MAX_LENGTH);
			return;
		}
	}
	cvar_set(argv[1], buf);
}


void cvar_init(void)
{
	cmd_addcommand("set", cvar_cmd_set, "set <name> <value>\n"  "    set variable <name> equal to <value>\n"
	                                    "set <name>\n"          "    show value of <name>\n"
	                                    "set\n"                 "    show value of all variables");
}


cvar_t *cvar_find(const char *cvar_name)
{
	const auto i = cvar_list.find(cvar_name);
	return i == cvar_list.end() ? nullptr : &i->second.get();
}


const char *cvar_complete(const char *text)
{
	uint_fast32_t len = strlen(text);
	if (!len)
		return NULL;
	range_for (const auto &i, cvar_list)
		if (!d_strnicmp(text, i.first, len))
			return i.first;
	return NULL;
}


/* Register a cvar */
void cvar_registervariable (cvar_t &cvar)
{
	cvar.value = fl2f(strtod(cvar.string.c_str(), NULL));
	cvar.intval = static_cast<int>(strtol(cvar.string.c_str(), NULL, 10));

	const auto i = cvar_list.insert(cvar_list_type::value_type(cvar.name, cvar));
	if (!i.second)
	{
		Int3();
		con_printf(CON_URGENT, "cvar %s already exists!", cvar.name);
		return;
	}
	/* insert at end of list */
}


/* Set a CVar's value */
void cvar_set_cvar(cvar_t *cvar, const char *value)
{
	if (!cvar)
		return;
	
	cvar->string = value;
	cvar->value = fl2f(strtod(value, NULL));
	cvar->intval = static_cast<int>(strtol(value, NULL, 10));
	con_printf(CON_VERBOSE, "%s: %s", cvar->name, value);
}


void cvar_set_cvarf(cvar_t *cvar, const char *fmt, ...)
{
	va_list arglist;
	char buf[CVAR_MAX_LENGTH];
	int n;
	
	va_start (arglist, fmt);
	n = vsnprintf(buf, sizeof(buf), fmt, arglist);
	va_end (arglist);
	
	if (n < 0 || n > CVAR_MAX_LENGTH) {
		Int3();
		con_printf(CON_CRITICAL, "error setting cvar %s", cvar->name);
		return;
	}

	cvar_set_cvar(cvar, buf);
}


void cvar_set(const char *cvar_name, char *value)
{
	cvar_t *cvar;
	
	cvar = cvar_find(cvar_name);
	if (!cvar) {
		Int3();
		con_printf(CON_NORMAL, "cvar %s not found", cvar_name);
		return;
	}

	if (cvar->flags & CVAR_CHEAT && !cheats_enabled())
	{
		con_printf(CON_NORMAL, "cvar %s is cheat protected.", cvar_name);
		return;
	}

	cvar_set_cvar(cvar, value);
}


/* Write archive cvars to file */
void cvar_write(PHYSFS_File *file)
{
	range_for (const auto &i, cvar_list)
		if (i.second.get().flags & CVAR_ARCHIVE)
			PHYSFSX_printf(file, "%s=%s\n", i.first, i.second.get().string.c_str());
}
