#pragma once

#include "nil.qh"
#include "progname.qh"
#include "static.qh"

ERASEABLE
void RegisterCvars(void(string name, string def, string desc, bool archive, string file) f) {}

ERASEABLE
bool cvar_value_issafe(string s)
{
	if (strstrofs(s, "\"", 0) >= 0)  return false;
	if (strstrofs(s, "\\", 0) >= 0)  return false;
	if (strstrofs(s, ";",  0) >= 0)  return false;
	if (strstrofs(s, "$",  0) >= 0)  return false;
	if (strstrofs(s, "\r", 0) >= 0)  return false;
	if (strstrofs(s, "\n", 0) >= 0)  return false;
	return true;
}

/// Escape the string to make it safe for consoles
ERASEABLE
string MakeConsoleSafe(string input)
{
	input = strreplace("\n", "", input);
	input = strreplace("\\", "\\\\", input);
	input = strreplace("$", "$$", input);
	input = strreplace("\"", "\\\"", input);
	return input;
}

/**
 * Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ...
 * +: all must match. this is the default
 * -: one must NOT match
 *
 * var>x
 * var<x
 * var>=x
 * var<=x
 * var==x
 * var!=x
 * var===x
 * var!==x
 */
ERASEABLE
bool expr_evaluate(string s)
{
	bool ret = false;
	if (str2chr(s, 0) == '+')
		s = substring(s, 1, -1);
	else if (str2chr(s, 0) == '-')
	{
		ret = true;
		s = substring(s, 1, -1);
	}
	bool expr_fail = false;
	for (int i = 0, n = tokenize_console(s); i < n; ++i)
	{
		int o;
		string k, v;
		s = argv(i);
		#define X(expr) \
			if (expr) \
				continue; \
			expr_fail = true; \
			break

		#define BINOP(op, len, expr) \
			if ((o = strstrofs(s, op, 0)) >= 0) \
			{ \
				k = substring(s, 0, o); \
				v = substring(s, o + len, -1); \
				X(expr); \
			}
		BINOP(">=",  2, cvar(k) >= stof(v))
		BINOP("<=",  2, cvar(k) <= stof(v))
		BINOP(">",   1, cvar(k) >  stof(v))
		BINOP("<",   1, cvar(k) <  stof(v))
		BINOP("==",  2, cvar(k) == stof(v))
		BINOP("!=",  2, cvar(k) != stof(v))
		BINOP("===", 3, cvar_string(k) == v)
		BINOP("!==", 3, cvar_string(k) != v)
		{
			k = s;
			bool b = true;
			if (str2chr(k, 0) == '!')
			{
				k = substring(s, 1, -1);
				b = false;
			}
			float f = stof(k);
			X(boolean((ftos(f) == k) ? f : cvar(k)) == b);
		}
		#undef BINOP
		#undef X
	}
	if (!expr_fail)
		ret = !ret;
	// now ret is true if we want to keep the item, and false if we want to get rid of it
	return ret;
}

ERASEABLE
void RegisterCvars_Set(string name, string def, string desc, bool archive, string file)
{
	localcmd(sprintf("\nset %1$s \"$%1$s\" \"%2$s\"\n", name, MakeConsoleSafe(desc)));
	if (archive)
		localcmd(sprintf("\nseta %1$s \"$%1$s\"\n", name));
}

int RegisterCvars_Save_fd;
ERASEABLE
void RegisterCvars_Save(string name, string def, string desc, bool archive, string file)
{
	if (!archive)
		return;
	fputs(RegisterCvars_Save_fd, sprintf("seta %s \"%s\"\n", name, def));
}

STATIC_INIT_LATE(Cvars)
{
	RegisterCvars(RegisterCvars_Set);
	RegisterCvars_Save_fd = fopen(sprintf("default%s.cfg", PROGNAME), FILE_WRITE);
	if (RegisterCvars_Save_fd >= 0)
	{
		RegisterCvars(RegisterCvars_Save);
		fclose(RegisterCvars_Save_fd);
	}
}

const noref bool default_bool = false;
const noref int default_int = 0;
const noref float default_float = 0;
const noref string default_string = "";
const noref vector default_vector = '0 0 0';

#define repr_cvar_bool(x)   ((x) ? "1" : "0")
#define repr_cvar_int(x)    (itos(x))
#define repr_cvar_float(x)  (ftos(x))
#define repr_cvar_string(x) (x)
#define repr_cvar_vector(x) (sprintf("%v", x))

//pseudo prototypes:
// void AUTOCVAR(<cvar_name>, <qc_var_type>, default_cvar_value, string desc)
// void AUTOCVAR_SAVE(<cvar_name>, <qc_var_type>, default_cvar_value, string desc)
//  where default_cvar_value has type <qc_var_type>
//  e.g.: AUTOCVAR(mycvar, float, 2.5, "cvar description")

#define __AUTOCVAR(file, archive, var, type, desc, default) \
	ACCUMULATE void RegisterCvars(void(string, string, string, bool, string) f) \
	{ \
		f( #var, repr_cvar_##type(default), desc, archive, file); \
	} \
	type autocvar_##var = default
#define AUTOCVAR_5(file, archive, var, type, desc) \
	__AUTOCVAR(file, archive, var, type, desc, default_##type)
#define AUTOCVAR_6(file, archive, var, type, default, desc) \
	__AUTOCVAR(file, archive, var, type, desc, default)
#define _AUTOCVAR(...) EVAL__AUTOCVAR(OVERLOAD(AUTOCVAR, __FILE__, __VA_ARGS__))
#define EVAL__AUTOCVAR(...) __VA_ARGS__
#define AUTOCVAR_SAVE(...) _AUTOCVAR(true, __VA_ARGS__)
#define AUTOCVAR(...) _AUTOCVAR(false, __VA_ARGS__)
