#include "net.qh"

#ifdef SVQC
#include <server/command/cmd.qh>

bool MapVote_SendEntity(entity this, entity to, int sf);
entity mapvote_ent;
void MapVote_Spawn()
{
	Net_LinkEntity(mapvote_ent = new(mapvote_ent), false, 0, MapVote_SendEntity);
}

void MapVote_TouchMask()
{
	mapvote_ent.SendFlags |= SF_MV_MASK;
}
void MapVote_Winner(int mappos)
{
	mapvote_ent.SendFlags |= SF_MV_WINNER;
	mv_winner_time = time;
	mv_winner = mappos;
}
#endif // SVQC

#ifdef SVQC
.float spamtime; // Reused here
void MapVote_ReadPlayerVote(entity voter)
{
	// clear possibly invalid vote
	if (!(mv_flags[voter.mapvote - 1] & GTV_AVAILABLE))
		voter.mapvote = 0;
	// use impulse as new vote
	if (CS(voter).impulse >= 1 && CS(voter).impulse <= mv_count // valid impulse
	&& (mv_flags[CS(voter).impulse - 1] & GTV_AVAILABLE) // map/gametype is available
	&& !ClientCommand_antispam(voter, strcat("impulse ", itos(CS(voter).impulse)), spamtime, 0.5, 2)) // not spamming
	{
		voter.mapvote = CS(voter).impulse;
		mapvote_ent.SendFlags |= SF_MV_VOTES;
	}
	CS(voter).impulse = 0;
}
#elifdef CSQC
void MapVote_WritePlayerVote(int index)
{
	TC(int, index);
	localcmd("\nimpulse ", itos(index + 1), "\n");
}
#endif

#ifdef SVQC
void MapVote_WriteMask()
{
	for (int b, B, i = 0; i < mv_count_real; )
	{
		B = 0;
		for (b = BIT(0); b < BIT(8) && i < mv_count_real; b <<= 1, ++i)
			if (mv_flags[i] & GTV_AVAILABLE)
				B |= b;
		WriteByte(MSG_ENTITY, B);
	}
}
#elifdef CSQC
void MapVote_ReadMask()
{
	int b, B, i;
	for (i = 0; i < mv_count_real; )
		for (B = ReadByte(), b = BIT(0); b < BIT(8) && i < mv_count_real; b <<= 1, ++i)
		{
			if (B & b)
				mv_flags[i] |= GTV_AVAILABLE;
			else
				mv_flags[i] &= ~GTV_AVAILABLE;
		}
	if (mv_abstain) // make abstain available
		mv_flags[mv_count_real] |= GTV_AVAILABLE;
}
#endif

#ifdef SVQC
/// Sends a single map vote option to the client
void MapVote_WriteOption(int i)
{
	WriteString(MSG_ENTITY, mv_entries[i]);
	WriteString(MSG_ENTITY, mv_pakfile[i]);
	WriteString(MSG_ENTITY, mv_show_suggester ? mv_suggester[i] : "");
	WriteByte(MSG_ENTITY, mv_ssdir_i[i]);
}
#elifdef CSQC
void MapVote_ReadOption(int i)
{
	TC(int, i);
	mv_entries[i] = strzone(ReadString());
	mv_data[i] = strzone(ReadString());
	mv_suggester[i] = strzone(ReadString());
	mv_flags[i] = GTV_AVAILABLE;

	mv_pics[i] = strzone(strcat(mv_ssdirs[ReadByte()], "/", mv_entries[i]));
	mv_preview[i] = false;
	MapVote_CheckPic(mv_pics[i], mv_data[i], i);
}
#endif

#ifdef SVQC
/// Sends a single gametype vote option to the client
void GameTypeVote_WriteOption(int i)
{
	string type_name = mv_entries[i];
	WriteString(MSG_ENTITY, type_name);
	WriteByte(MSG_ENTITY, mv_flags[i]);
	if (mv_flags[i] & GTV_CUSTOM)
	{
		WriteString(MSG_ENTITY, cvar_string(strcat("sv_vote_gametype_", type_name, "_name")));
		WriteString(MSG_ENTITY, cvar_string(strcat("sv_vote_gametype_", type_name, "_description")));
		WriteString(MSG_ENTITY, cvar_string(strcat("sv_vote_gametype_", type_name, "_type")));
	}
}
#elifdef CSQC
void GameTypeVote_ReadOption(int i)
{
	TC(int, i);
	mv_entries[i] = strzone(ReadString());
	mv_flags[i] = ReadByte();
	mv_suggester[i] = "";

	string basetype = "";

	if (mv_flags[i] & GTV_CUSTOM)
	{
		string name = ReadString();
		if (strlen(name) < 1)
			name = mv_entries[i];
		mv_data[i] = strzone(name);
		mv_desc[i] = strzone(ReadString());
		basetype = strzone(ReadString());
	}
	else
	{
		Gametype type = MapInfo_Type_FromString(mv_entries[i], false, false);
		mv_data[i] = strzone(MapInfo_Type_ToText(type));
		mv_desc[i] = MapInfo_Type_Description(type);
	}

	string mv_picpath = sprintf("gfx/menu/%s/gametype_%s", autocvar_menu_skin, mv_entries[i]);
	if (precache_pic(mv_picpath) == "")
	{
		mv_picpath = strcat("gfx/menu/wickedx/gametype_", mv_entries[i]);
		if (precache_pic(mv_picpath) == "")
		{
			mv_picpath = sprintf("gfx/menu/%s/gametype_%s", autocvar_menu_skin, basetype);
			if (precache_pic(mv_picpath) == "")
				mv_picpath = strcat("gfx/menu/wickedx/gametype_", basetype);
		}
	}
	mv_pics[i] = strzone(mv_picpath);
	mv_preview[i] = PreviewExists(mv_pics[i]);
}
#endif

#ifdef SVQC
void MapVote_Init_Server()
{
	int i;

	WriteByte(MSG_ENTITY, mv_ssdirs_count);
	for (i = 0; i < mv_ssdirs_count; ++i)
		WriteString(MSG_ENTITY, mv_ssdirs[i]);
	WriteByte(MSG_ENTITY, mv_count);
	WriteFloat(MSG_ENTITY, mv_timeout);

	if (gametypevote)
	{
		WriteByte(MSG_ENTITY, SF_MV_GTV_NOW); // gametypevote_flags: gametype vote
		WriteString(MSG_ENTITY, get_nextmap());
	}
	else if (autocvar_sv_vote_gametype)
	{
		WriteByte(MSG_ENTITY, SF_MV_GTV_DONE); // gametypevote_flags: map vote but gametype has been chosen via voting screen
		string voted_gametype_name = (voted_gametype_string == MapInfo_Type_ToString(voted_gametype))
			? MapInfo_Type_ToText(voted_gametype)
			: cvar_string(strcat("sv_vote_gametype_", voted_gametype_string, "_name"));
		WriteString(MSG_ENTITY, voted_gametype_name);
	}
	else
		WriteByte(MSG_ENTITY, SF_MV_GTV_MAPS); // gametypevote_flags: map vote

	MapVote_WriteMask();

	// Send data for the vote options
	void(int) WriteOption = (gametypevote ? GameTypeVote_WriteOption : MapVote_WriteOption);
	for (i = 0; i < mv_count_real; ++i)
		WriteOption(i);
}
#elifdef CSQC
void MapVote_Init_Client(int sf)
{
	mv_active = true;
	if (!autocvar_hud_cursormode)
		mousepos = (eX * vid_conwidth + eY * vid_conheight) * 0.5;
	mv_selection = -1;
	mv_selection_keyboard = false;

	string s;
	int i;
	mv_ssdirs_count = ReadByte(); // will be < MAPVOTE_SSDIRS_COUNT
	for (i = 0; i < mv_ssdirs_count; ++i)
		mv_ssdirs[i] = ReadString();

	mv_count_real = mv_count = ReadByte(); // will be < MAPVOTE_COUNT

	mv_abstain = boolean(sf & SF_MV_ABSTAIN);
	if (mv_abstain)
		--mv_count_real;
	mv_detail = (sf & SF_MV_DETAIL_WINNER) ? 2 : ((sf & SF_MV_DETAIL_COUNTS) ? 1 : 0);

	mv_suggester_cache = "";
	mv_suggester_cachetime = 0;

	mv_ownvote = -1;
	mv_timeout = ReadFloat();

	int gametypevote_flags = ReadByte();
	gametypevote = (gametypevote_flags == SF_MV_GTV_NOW);
	if (gametypevote_flags != SF_MV_GTV_MAPS)
		mv_chosenmap = strzone(ReadString());

	MapVote_ReadMask();
	for (i = 0; i < mv_count; ++i)
		mv_flags_start[i] = mv_flags[i];

	// Assume mv_pk3list is NULL, there should only be 1 mapvote per round
	mv_pk3list = NULL; // I'm still paranoid!

	void(int) ReadOption = (gametypevote ? GameTypeVote_ReadOption : MapVote_ReadOption);
	for (i = 0; i < mv_count_real; ++i)
	{
		mv_votes[i] = 0;
		mv_select_lasttime[i] = 0;
		ReadOption(i);
	}
	if (mv_abstain)
		mv_entries[mv_count_real] = "";

	for (i = 0; i < mv_ssdirs_count; ++i)
		mv_ssdirs[mv_ssdirs_count] = string_null;
	mv_ssdirs_count = 0;
}
#endif

#ifdef SVQC
bool MapVote_SendEntity(entity this, entity to, int sf)
{
	int i;
	if (!mv_winner_time)
		sf &= ~SF_MV_WINNER; // no winner yet

	// Pack some extra data into sf
	sf &= ~(SF_MV_ABSTAIN | SF_MV_DETAIL_COUNTS | SF_MV_DETAIL_WINNER);
	if (mv_abstain)
		sf |= SF_MV_ABSTAIN;
	if (mv_detail == 1)
		sf |= SF_MV_DETAIL_COUNTS;
	else if (mv_detail == 2)
		sf |= SF_MV_DETAIL_WINNER;

	WriteHeader(MSG_ENTITY, ENT_CLIENT_MAPVOTE);
	WriteByte(MSG_ENTITY, sf);
	if (sf & SF_MV_INIT)
		MapVote_Init_Server();
	else if (sf & SF_MV_MASK) // else if, since init already writes the mask
		MapVote_WriteMask();

	if (sf & SF_MV_VOTES)
	{
		if (mv_detail)
		{
			int i;
			for (i = 0; i < mv_count; ++i)
				if (mv_flags[i] & GTV_AVAILABLE)
					WriteByte(MSG_ENTITY, mv_votes[i]);

			if (mv_detail == 2) // tell the client what the tie winner will be
				i = mv_ranked[0];
			else if (mv_votes[mv_ranked[0]] == 0) // no votes yet, don't draw a winner (-1)
				i = -1;
			else // figure out winners yourself (-2)
				i = -2;
			WriteByte(MSG_ENTITY, i + 2);
		}

		WriteByte(MSG_ENTITY, to.mapvote);
	}

	if (sf & SF_MV_WINNER)
		WriteByte(MSG_ENTITY, mv_winner + 1);

	return true;
}
#elifdef CSQC
NET_HANDLE(ENT_CLIENT_MAPVOTE, bool isNew)
{
	int sf = ReadByte();

	if (sf & SF_MV_INIT)
		MapVote_Init_Client(sf);
	else if (sf & SF_MV_MASK)
	{
		MapVote_ReadMask();
		mv_reduce_time = time;
	}

	if (sf & SF_MV_VOTES)
	{
		for (int i = 0; i < mv_count; ++i)
		{
			if (mv_flags[i] & GTV_AVAILABLE)
			{
				if (mv_detail)
					mv_votes[i] = ReadByte();
				else
					mv_votes[i] = 0;
			}
			else
				mv_votes[i] = -1;
		}
		if (mv_detail)
			mv_tie_winner = ReadByte() - 2;

		mv_ownvote = ReadByte() - 1;
	}

	if (sf & SF_MV_WINNER)
	{
		mv_winner = ReadByte();
		mv_winner_time = time;
	}

	return true;
}
#endif

#ifdef SVQC
void MapVote_WritePicture(entity to, int id)
{
	msg_entity = to;
	WriteHeader(MSG_ONE, TE_CSQC_PICTURE);
	WriteByte(MSG_ONE, id);
	WritePicture(MSG_ONE, strcat(mv_ssdirs[mv_ssdir_i[id]], "/", mv_entries[id]), 3072);
}
#elifdef CSQC
NET_HANDLE(TE_CSQC_PICTURE, bool isNew)
{
	int type = ReadByte();
	mv_preview[type] = true;
	mv_pics[type] = strzone(ReadPicture());
	return true;
}
#endif
