/*

Copyright (C) 2002-2003       A Nourai

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the included (GNU.txt) GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include <time.h>
#include <stdlib.h>
#include <string.h>

#include "quakedef.h"
#include "sys_io.h"
#include "teamplay.h"
#include "utils.h"
#include "logging.h"
#include "readablechars.h"

#include "strl.h"

#define MAX_STATIC_STRING 1024


cvar_t match_format_solo = {"match_format_solo", "solo/%n - [%M]"};
cvar_t match_format_coop = {"match_format_coop", "coop/%n - [%C_player_coop] - [%M]"};
cvar_t match_format_race = {"match_format_race", "race/%n - [race] - [%M]"};

cvar_t match_format_duel = {"match_format_duel", "duel/%n - %p%v%e - [dmm%D] - [%M]"};
cvar_t match_format_ffa = {"match_format_ffa", "ffa/%n - [%C_player_ffa] - [%M]"};
cvar_t match_format_2on2 = {"match_format_2on2", "2on2/%n - [%k%v%l] - [%M]"};
cvar_t match_format_3on3 = {"match_format_3on3", "tdm/%n - [%Oon%E_%t%v%e] - [%M]"};
cvar_t match_format_4on4 = {"match_format_4on4", "tdm/%n - [%Oon%E_%t%v%e] - [%M]"};
cvar_t match_format_tdm = {"match_format_tdm", "tdm/%n - [%Oon%E_%t%v%e] - [%M]"};
cvar_t match_format_multiteam = {"match_format_multiteam", "tdm/%n - [%a_%b] - [%M]"};

cvar_t match_format_arena = {"match_format_arena", "arena/%n - %p%v%e - [%F_frags] - [%M]"};
cvar_t match_format_tf_duel = {"match_format_tf_duel", "tfduel/%n - %p%v%e [%M]"};
cvar_t match_format_tf_clanwar = {"match_format_tf_clanwar", "tfwar/%n - [%Oon%E_%t%v%e] - [%M]"};

cvar_t match_name_and = {"match_name_and", "_&_"};			
cvar_t match_name_versus = {"match_name_versus", "_vs_"};	
cvar_t match_name_on = {"match_name_on", "on"};				
cvar_t match_name_nick = {"match_name_nick", ""};			
cvar_t match_name_spec = {"match_name_spec", "(SPEC)"};		


static char *MT_CleanString(char *string, qboolean allow_spaces_and_slashes)
{
	char *in, *out;
	byte c, d;
	char *disallowed;
	static char buf[MAX_STATIC_STRING];
	char badchars[] = {' ', '\\', '/', '?', '*', ':', '<', '>', '"', '|'};

	disallowed = allow_spaces_and_slashes ? badchars + 3 : badchars;

	#define CLEANCHAR(c) \
		((readablechars[(byte) c] < ' ' || strchr(disallowed, readablechars[(byte) c])) ? '_' : readablechars[(byte) c])

	in = string;
	out = buf;

	while ((c = *in++) && out - buf < sizeof(buf) - 1)
	{
		d = CLEANCHAR(c);
		*out++ = d;
		if (d == '_')
		{
			for ( ; *in && CLEANCHAR(*in) == '_'; in++)
				;
		}
	}

	#undef CLEANCHAR

	*out = 0;
	Util_Process_Filename(buf);
	return buf;
}

static char *MT_PlayerName(void)
{
	return TP_PlayerName();
}

static char *MT_PlayerTeam(void)
{
	return TP_PlayerTeam();
}

static char *MT_EnemyName(void)
{
	int i;
	char *myname, *name;
	static char enemyname[MAX_INFO_STRING];

	enemyname[0] = 0;
	myname = MT_PlayerName ();

	for (i = 0; i < MAX_CLIENTS; i++)
	{
		if (cl.players[i].name[0] && !cl.players[i].spectator)
		{
			name = Info_ValueForKey(cl.players[i].userinfo, "name");
			if (strcmp(name, myname))
			{
				strcpy(enemyname, name);
				return enemyname;
			}
		}
	}

	return enemyname;
}

static char *MT_EnemyTeam(void)
{
	int i;
	char *myteam, *team;
	static char enemyteam[MAX_INFO_STRING];

	enemyteam[0] = 0;
	myteam = MT_PlayerTeam();

	for (i = 0; i < MAX_CLIENTS; i++)
	{
		if (cl.players[i].name[0] && !cl.players[i].spectator)
		{
			team = cl.players[i].team;
			if (team[0] && strcmp(team, myteam))
			{
				strcpy(enemyteam, team);
				return enemyteam;
			}
		}
	}

	return enemyteam;
}

static int MT_CountPlayers(void)
{
	return TP_CountPlayers();
}

static int MT_CountTeamMembers(char *team)
{
	int i, count = 0;

	for (i = 0; i < MAX_CLIENTS; i++)
	{
		if (!cl.players[i].name[0] || cl.players[i].spectator)
			continue;
		if (!strcmp(cl.players[i].team, team))
			count++;
	}

	return count;
}

static char *MT_NameAndClean_TeamMembers(char *team)
{
	static char namebuf[MAX_STATIC_STRING];
	int i;

	namebuf[0] = 0;
	for (i = 0; i < MAX_CLIENTS; i++)
	{
		if (!cl.players[i].name[0] || cl.players[i].spectator)
			continue;

		if (!strcmp(cl.players[i].team, team))
		{
			if (namebuf[0])
				strlcat(namebuf, match_name_and.string, sizeof(namebuf));

			strlcat(namebuf, MT_CleanString(cl.players[i].name, false), sizeof(namebuf));
		}
	}

	return namebuf;
}

static char *MT_MapName(void)
{
	static char buf[MAX_OSPATH];

	Q_strncpyz(buf, TP_MapName(), sizeof(buf));
	return buf;
}

static void MT_GetPlayerNames(char *name1, char *name2)
{
	int i;
	char *s1 = NULL, *s2 = NULL;

	name1[0] = name2[0] = 0;

	for (i = 0; i < MAX_CLIENTS; i++)
	{
		if (!cl.players[i].name[0] || cl.players[i].spectator)
			continue;

		if (!s1)
		{
			s1 = cl.players[i].name;
		}
		else
		{
			s2 = cl.players[i].name;
			break;
		}
	}

	if (s1)
		strcpy(name1, s1);

	if (s2)
		strcpy(name2, s2);
}

static int MT_GetTeamNames(char teams[][MAX_INFO_STRING], int max) {
	int i, j, count = 0;

	memset(teams, 0, sizeof(teams));

	for (i = 0; i < MAX_CLIENTS; i++) {
		if (!cl.players[i].name[0] || cl.players[i].spectator)
			continue;

		for (j = 0; j < i; j++) {
			if (!cl.players[j].name[0] || cl.players[j].spectator)
				continue;

			if (!strcmp(cl.players[i].team, cl.players[j].team))
				break;
		}
		if (j == i) {
			strcpy(teams[count], cl.players[i].team);
			count++;
		}
		if (count == max)
			break;
	}
	return count;
}

static char *MT_Serverinfo_Race(void) {
	static char buf[MAX_OSPATH];

	Q_strncpyz(buf, Info_ValueForKey(cl.serverinfo, "race"), sizeof(buf));
	return buf;
}


typedef enum {
	mt_duel, mt_ffa,
	mt_2on2, mt_3on3, mt_4on4, mt_tdm,
	mt_multiteam,
	mt_arena,
	mt_tf_duel, mt_tf_clanwar,
	mt_solo,		
	mt_coop,		
	mt_race,		
	mt_empty,		
	mt_unknown_gamedir,
	mt_unknown,
	mt_numtypes,
} matchtype_t;

typedef struct matchcvar_s {
	matchtype_t matchtype;
	char		*nickname;
	cvar_t		*format;
	qboolean	autosshot;
} matchcvar_t;

static matchcvar_t matchcvars[mt_numtypes] = {
	{mt_duel, "duel", &match_format_duel, true},
	{mt_ffa, "ffa", &match_format_ffa, true},
	{mt_2on2, "2on2", &match_format_2on2, true},
	{mt_3on3, "3on3", &match_format_3on3, true},
	{mt_4on4, "4on4", &match_format_4on4, true},
	{mt_tdm, "tdm", &match_format_tdm, true},
	{mt_multiteam, "tdm", &match_format_multiteam, true},
	{mt_arena, "arena", &match_format_arena, true},
	{mt_tf_duel, "tfduel", &match_format_tf_duel, true},
	{mt_tf_clanwar, "tfclanwar", &match_format_tf_clanwar, true},
	{mt_solo, "solo", &match_format_solo, false},
	{mt_coop, "coop", &match_format_coop, false},
	{mt_race, "race", &match_format_race, false},
	{mt_empty, "empty", NULL, false},
	{mt_unknown_gamedir, "unknown", NULL, false},
	{mt_unknown, "unknown", NULL, false},
};

typedef struct matchinfo_s {
	qboolean spectator;
	char myname[MAX_INFO_STRING];
	char player1[MAX_INFO_STRING];
	char player2[MAX_INFO_STRING];
	char team1[MAX_INFO_STRING];
	char team2[MAX_INFO_STRING];
	int team1count;
	int team2count;
	int numteams;
	char team1names[MAX_STATIC_STRING];	
	char team2names[MAX_STATIC_STRING];	
	char multiteamnames[MAX_STATIC_STRING];	
	char multiteamcounts[128];				
	int maxteamsize;
	int numplayers;
	int timelimit;
	int fraglimit;
	int teamplay;
	int deathmatch;
	char mapname[MAX_OSPATH];
	char gamedir[MAX_OSPATH];
	matchtype_t matchtype;
	char day[8];
	char month[8];
	char year[8];
	char bigyear[8];
	char hour[8];
	char minute[8];
	char second[8];
} matchinfo_t;

static matchtype_t MT_GetMatchType(matchinfo_t *matchinfo) {

	if (matchinfo->numplayers < 1)
		return mt_empty;


	if (!Q_strcasecmp(matchinfo->gamedir, "qw") && !strcmp(MT_Serverinfo_Race(), matchinfo->mapname))
		return mt_race;


	if (matchinfo->numplayers < 2)
		return mt_solo;


	if (cl.teamfortress) {
		return	(matchinfo->numplayers == 2) ? mt_tf_duel : 
				(matchinfo->numteams < 2) ? mt_unknown : mt_tf_clanwar;
	}

	if (strstr(matchinfo->gamedir, "arena"))
		return mt_arena;

	if (!matchinfo->deathmatch)
		return mt_coop;


	if (matchinfo->numplayers == 2)
		return (!matchinfo->teamplay || matchinfo->numteams == 2) ? mt_duel : mt_unknown;

	if (!matchinfo->teamplay || matchinfo->maxteamsize <= 1)
		return mt_ffa;


	if (matchinfo->numteams !=  2)
		return (matchinfo->numteams > 2) ? mt_multiteam : mt_unknown;

	switch (matchinfo->maxteamsize) {
		case 2: return mt_2on2;
		case 3: return mt_3on3;
		case 4: return mt_4on4;
		default: return mt_tdm;
	}

	return mt_unknown_gamedir;
}

static matchinfo_t *MT_GetMatchInfo(void) {
	static matchinfo_t matchinfo;
	char teamnames[MAX_CLIENTS][MAX_INFO_STRING];
	int i, numteams, maxteamsize, teamsize;
	time_t t;
	struct tm *ptm;

	memset(&matchinfo, 0, sizeof(matchinfo));
	numteams = MT_GetTeamNames(teamnames, MAX_CLIENTS);

	matchinfo.spectator = cl.spectator;
	Q_strncpyz(matchinfo.myname, MT_PlayerName(), sizeof(matchinfo.myname));

	if (cl.spectator) {
		MT_GetPlayerNames(matchinfo.player1, matchinfo.player2);
		Q_strncpyz(matchinfo.team1, teamnames[0], sizeof(matchinfo.team1));
		Q_strncpyz(matchinfo.team2, teamnames[1], sizeof(matchinfo.team2));
	} else {
		Q_strncpyz(matchinfo.player1, MT_PlayerName(), sizeof(matchinfo.player1));
		Q_strncpyz(matchinfo.player2, MT_EnemyName(), sizeof(matchinfo.player2));
		Q_strncpyz(matchinfo.team1, MT_PlayerTeam(), sizeof(matchinfo.team1));
		Q_strncpyz(matchinfo.team2, MT_EnemyTeam(), sizeof(matchinfo.team2));
	}


	matchinfo.team1count = MT_CountTeamMembers(matchinfo.team1);
	matchinfo.team2count = MT_CountTeamMembers(matchinfo.team2);
	matchinfo.numteams = numteams;
	Q_strncpyz(matchinfo.team1names, MT_NameAndClean_TeamMembers(matchinfo.team1), sizeof(matchinfo.team1names));
	Q_strncpyz(matchinfo.team2names, MT_NameAndClean_TeamMembers(matchinfo.team2), sizeof(matchinfo.team2names));


#define CLEANFIELD(x) Q_strncpyz(matchinfo.x, MT_CleanString(matchinfo.x, false), sizeof(matchinfo.x));	
	CLEANFIELD(myname);
	CLEANFIELD(player1);
	CLEANFIELD(player2);
	CLEANFIELD(team1);
	CLEANFIELD(team2);
#undef CLEANFIELD

#define BUF matchinfo.multiteamnames
	for (i = 0; i < numteams; i++)
	{
		strlcat(BUF, MT_CleanString(teamnames[i], false), sizeof(BUF));
		if (i < numteams - 1)
			strlcat(BUF, match_name_versus.string, sizeof(BUF));
	}
#undef BUF

	maxteamsize = 0;
#define BUF matchinfo.multiteamcounts
	for (i = 0; i < numteams; i++)
	{
		teamsize = MT_CountTeamMembers(teamnames[i]);
		if (*teamnames[i])
			maxteamsize = max(maxteamsize, teamsize);
		strlcat(BUF, va("%d", teamsize), sizeof(BUF));
		if (i < numteams - 1)
			strlcat(BUF, match_name_on.string, sizeof(BUF));
	}
	matchinfo.maxteamsize = maxteamsize;
#undef BUF

	matchinfo.numplayers = MT_CountPlayers();

	matchinfo.timelimit = Q_atoi(Info_ValueForKey(cl.serverinfo, "timelimit"));
	matchinfo.fraglimit = Q_atoi(Info_ValueForKey(cl.serverinfo, "fraglimit"));
	matchinfo.teamplay = Q_atoi(Info_ValueForKey(cl.serverinfo, "teamplay"));
	matchinfo.deathmatch = cl.deathmatch;

	Q_strncpyz(matchinfo.mapname, MT_MapName(), sizeof(matchinfo.mapname));
	Q_strncpyz(matchinfo.gamedir, cls.gamedirfile, sizeof(matchinfo.gamedir));

	matchinfo.matchtype = MT_GetMatchType(&matchinfo);

	time(&t);
	if ((ptm = localtime(&t))) {
		strftime (matchinfo.day, sizeof(matchinfo.day) - 1, "%d", ptm);
		strftime (matchinfo.month, sizeof(matchinfo.month) - 1, "%m", ptm);
		strftime (matchinfo.year, sizeof(matchinfo.year) - 1, "%y", ptm);
		strftime (matchinfo.bigyear, sizeof(matchinfo.bigyear) - 1, "%Y", ptm);
		strftime (matchinfo.hour, sizeof(matchinfo.hour) - 1, "%H", ptm);
		strftime (matchinfo.minute, sizeof(matchinfo.minute) - 1, "%M", ptm);
		strftime (matchinfo.second, sizeof(matchinfo.second) - 1, "%S", ptm);
	}

	return &matchinfo;
}

void MT_Macrolist_f(void) {
	int argc;

	switch((argc = Cmd_Argc())) {
	case 1:
		Com_Printf("\x02The following macros can be used to name your matches\n");
		Com_Printf("\x02The square brackets apply when spectating a match\n\n");
		Com_Printf("\x02%%n"); Com_Printf(" - your nick [followed by match_name_spec]\n");
		Com_Printf("\x02%%p"); Com_Printf(" - your name [player1's name]\n");
		Com_Printf("\x02%%t"); Com_Printf(" - your team [team1's name]\n");
		Com_Printf("\x02%%e"); Com_Printf(" - enemy nick in duels, enemy team in tp [player2/team2]\n");

		Com_Printf("\x02%%k"); Com_Printf(" - names of players on your team [team1]\n");
		Com_Printf("\x02%%l"); Com_Printf(" - names of players on enemy team [team2]\n");

		Com_Printf("\x02%%O"); Com_Printf(" - number of teammates [number on team1]\n");
		Com_Printf("\x02%%E"); Com_Printf(" - number of enemies [number on team2]\n");
		Com_Printf("\x02%%C"); Com_Printf(" - number of players on the server\n");

		Com_Printf("\x02%%a"); Com_Printf(" - team counts separated by match_name_on (eg 4on3on4)\n");
		Com_Printf("\x02%%b"); Com_Printf(" - team names separated by match_name_versus\n");
		Com_Printf("\x02%%v"); Com_Printf(" - shortcut for $match_name_versus\n");

		Com_Printf("\x02%%T"); Com_Printf(" - timelimit on the server\n");
		Com_Printf("\x02%%F"); Com_Printf(" - fraglimit n the server\n");
		Com_Printf("\x02%%p"); Com_Printf(" - teamplay setting on the server\n");
		Com_Printf("\x02%%D"); Com_Printf(" - deathmatch mode on the server\n");

		Com_Printf("\x02%%M"); Com_Printf(" - mapname\n");
		Com_Printf("\x02%%G"); Com_Printf(" - gamedir (eg. qw, fortress, arena, etc)\n");

		Com_Printf("\x02%%d"); Com_Printf(" - day\n");
		Com_Printf("\x02%%m"); Com_Printf(" - month\n");
		Com_Printf("\x02%%H"); Com_Printf(" - hour\n");
		Com_Printf("\x02%%Q"); Com_Printf(" - minute\n");
		Com_Printf("\x02%%S"); Com_Printf(" - second\n");
		Com_Printf("\x02%%y"); Com_Printf(" - year (without century)\n");
		Com_Printf("\x02%%Y"); Com_Printf(" - year (with century)\n");
		break;
	default:
		Com_Printf("%s : no arguments expected\n", Cmd_Argv(0));
		break;
	}
}

static char *MT_ParseFormat(char *format, matchinfo_t *matchinfo) {
	static char buf[MAX_STATIC_STRING];
	char c, *out, *in, *temp;

	buf[0] = 0;
	out = buf;
	in = format;

	while ((c = *in++) && (out - buf < MAX_STATIC_STRING - 1)) {
		if ((c == '%') && *in) {
			switch((c = *in++)) {
				case '%':
					temp = "%"; break;
				case 'n':
					temp = match_name_nick.string[0] ? match_name_nick.string : matchinfo->myname;
					temp = cl.spectator ? va("%s%s", temp, match_name_spec.string) : temp;
					break;
				case 'p':
					temp = matchinfo->player1; break;
				case 't':
					temp = matchinfo->team1; break;
				case 'e':
					temp = matchinfo->numplayers == 2 ? matchinfo->player2 : matchinfo->team2; break;
				case 'k':
					temp = matchinfo->team1names; break;
				case 'l':
					temp = matchinfo->team2names; break;
				case 'O':
					temp = matchinfo->teamplay ? va("%d", matchinfo->team1count) : "1"; break;
				case 'E':
					temp = va("%d", matchinfo->teamplay ? matchinfo->team2count : matchinfo->numplayers - 1); break;
				case 'C':
					temp = va("%d", matchinfo->numplayers); break;
				case 'a':
					temp = matchinfo->multiteamcounts; break;
				case 'b':
					temp = matchinfo->multiteamnames; break;
				case 'v':
					temp = match_name_versus.string; break;
				case 'T':
					temp = va("%d", matchinfo->timelimit); break;
				case 'F':
					temp = va("%d", matchinfo->fraglimit); break;
				case 'N':
					temp = va("%d", matchinfo->teamplay); break;
				case 'D':
					temp = va("%d", matchinfo->deathmatch); break;
				case 'M':
					temp = matchinfo->mapname; break;
				case 'G':
					temp = matchinfo->gamedir; break;
				case 'd':
					temp = matchinfo->day; break;
				case 'm':
					temp =  matchinfo->month; break;
				case 'y':
					temp = matchinfo->year; break;
				case 'Y':
					temp = matchinfo->bigyear; break;
				case 'H':
					temp = matchinfo->hour; break;
				case 'Q':
					temp = matchinfo->minute; break;
				case 'S': 
					temp = matchinfo->second; break;
				default:
					temp = va("%%%c", c); break;
			}
			Q_strncpyz(out, temp, sizeof(buf) - (out - buf));
			out += strlen(temp);
		} else {
			*out++ = c;
		}
	}

	*out = 0;
	return buf;
}

static char *MT_NameForMatchInfo(matchinfo_t *matchinfo) {
	char *format;

	switch (matchinfo->matchtype) {
	case mt_empty:
		format = "%n - [%M]"; break;
	case mt_unknown_gamedir:
		format = "%n - [gamedir - %G, %C players] - [%M]"; break;
	case mt_unknown:
		format = "%n - [Unknown Game, gamedir - %G, %C players] - [%M]"; break;
	default:
		if (matchinfo->matchtype >= 0 && matchinfo->matchtype < mt_numtypes) {
			format = matchcvars[matchinfo->matchtype].format->string;
		} else {
			Sys_Error("Macro_Matchdesc : Unknown match type %d", matchinfo->matchtype);
		}
	}
	if (!format)
		Sys_Error("MT_NameForMatchInfo: NULL format");

	return MT_CleanString(MT_ParseFormat(format, matchinfo), true);
}

char *Macro_MatchName(void) {
	return (cls.state < ca_active) ? "No match in progress" : MT_NameForMatchInfo(MT_GetMatchInfo());
}

char *MT_MatchName(void) {
	static char buf[MAX_STATIC_STRING];

	Q_strncpyz(buf, Macro_MatchName(), sizeof(buf));
	return buf;
}

#define MT_SCOREBOARD_SHOWIME	4

void MT_TakeScreenshot(void);

cvar_t match_auto_record = {"match_auto_record", "0"};
cvar_t match_auto_logconsole = {"match_auto_logconsole", "0"};
cvar_t match_auto_sshot = {"match_auto_sshot", "0"};
cvar_t match_auto_minlength = {"match_auto_minlength", "30"};
cvar_t match_auto_spectating = {"match_auto_spectating", "0"};


typedef struct mt_matchtstate_s {
	qboolean standby;
	int intermission;
	int status;
	float starttime;
	float endtime;
	char matchname[2 * MAX_OSPATH];
	matchtype_t matchtype;
} mt_matchstate_t;

static mt_matchstate_t matchstate;

static void MT_Delayed_EndMatch(void) {
	matchstate.endtime = 0;

	Log_AutoLogging_StopMatch();
	CL_AutoRecord_StopMatch();
}

static void MT_EndMatch(void) {
	MT_TakeScreenshot();

	if (!matchstate.status)
		return;

	matchstate.starttime = 0;
	matchstate.status = 0;
 	matchstate.endtime = cls.realtime;
}

static void MT_CancelMatch(void) {
	if (matchstate.endtime)
		MT_Delayed_EndMatch();

	if (!matchstate.status)
		return;

	matchstate.starttime = 0;
	matchstate.status = 0;
	matchstate.endtime = 0;	

	Log_AutoLogging_CancelMatch();
	CL_AutoRecord_CancelMatch();
}

static void MT_StartMatch(void) {
	if (matchstate.endtime)	
		MT_Delayed_EndMatch();

	if (matchstate.status)	
		MT_CancelMatch();

	matchstate.status = 1;
	matchstate.starttime = cls.realtime;
	matchstate.endtime = 0;

	if (cls.state < ca_active) {
		matchstate.matchtype = mt_empty;
		Q_strncpyz(matchstate.matchname, "No match in progress", sizeof(matchstate.matchname));
	} else {
		matchinfo_t *matchinfo = MT_GetMatchInfo();
		matchstate.matchtype = matchinfo->matchtype;
		Q_strncpyz(matchstate.matchname, MT_NameForMatchInfo(matchinfo), sizeof(matchstate.matchname));
	}

	CL_AutoRecord_StartMatch(matchstate.matchname);
	Log_AutoLogging_StartMatch(matchstate.matchname);

	Stats_Reset();
}

static void MT_ClearClientState(void) {
	memset(&matchstate, 0, sizeof(matchstate));
}

void MT_Frame(void) {
	if (matchstate.endtime && cls.realtime >= matchstate.endtime + MT_SCOREBOARD_SHOWIME)
		MT_Delayed_EndMatch();

	if (cls.state != ca_active || cls.demoplayback)
		return;

	if (matchstate.standby && !cl.standby && !cl.intermission) {
		if (!cl.spectator || match_auto_spectating.value)
			MT_StartMatch();
	}

	if (!matchstate.intermission && cl.intermission)
		MT_EndMatch();
	else if (matchstate.status && !matchstate.standby && cl.standby)
		MT_CancelMatch();

	matchstate.standby = cl.standby;
	matchstate.intermission = cl.intermission;
}

void MT_NewMap(void) {
	MT_CancelMatch();
	MT_ClearClientState();
}

void MT_Disconnect(void) {
	MT_CancelMatch();
	MT_ClearClientState();
}

char *MT_TempDirectory(void) {
	static char dir[MAX_OSPATH * 2] = {0};

	if (!dir[0])
		Q_snprintfz(dir, sizeof(dir), "%s/fodquake/temp", com_basedir);

	return dir;
}

void MT_SaveMatch_f(void) {
	int demo_status, log_status;

	demo_status = CL_AutoRecord_Status();
	log_status = Log_AutoLogging_Status();

	if ((demo_status & 2)|| (log_status & 2)) {
		Com_Printf("\x02Saving match...\n");
		CL_AutoRecord_SaveMatch();
		Log_AutoLogging_SaveMatch();
	} else {
		if ((demo_status & 1) || (log_status & 1)) {
			if ((demo_status & 1) && (log_status & 1))
				Com_Printf("Auto demo recording and console logging still in progress\n");
			else if ((demo_status & 1))
				Com_Printf("Auto demo recording still in progress\n");
			else if ((log_status & 1))
				Com_Printf("Auto console logging still in progress\n");
		} else {
			Com_Printf("Nothing to save!\n");
		}
	}
}

void MT_Match_ForceStart_f(void) {
	switch (Cmd_Argc()) {
	case 1:
		if (cls.state != ca_active || cls.demoplayback) {
			Com_Printf("You must be connected to a server before using \"%s\"\n", Cmd_Argv(0));
			return;
		}
		MT_StartMatch();
		break;
	default:
		Com_Printf("%s : no arguments expected\n", Cmd_Argv(0));
		break;
	}
}


void MT_TakeScreenshot(void) {
	int i;
	qboolean have_opponent;

	if (!match_auto_sshot.value || (cl.spectator && !match_auto_spectating.value))
		return;

	//don't bother screen-shotting solo games etc
	if (!matchcvars[matchstate.matchtype].autosshot)
		return;

	//make sure there are actually some frags on the board, and somebody besides us
	have_opponent = false;
	for (i = 0; i < MAX_CLIENTS; i++) {
		if (!cl.players[i].name[0] || cl.players[i].spectator)
			continue;
		if (i != cl.playernum)
			have_opponent = true;		
	}
	if (!have_opponent) {
		Com_Printf("Auto screenshot cancelled\n");
		return;
	}

	if (!matchstate.status) {
		matchinfo_t *matchinfo = MT_GetMatchInfo();
		matchstate.matchtype = matchinfo->matchtype;
		Q_strncpyz(matchstate.matchname, MT_NameForMatchInfo(matchinfo), sizeof(matchstate.matchname));
	}
	SCR_AutoScreenshot(matchstate.matchname);
}

#define MAX_GROUP_MEMBERS	36

typedef struct mapgroup_s {
	char groupname[MAX_QPATH];
	char members[MAX_GROUP_MEMBERS][MAX_QPATH];
	struct mapgroup_s *next, *prev;
	qboolean system;
	int nummembers;
} mapgroup_t;

static mapgroup_t *mapgroups = NULL;	
static mapgroup_t *last_system_mapgroup = NULL;
static qboolean mapgroups_init = false;	

#define FIRSTUSERGROUP (last_system_mapgroup ? last_system_mapgroup->next : mapgroups)

static mapgroup_t *GetGroupWithName(char *groupname) {
	mapgroup_t *node;

	for (node = mapgroups; node; node = node->next) {
		if (!Q_strcasecmp(node->groupname, groupname))
			return node;
	}
	return NULL;
}

static mapgroup_t *GetGroupWithMember(char *member) {
	int j;
	mapgroup_t *node;

	for (node = mapgroups; node; node = node->next) {
		for (j = 0; j < node->nummembers; j++) {
			if (!Q_strcasecmp(node->members[j], member))
				return node;
		}
	}
	return NULL;
}

static void DeleteMapGroup(mapgroup_t *group) {
	if (!group)
		return;

	if (group->prev)
		group->prev->next = group->next;
	if (group->next)
		group->next->prev = group->prev;

	
	if (group == mapgroups) {
		mapgroups = mapgroups->next;		
		if (group == last_system_mapgroup)
			last_system_mapgroup = NULL;
	} else if (group == last_system_mapgroup) {
		last_system_mapgroup = last_system_mapgroup->prev;
	}

	free(group);
}

static void ResetGroupMembers(mapgroup_t *group) {
	int i;

	if (!group)
		return;

	for (i = 0; i < group->nummembers; i++)
		group->members[i][0] = 0;

	group->nummembers = 0;
}

static void DeleteGroupMember(mapgroup_t *group, char *member) {
	int i;

	if (!group)
		return;

	for (i = 0; i < group->nummembers; i++) {
		if (!Q_strcasecmp(member, group->members[i]))
			break;
	}

	if (i == group->nummembers)	
		return;

	if (i < group->nummembers - 1)
		memmove(group->members[i], group->members[i + 1], (group->nummembers - 1 - i) * sizeof(group->members[0]));

	group->nummembers--;
}

static void AddGroupMember(mapgroup_t *group, char *member) {
	int i;

	if (!group || group->nummembers == MAX_GROUP_MEMBERS)
		return;

	for (i = 0; i < group->nummembers; i++) {		
		if (!Q_strcasecmp(member, group->members[i]))
			return;
	}

	Q_strncpyz(group->members[group->nummembers], member, sizeof(group->members[group->nummembers]));
	group->nummembers++;
}

void MT_MapGroup_f(void) {
	int i, c, j;
	qboolean removeflag = false;
	mapgroup_t *node, *group, *tempnode;
	char *groupname, *member;

	if ((c = Cmd_Argc()) == 1) {		
		if (!FIRSTUSERGROUP) {
			Com_Printf("No map groups defined\n");
		} else {
			for (node = FIRSTUSERGROUP; node; node = node->next) {
				Com_Printf("\x02%s: ", node->groupname);
				for (j = 0; j < node->nummembers; j++)
					Com_Printf("%s ", node->members[j]);
				Com_Printf("\n");
			}
		}
		return;
	}

	groupname = Cmd_Argv(1);

	if (c == 2 && !Q_strcasecmp(groupname, "clear")) {	
		for (node = FIRSTUSERGROUP; node; node = tempnode) {
			tempnode = node->next;
			DeleteMapGroup(node);
		}
		return;
	}

	if (Util_Is_Valid_Filename(groupname) == false) {
		Com_Printf("Error: %s is not a valid map group name\n", groupname);
		return;
	}

	group = GetGroupWithName(groupname);

	if (c == 2) {	
		if (!group) {
			Com_Printf("No map group named \"%s\"\n", groupname);
		} else {
			Com_Printf("\x02%s: ", groupname);
			for (j = 0; j < group->nummembers; j++)
				Com_Printf("%s ", group->members[j]);
			Com_Printf("\n");
		}
		return;
	}

	if (group && group->system) {
		Com_Printf("Cannot modify system group \"%s\"\n", groupname);
		return;
	}

	if (c == 3 && !Q_strcasecmp(Cmd_Argv(2), "clear")) {	
		if (!group)
			Com_Printf("\"%s\" is not a map group name\n", groupname);
		else
			DeleteMapGroup(group);
		return;
	}

	

	if (!group) {	
		group = Q_Calloc(1, sizeof(mapgroup_t));
		Q_strncpyz(group->groupname, groupname, sizeof(group->groupname));
		group->system = !mapgroups_init;
		if (mapgroups) {	
			for (tempnode = mapgroups; tempnode->next; tempnode = tempnode->next)
				;
			tempnode->next = group;
			group->prev = tempnode;
		} else {
			mapgroups = group;
		}
	} else {		
		member = Cmd_Argv(2);
		if (member[0] != '+' && member[0] != '-')
			ResetGroupMembers(group);
	}

	for (i = 2; i < c; i++) {
		member = Cmd_Argv(i);
		if (member[0] == '+') {
			removeflag = false;
			member++;
		} else if (member[0] == '-') {
			removeflag = true;
			member++;
		}

		if (!removeflag && (tempnode = GetGroupWithMember(member)) && tempnode != group) {
			if (cl_warncmd.value || developer.value)
				Com_Printf("Warning: \"%s\" is already a member of group \"%s\"...ignoring\n", member, tempnode->groupname);
			continue;
		}

		if (removeflag)
			DeleteGroupMember(group, member);
		else
			AddGroupMember(group, member);
	}

	if (!group->nummembers)	
		DeleteMapGroup(group);
}

static void MT_AddMapGroups(void)
{
	mapgroup_t *tempnode;

	Cmd_TokenizeString("mapgroup exmy start e1m1 e1m2 e1m3 e1m4 e1m5 e1m6 e1m7 e1m8 e2m1 e2m2 e2m3 e2m4 e2m5 e2m6 e2m7 e3m1 e3m2 e3m3 e3m4 e3m5 e3m6 e3m7 e4m1 e4m2 e4m3 e4m4 e4m5 e4m6 e4m7 e4m8");
	MT_MapGroup_f();

	for (tempnode = mapgroups; tempnode->next; tempnode = tempnode->next);
	last_system_mapgroup = tempnode;

	mapgroups_init = true;
}

char *MT_GetMapGroupName(char *mapname, qboolean *system) {
	mapgroup_t *group;

	group = GetGroupWithMember(mapname);
	if (group && strcmp(mapname, group->groupname)) {
		if (system)
			*system = group->system;
		return group->groupname;
	} else {
		return NULL;
	}
}

void DumpMapGroups(FILE *f) {
	mapgroup_t *node;
	int j;

	if (!FIRSTUSERGROUP) {
		fprintf(f, "mapgroup clear\n");
		return;
	}
	for (node = FIRSTUSERGROUP; node; node = node->next) {
		fprintf(f, "mapgroup %s ", node->groupname);
		for (j = 0; j < node->nummembers; j++)
			fprintf(f, "%s ", node->members[j]);
		fprintf(f, "\n");
	}
}

char *Macro_MatchType(void) {
	matchinfo_t *matchinfo;

	if (cls.state < ca_active)
		return "No match in progress";

	matchinfo = MT_GetMatchInfo();

	return matchcvars[matchinfo->matchtype].nickname;
}

void MT_CvarInit(void)
{
	Cmd_AddMacro("matchname", Macro_MatchName);
	Cmd_AddMacro("matchtype", Macro_MatchType);

	Cmd_AddCommand("match_format_macrolist", MT_Macrolist_f);
	Cmd_AddCommand("match_forcestart", MT_Match_ForceStart_f);
	Cmd_AddCommand("match_save", MT_SaveMatch_f);
	Cmd_AddCommand ("mapgroup", MT_MapGroup_f);

	Cvar_SetCurrentGroup(CVAR_GROUP_MATCH_TOOLS);
	Cvar_Register(&match_format_solo);
	Cvar_Register(&match_format_coop);
	Cvar_Register(&match_format_race);
	Cvar_Register(&match_format_duel);
	Cvar_Register(&match_format_ffa);
	Cvar_Register(&match_format_2on2);
	Cvar_Register(&match_format_3on3);
	Cvar_Register(&match_format_4on4);
	Cvar_Register(&match_format_tdm);
	Cvar_Register(&match_format_multiteam);
	Cvar_Register(&match_format_arena);
	Cvar_Register(&match_format_tf_duel);
	Cvar_Register(&match_format_tf_clanwar);

	Cvar_Register(&match_name_and);
	Cvar_Register(&match_name_versus);
	Cvar_Register(&match_name_on);

	Cvar_Register(&match_name_nick);
	Cvar_Register(&match_name_spec);

	Cvar_Register(&match_auto_record);
	Cvar_Register(&match_auto_logconsole);
	Cvar_Register(&match_auto_sshot);
	Cvar_Register(&match_auto_minlength);
	Cvar_Register(&match_auto_spectating);

	Cvar_ResetCurrentGroup();
}

void MT_Init(void)
{
	Sys_IO_Create_Directory(va("%s/fodquake/temp", com_basedir));

	MT_ClearClientState();

	MT_AddMapGroups();
}

