#pragma once

#ifdef CSQC
	const int REPLICATEVARS_SEND_ALL  = -1; ///< sync all cvars with the server (init)
	const int REPLICATEVARS_CHECK     = 0;  ///< check if any cvar has changed and sync it with the server
	const int REPLICATEVARS_DESTROY   = 1;  ///< destroy data associated with cvars (shutdown)
	#define REPLICATE_INIT(type, name) type name
#elifdef SVQC
	#define REPLICATE_INIT(type, name) .type name
#endif

#ifdef GAMEQC

	/**
	 * \def REPLICATE(fld, type, cvar)
	 * Replicates a client cvar into a server field
	 *
	 * @param fld   The field to replicate into
	 * @param type  The field type
	 * @param cvarname
	 * @param fixup_func((entity this, string original_value))  optional parameter
	 */
	#define REPLICATE(...) EVAL_REPLICATE(OVERLOAD(REPLICATE, __VA_ARGS__))
	#define EVAL_REPLICATE(...) __VA_ARGS__

	#ifdef SVQC
	ACCUMULATE void ReplicateVars(entity this, entity store, string thisname, int i) {}
	ACCUMULATE void ReplicateVars_ApplyChange(entity this, entity store, string thisname, int i) {}
	/**
	 * \def REPLICATE_APPLYCHANGE(cvarname, ApplyChange_code)
	 * Allows setting code that will be executed on cvar value changes
	 *
	 * @param cvarname
	 * @param ApplyChange_code  code meant to be run on cvar value changes
	 */
	#define REPLICATE_APPLYCHANGE(var, ApplyChange_code) \
		void ReplicateVars_ApplyChange(entity this, entity store, string thisname, int i) \
		{ \
			if (thisname == var) \
				LAMBDA(ApplyChange_code) \
		}
	#elifdef CSQC
	noref float ReplicateVars_time;
	ACCUMULATE void ReplicateVars(int mode)
	{
		if (!ReplicateVars_time || time < ReplicateVars_time)
			return;
		ReplicateVars_time = time + 0.8 + random() * 0.4; // check cvars periodically
	}
	void ReplicateVars_Start()
	{
		if (!ReplicateVars_time) // make sure it gets executed only once
		{
			ReplicateVars_time = time;
			ReplicateVars(REPLICATEVARS_SEND_ALL);
		}
	}
	#endif

	#define REPLICATE_3(fld, type, var) REPLICATE_4(fld, type, var, )
	#define REPLICATE_4(fld, type, var, func) REPLICATE_##type(fld, var, func)
	#ifdef SVQC
		#define REPLICATE_string(fld, var, func) \
			REPLICATE_7(fld, string, var, , \
				strcpy(field, it), \
				strfree(field), \
				{ \
					/* also initialize to the default value of func when requesting cvars */ \
					string s = func(this, strcat(field)); \
					if (s != field) \
						strcpy(field, s); \
				} \
			)
		#define REPLICATE_vector(fld, var, func) REPLICATE_7(fld, vector, var, func, field = stov(it),          , )
		#define REPLICATE_float(fld, var, func)  REPLICATE_7(fld, float,  var, func, field = stof(it),          , )
		#define REPLICATE_bool(fld, var, func)   REPLICATE_7(fld, bool,   var, func, field = boolean(stoi(it)), , )
		#define REPLICATE_int(fld, var, func)    REPLICATE_7(fld, int,    var, func, field = stoi(it),          , )

		#define REPLICATE_7(fld, type, var, func, create, destroy, after) \
			void ReplicateVars(entity this, entity store, string thisname, int i) \
			{ \
				type field = store.fld; \
				if (i < 0) \
					LAMBDA(destroy) \
				else \
				{ \
					string it = func(argv(i + 1)); \
					bool current = (thisname == var); \
					if (i > 0) \
					{ \
						if (current) \
							LAMBDA(create) \
					} \
					else \
						stuffcmd(this, strcat("cl_cmd sendcvar ", var, "\n")); \
					if (current) \
						LAMBDA(after) \
				} \
				store.fld = field; \
			}
	#elifdef CSQC
		#define REPLICATE_vector(fld, var, func) REPLICATE_7(fld, vector, var, func, fld != stov(cvar_string(var)), fld = stov(cvar_string(var)), )
		#define REPLICATE_string(fld, var, func) REPLICATE_7(fld, float,  var, func, fld != cvar_string(var), strcpy(fld, cvar_string(var)), strfree(fld))
		#define REPLICATE_float(fld, var, func)  REPLICATE_7(fld, float,  var, func, fld != cvar(var), fld = cvar(var), )
		#define REPLICATE_bool(fld, var, func)   REPLICATE_7(fld, bool,   var, func, fld != cvar(var), fld = cvar(var), )
		#define REPLICATE_int(fld, var, func)    REPLICATE_7(fld, int,    var, func, fld != cvar(var), fld = cvar(var), )

		void ReplicateVars_Send(string cvarname)
		{
			localcmd(strcat("cl_cmd sendcvar ", cvarname, "\n"));
		}

		#define REPLICATE_7(fld, type, var, func, check, update, destroy) \
			void ReplicateVars(int mode) \
			{ \
				if (mode == REPLICATEVARS_DESTROY) \
					LAMBDA(destroy) \
				else if (mode == REPLICATEVARS_SEND_ALL || (check)) \
				{ \
					ReplicateVars_Send(var); \
					LAMBDA(update) \
				} \
			}

		#define REPLICATE_SIMPLE(field, cvarname) MACRO_BEGIN \
			float thecvar = cvar(cvarname); \
			if (field != thecvar) \
			{ \
				ReplicateVars_Send(cvarname); \
				field = thecvar; \
			} \
		MACRO_END
	#endif

#endif
