#pragma once

#include <common/command/_mod.qh>
#include <common/stats.qh>
#include "config.qh"

#include "weapon.qh"

#ifdef SVQC
void WriteWepSet(float dest, WepSet w);
#endif

#ifdef CSQC
WepSet WepSet_GetFromStat();
WepSet WepSet_GetFromStat_InMap();
WepSet ReadWepSet();
#endif

#ifdef GAMEQC
	#include "calculations.qh"
	#include "projectiles.qh"
	#include <common/models/all.qh>
	#include <common/effects/all.qh>
#endif

#include <common/util.qh>

REGISTRY(Weapons, 72) // Increase as needed. Can be up to 72.
REGISTER_REGISTRY(Weapons)
#ifdef GAMEQC
STATIC_INIT(WeaponPickup)
{
	FOREACH(Weapons, true,
	{
		if ((it.spawnflags & WEP_FLAG_SPECIALATTACK) && !(it.spawnflags & WEP_FLAG_HIDDEN))
			error(sprintf("Special attack \"%s\" without WEP_FLAG_HIDDEN flag set.", it.m_name));
		it.m_pickup = NEW(WeaponPickup, it);
	});
}
#endif

#ifdef SVQC
#include <server/bot/api.qh>
#endif

.WepSet m_wepset;
#define WEPSET(id) (WEP_##id.m_wepset)
#define WepSet_FromWeapon(it) ((it).m_wepset)
WepSet _WepSet_FromWeapon(int i);

.int m_unique_impulse;

#define DEFAULT_FILENAME "weapons_dump.cfg"
// NOTE: dumpeffectinfo, dumpnotifs, dumpturrets and dumpweapons use similar code
GENERIC_COMMAND(dumpweapons, "Dump all weapons into " DEFAULT_FILENAME, false) // WEAPONTODO: make this work with other progs than just server
{
	switch (request)
	{
		case CMD_REQUEST_COMMAND:
		{
			#ifdef SVQC
			wep_config_file = -1;
			wep_config_alsoprint = -1;
			string filename = argv(1);

			if (filename == "")
			{
				filename = DEFAULT_FILENAME;
				wep_config_alsoprint = false;
			}
			else if (filename == "-")
			{
				filename = DEFAULT_FILENAME;
				wep_config_alsoprint = true;
			}
			wep_config_file = fopen(filename, FILE_WRITE);

			if (wep_config_file >= 0)
			{
				Dump_Weapon_Settings();
				LOG_INFOF("Dumping weapons... File located in ^2data/data/%s^7.", filename);
				fclose(wep_config_file);
				wep_config_file = -1;
				wep_config_alsoprint = -1;
			}
			else
				LOG_INFOF("^1Error: ^7Could not open file '%s'!", filename);
			#else
			LOG_INFO("Weapons dump command only works with sv_cmd.");
			#endif
			return;
		}

		default:
		case CMD_REQUEST_USAGE:
			LOG_HELP("Usage:^3 ", GetProgramCommandPrefix(), " dumpweapons [<filename>]");
			LOG_HELPF("  Where <filename> is the file to write (default is %s),", DEFAULT_FILENAME);
			LOG_HELP("  if supplied with '-' output to console as well as default,");
			LOG_HELP("  if left blank, it will only write to default.");
			return;
	}
}
#undef DEFAULT_FILENAME

#ifdef SVQC
entity W_PROP_reloader;
float autocvar_w_prop_interval = 5;
.void(Weapon this, int) wr_net;

void W_PROP_reload(int chan, entity to)
{
	W_PROP_reloader.nextthink = time + autocvar_w_prop_interval;
	msg_entity = to;
	FOREACH(Weapons, true,
	{
		it.wr_update(it);
		void(Weapon, int) f = it.wr_net;
		if (f)
			f(it, chan);
	});
}

void W_PROP_think(entity this)
{
	W_PROP_reload(MSG_ALL, NULL);
}

STATIC_INIT_LATE(W_PROP_reloader)
{
	entity e = W_PROP_reloader = new_pure(W_PROP_reloader);
	setthink(e, W_PROP_think);
	W_PROP_think(e);
}
#endif

#define REGISTER_WEAPON(id, inst) REGISTER(Weapons, WEP, id, m_id, inst)

CLASS(NullWeapon, Weapon)
	ATTRIB(NullWeapon, m_hidden, bool, true);
ENDCLASS(NullWeapon)
REGISTER_WEAPON(Null, NEW(NullWeapon));
REGISTRY_DEFINE_GET(Weapons, WEP_Null)

Weapon Weapon_from_name(string s)
{
	FOREACH(Weapons, it != WEP_Null && it.netname == s, return it);
	return WEP_Null;
}

Weapon Weapon_from_impulse(int imp)
{
	FOREACH(Weapons, it != WEP_Null && it.m_unique_impulse == imp, return it);
	return WEP_Null;
}


#ifdef GAMEQC

// legacy w_prop mappings
#define X(fld, T) .T fld; .T wepvar_##fld = fld;
X(switchdelay_drop, float)
X(switchdelay_raise, float)
X(weaponreplace, string)
X(weaponstartoverride, float)
X(weaponstart, float)
X(weaponthrowable, float)
#ifdef SVQC
X(reload_ammo, float)
const .float reloading_ammo = reload_ammo;
X(reload_time, float)
const .float reloading_time = reload_time;
#endif
#undef X

#endif


#ifdef CSQC
	#define Read_int() ReadInt24_t()
	#define Read_float() ReadFloat()
	#define Read_string() ReadString()
#else
	#define Write_int(to, f) WriteInt24_t(to, f)
	#define Write_float(to, f) WriteFloat(to, f)
	#define Write_string(to, f) WriteString(to, f)
#endif

#define W_PROPS(L, class, prefix) \
	L(W_PROP_BEGIN, W_PROP, W_PROP_END, class, prefix) \
	L(W_CONFIG_BEGIN, W_CONFIG, W_CONFIG_END, class, prefix) \
	L(W_UPDATE_BEGIN, W_UPDATE, W_UPDATE_END, class, prefix) \
	L(W_NET_BEGIN, W_NET, W_NET_END, class, prefix) \
	/**/ \


	#define W_PROP(class, wepname, fld, T, m)       W_PROP_##m(class, fld, T, wepname)
	#define W_PROP_NONE(class, fld, T, wepname)     _W_PROP(class, fld, T, wepname)
	#define W_PROP_PRI(class, fld, T, wepname)      _W_PROP(class, primary_##fld, T, wepname)
	#define W_PROP_SEC(class, fld, T, wepname)      _W_PROP(class, secondary_##fld, T, wepname)
	#define W_PROP_BOTH(class, fld, T, wepname) \
		W_PROP_PRI(class, fld, T, wepname) \
		W_PROP_SEC(class, fld, T, wepname)
	#define W_PROP_BEGIN(class)
		#if defined(SVQC)
			#define _W_PROP(class, fld, T, wepname) \
				/* static */ T _W_PROP_CVAR(wepname, fld); \
				ATTRIB(class, wepvar_##fld, T, _W_PROP_CVAR(wepname, fld));
			#define _W_PROP_CVAR(wepname, fld) autocvar_g_balance_##wepname##_##fld
		#elif defined(CSQC)
			#define _W_PROP(class, fld, T, wepname) ATTRIB(class, wepvar_##fld, T);
		#else
			#define _W_PROP(class, fld, T, wepname)
		#endif
	#define W_PROP_END()



	#define W_CONFIG(class, wepname, fld, T, m)     W_CONFIG_##m(class, fld, T, wepname)
	#define W_CONFIG_NONE(class, fld, T, wepname)   _W_CONFIG(class, fld, T, wepname)
	#define W_CONFIG_PRI(class, fld, T, wepname)    _W_CONFIG(class, primary_##fld, T, wepname)
	#define W_CONFIG_SEC(class, fld, T, wepname)    _W_CONFIG(class, secondary_##fld, T, wepname)
	#define W_CONFIG_BOTH(class, fld, T, wepname) \
		W_CONFIG_PRI(class, fld, T, wepname) \
		W_CONFIG_SEC(class, fld, T, wepname)
	#ifdef SVQC
		#define W_CONFIG_BEGIN(class) METHOD(class, wr_config, void(class this)) {
			#define _W_CONFIG(class, fld, T, wepname) if (#wepname == this.netname) WEP_CONFIG_WRITE_CVARS(wepname, fld, T);
		#define W_CONFIG_END() }
	#else
		#define W_CONFIG_BEGIN(class)
			#define _W_CONFIG(class, fld, T, wepname)
		#define W_CONFIG_END()
	#endif


	#define W_UPDATE(class, wepname, fld, T, m)     W_UPDATE_##m(class, fld, T, wepname)
	#define W_UPDATE_NONE(class, fld, T, wepname)   _W_UPDATE(class, fld, T, wepname)
	#define W_UPDATE_PRI(class, fld, T, wepname)    _W_UPDATE(class, primary_##fld, T, wepname)
	#define W_UPDATE_SEC(class, fld, T, wepname)    _W_UPDATE(class, secondary_##fld, T, wepname)
	#define W_UPDATE_BOTH(class, fld, T, wepname) \
		W_UPDATE_PRI(class, fld, T, wepname) \
		W_UPDATE_SEC(class, fld, T, wepname)
	#ifdef GAMEQC
		.entity baseline, baseline_target;
		#define W_UPDATE_BEGIN(class) \
			METHOD(class, wr_update, void(class this)) \
			{ \
				noref entity b = this.baseline; \
				if (!b) \
				{ \
					b = this.baseline = new_pure(baseline); \
					b.baseline_target = this; \
				}
			#ifdef SVQC
				#define _W_UPDATE(class, fld, T, wepname) \
					{ \
						T it = _W_PROP_CVAR(wepname, fld); \
						b.wepvar_##fld = this.wepvar_##fld; \
						this.wepvar_##fld = it; \
					}
			#else
				#define _W_UPDATE(class, fld, T, wepname)
			#endif
		#define W_UPDATE_END() }
	#else
		#define W_UPDATE_BEGIN(class)
			#define _W_UPDATE(class, fld, T, wepname)
		#define W_UPDATE_END()
	#endif


	#define W_NET(class, wepname, fld, T, m)     W_NET_##m(class, fld, T, wepname)
	#define W_NET_NONE(class, fld, T, wepname)   _W_NET(class, fld, T, wepname)
	#define W_NET_PRI(class, fld, T, wepname)    _W_NET(class, primary_##fld, T, wepname)
	#define W_NET_SEC(class, fld, T, wepname)    _W_NET(class, secondary_##fld, T, wepname)
	#define W_NET_BOTH(class, fld, T, wepname) \
		W_NET_PRI(class, fld, T, wepname) \
		W_NET_SEC(class, fld, T, wepname)
	#if defined(CSQC)
		REGISTER_NET_TEMP(WeaponUpdate)
		#define W_NET_BEGIN(class) \
			METHOD(class, wr_net, void(class this, int i)) \
			{ \
				int n = 0;
			#define _W_NET(class, fld, T, wepname) \
				{ \
					if (++n == i) \
						this.wepvar_##fld = Read_##T(); \
				}
		#define W_NET_END() \
			}
		.void(Weapon this, int i) wr_net;
		NET_HANDLE(WeaponUpdate, bool isnew)
		{
			Weapon w = ReadRegistered(Weapons);
			for (int i; (i = ReadByte()); )
				w.wr_net(w, i);
			return true;
		}
	#elif defined(SVQC)
		REGISTER_NET_TEMP(WeaponUpdate)
		#define W_NET_BEGIN(class) \
			METHOD(class, wr_net, void(class this, int chan)) \
			{ \
				bool commit = false; \
				int i = 0;
			#define _W_NET(class, fld, T, wepname) \
				{ \
					++i; \
					T it = this.wepvar_##fld; \
					if (chan == MSG_ONE || it != this.baseline.wepvar_##fld) \
					{ \
						if (!commit) \
						{ \
							commit = true; \
							WriteHeader(chan, WeaponUpdate); \
							WriteRegistered(Weapons, chan, this); \
						} \
						WriteByte(chan, i); \
						Write_##T(chan, it); \
					} \
				}
		#define W_NET_END() \
				if (commit) \
					WriteByte(chan, 0); \
			}
	#else
		#define W_NET_BEGIN(class)
			#define _W_NET(class, fld, T, wepname)
		#define W_NET_END()
	#endif



// read cvars from weapon settings
// cvars are created as such: g_balance_wepname_name
#define WEP_CVAR(wep, name) (wep.wepvar_##name)
#define WEP_CVAR_PRI(wep, name) WEP_CVAR(wep, primary_##name)
#define WEP_CVAR_SEC(wep, name) WEP_CVAR(wep, secondary_##name)
#define WEP_CVAR_BOTH(wep, isprimary, name) ((isprimary) ? WEP_CVAR_PRI(wep, name) : WEP_CVAR_SEC(wep, name))

const int WEP_FIRST = 1;
#define WEP_LAST (REGISTRY_COUNT(Weapons) - 1)
WepSet WEPSET_ALL;
WepSet WEPSET_SUPERWEAPONS;

#include "all.inc"

// TODO: invert after changes have been made to the hardcoded weapon impulses. Will require moving 'best weapon' impulses
#define WEP_IMPULSE_BEGIN 230
#define WEP_IMPULSE_END bound(WEP_IMPULSE_BEGIN, WEP_IMPULSE_BEGIN + (REGISTRY_COUNT(Weapons) - 1) - 1, 253)

REGISTRY_SORT(Weapons, WEP_HARDCODED_IMPULSES + 1)
REGISTRY_CHECK(Weapons)

STATIC_INIT(register_weapons_done)
{
	string inaccessible = "";
	int imp = WEP_IMPULSE_BEGIN;
	FOREACH(Weapons, true,
	{
		WepSet set = it.m_wepset = _WepSet_FromWeapon(it.m_id = i);
		WEPSET_ALL |= set;
		if (it.spawnflags & WEP_FLAG_SUPERWEAPON)
			WEPSET_SUPERWEAPONS |= set;
		if (it == WEP_Null)
			continue;
		if (it.spawnflags & WEP_FLAG_SPECIALATTACK)
			continue;
		// TODO: this exists to filter out the Ball Stealer, otherwise we run out of impulses
		if ((it.spawnflags & WEP_FLAG_MUTATORBLOCKED) && (it.spawnflags & WEP_TYPE_OTHER))
			continue;

		it.m_unique_impulse = imp;
#ifdef CSQC
		if (imp <= WEP_IMPULSE_END)
			localcmd(sprintf("alias weapon_%s \"impulse %d\"\n", it.netname, imp)); // keep xonotic-client.cfg up to date
		else
#endif
			inaccessible = strcat(inaccessible, "\n", it.netname);
		++imp;
	});
	if (inaccessible != "" && autocvar_developer > 0)
		LOG_TRACEF("Impulse limit exceeded, weapon(s) will not be directly accessible: %s", inaccessible);
	#ifdef CSQC
	FOREACH(Weapons, true, it.wr_init(it));
	#endif
	#ifdef MENUQC
	FOREACH(Weapons, (it.spawnflags & WEP_FLAG_HIDDEN) && (it.spawnflags & WEP_FLAG_SPECIALATTACK), it.m_hidden = true);
	#endif
	weaponorder_byid = "";
	for (int i = REGISTRY_MAX(Weapons) - 1; i >= 1; --i)
		if (REGISTRY_GET(Weapons, i))
			weaponorder_byid = strcat(weaponorder_byid, " ", ftos(i));
	weaponorder_byid = strzone(substring(weaponorder_byid, 1, -1));
}

#ifdef GAMEQC

.entity weaponchild;
.entity exteriorweaponentity;
vector weaponentity_glowmod(Weapon wep, int c, entity wepent)
{
	vector g = wep.wr_glow(wep, c, wepent);
	if (!g)
		g = colormapPaletteColor(c & 0x0F, true);
	return g;
}

.int m_gunalign;

//.int weapon; // current weapon
.string weaponname; // name of .weapon

#ifdef SVQC
.vector spawnorigin; // for casings
#endif

.vector movedir_aligned; // shot origin based on weapon alignment (unaffected by shootfromeye)

// weapon animation vectors:
.vector anim_fire1;
.vector anim_fire2;
.vector anim_idle;
.vector anim_reload;

.entity muzzle_flash;

// static frame globals

ENUMCLASS(WFRAME)
CASE(WFRAME, DONTCHANGE)
CASE(WFRAME, FIRE1)
CASE(WFRAME, FIRE2)
CASE(WFRAME, IDLE)
CASE(WFRAME, RELOAD)
ENUMCLASS_END(WFRAME)

.WFRAME wframe;

#ifdef SVQC
	string autocvar_g_shootfromfixedorigin;
	#define G_SHOOTFROMFIXEDORIGIN autocvar_g_shootfromfixedorigin
#elif defined(CSQC)
	string autocvar_cl_shootfromfixedorigin;
	#define G_SHOOTFROMFIXEDORIGIN autocvar_cl_shootfromfixedorigin
#endif

vector shotorg_adjust(vector vecs, bool y_is_right, bool visual, int algn);
void CL_WeaponEntity_SetModel(entity this, string name, bool _anim);

REPLICATE_INIT(int, cvar_cl_gunalign);
REPLICATE_INIT(bool, cvar_cl_weapon_switch_reload);
REPLICATE_INIT(bool, cvar_cl_weapon_switch_fallback_to_impulse);
REPLICATE_INIT(int, cvar_cl_weaponimpulsemode);

REPLICATE_INIT(string, cvar_cl_weaponpriority);
REPLICATE_INIT(string, cvar_cl_weaponpriorities[10]);

#ifdef CSQC
REPLICATE_INIT(bool, cvar_cl_accuracy_data_share);
REPLICATE_INIT(bool, cvar_cl_accuracy_data_receive);
#endif

#ifdef SVQC
void wframe_send(entity actor, entity weaponentity, int wepframe, float attackrate, bool restartanim);

void W_MuzzleFlash(Weapon thiswep, entity actor, .entity weaponentity, vector shotorg, vector shotdir);

string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(entity this, string wo);
string W_FixWeaponOrder_AllowIncomplete(entity this, string order);
#endif

#endif // GAMEQC

// functions:
string W_FixWeaponOrder(string order, float complete);
string W_NameWeaponOrder(string order);
string W_NumberWeaponOrder(string order);
string W_FixWeaponOrder_BuildImpulseList(string o);
string W_FixWeaponOrder_AllowIncomplete(entity this, string order);
string W_FixWeaponOrder_ForceComplete(string order);
WepSet W_RandomWeapons(entity e, WepSet remaining, int n);

entity GetAmmoItem(Resource ammotype);
entity GetAmmoResource(Ammo ammotype);

#ifdef CSQC
int GetAmmoStat(Resource ammotype);
#endif

string W_Sound(string w_snd);
string W_Model(string w_mdl);


#ifdef MENUQC
string W_Guide_Keybinds(Weapon wep);

string W_Guide_DPS_onlyOne_unnamed(string name);
string W_Guide_DPS_onlyOne(string name, string fire);
string W_Guide_DPS_onlyOne_unnamed_andDirectHit(string name);
string W_Guide_DPS_onlyOne_andDirectHit(string name, string fire);
string W_Guide_DPS(string name, string pri, string sec);
string W_Guide_DPS_bothMultishot(string name, string pri, string sec);
string W_Guide_DPS_secondaryMultishot(string name, string pri, string sec, string shots, string refire2, bool sec_variable);
string W_Guide_DPS_primaryMultishot(string name, string pri, string sec, string shots, string refire2);
string W_Guide_DPS_onlySecondary(string name, string sec);
string W_Guide_DPS_onlyOneMultishot(string name, string fire, string shots, string refire2);
string W_Guide_DPS_secondaryMultishotWithCombo(string name, string pri, string sec, string shots, string refire2, string combo, bool sec_variable);
string W_Guide_DPS_primaryDPS(string name, string pri, string sec);
#endif // MENUQC
