#pragma once

#ifdef CSQC
	#include <client/csqcmodel_hooks.qh>
	#include <client/main.qh> // for playerslots
#endif

REGISTER_NET_LINKED(ENT_CLIENT_ENTCS)
REGISTER_NET_TEMP(CLIENT_ENTCS)

#ifdef CSQC
/** True when private information such as origin is available */
.bool m_entcs_private;

/** True when origin is available */
.bool has_origin;

/** True when a recent server sent origin has been received */
.bool has_sv_origin;
#endif
.int sv_solid;

#ifdef SVQC
/*
 * The point of these entities is to avoid the problems
 * with clientprediction.
 * If you add SendEntity to players, the engine will not
 * do any prediction anymore, and you'd have to write the whole
 * prediction code in CSQC, you want that? :P
 * Data can depend on gametype. For now, it serves as GPS entities
 * in onslaught... YAY ;)
 */

	.entity entcs;

	bool entcs_send(entity this, entity to, int sf);

	void entcs_think(entity this);

	void entcs_attach(entity player);

	void entcs_detach(entity player);

	.int m_forceupdate;

	void entcs_force_origin(entity player);

	void entcs_update_players(entity player);

	bool radar_showenemies;

#endif

#ifdef CSQC

	ArrayList _entcs;
	STATIC_INIT(_entcs)
	{
		AL_NEW(_entcs, 255, NULL, e);  // 255 is the engine limit on maxclients
	}
	SHUTDOWN(_entcs)
	{
		AL_DELETE(_entcs);
	}
	#define entcs_receiver(...) EVAL_entcs_receiver(OVERLOAD(entcs_receiver, __VA_ARGS__))
	#define EVAL_entcs_receiver(...) __VA_ARGS__
	#define entcs_receiver_1(i) AL_gete(_entcs, i)
	#define entcs_receiver_2(i, v) AL_sete(_entcs, i, v)
	#define entcs_is_self(e) ((e).sv_entnum == player_localentnum - 1)

	.int frags;
	.int handicap_level;
	.int wants_join;
	const int ENTCS_SPEC_PURE = 1; // real spectator
	const int ENTCS_SPEC_IN_SCOREBOARD = 2; // spectator but still in game (can be in a team)
	#define entcs_IsSpectating(i) boolean(entcs_GetSpecState(i))

	/**
	 * @param i zero indexed player
	 */
	int entcs_GetSpecState(int i)
	{
		bool unconnected = !playerslots[i].gotscores;
		entity e = entcs_receiver(i);
		int fr = ((e) ? e.frags : 0);
		if (unconnected || fr == FRAGS_SPECTATOR)
			return ENTCS_SPEC_PURE;
		int sol = ((e) ? e.sv_solid : SOLID_NOT);
		if (fr == FRAGS_PLAYER_OUT_OF_GAME && sol == SOLID_NOT)
			return ENTCS_SPEC_IN_SCOREBOARD;
		return 0;
	}

	/**
	 * @param i zero indexed player
	 */
	int entcs_GetHandicapLevel(int i)
	{
		entity e = entcs_receiver(i);
		return e.handicap_level;
	}

	/**
	 * @param i zero indexed player
	 */
	int entcs_GetWantsJoin(int i)
	{
		entity e = entcs_receiver(i);
		return e.wants_join;
	}

	/**
	 * @param i zero indexed player
	 */
	int entcs_GetClientColors(int i)
	{
		entity e = entcs_receiver(i);
		return e ? e.colormap : 0;
	}

	/**
	* @param i zero indexed player
	* @returns 0 if not teamplay
	*/
	int entcs_GetTeamColor(int i)
	{
		return (!teamplay) ? 0 : entcs_GetClientColors(i) & 15;
	}

	/**
	* @param i zero indexed player
	*/
	int entcs_GetTeam(int i)
	{
		return (entcs_GetSpecState(i) == ENTCS_SPEC_PURE) ? NUM_SPECTATOR : entcs_GetTeamColor(i);
	}

	/**
	 * Same as `entcs_GetTeam`, but returns -1 for no team in teamplay
	 */
	int entcs_GetScoreTeam(int i)
	{
		int t = entcs_GetTeam(i);
		if (teamplay && !t) t = -1;
		return t;
	}

	/**
	* @param i zero indexed player
	*/
	string entcs_GetName(int i)
	{
		entity e = entcs_receiver(i);
		return e ? ColorTranslateRGB(e.netname) : "";
	}

	/**
	 * @param i zero indexed player
	 */
	entity CSQCModel_server2csqc(int i);

	.float alpha;

	/**
	 * @param i zero indexed player
	 */
	float entcs_GetAlpha(int i)
	{
		entity e = CSQCModel_server2csqc(i);
		return e ? e.alpha : 1;
	}

	/**
	 * @param i zero indexed player
	 */
	vector entcs_GetColor(int i)
	{
		entity e = CSQCModel_server2csqc(i);
		return (!e || e.colormap <= 0)
			   ? '1 1 1'
			   : colormapPaletteColor(((e.colormap >= 1024)
			? e.colormap
			: entcs_GetClientColors(e.colormap - 1)) & 15, true)
		;
	}

	/**
	 * @param i zero indexed player
	 */
	bool entcs_IsDead(int i)
	{
		entity e = CSQCModel_server2csqc(i);
		return e ? e.csqcmodel_isdead : false;
	}

#endif
