/*
Copyright (C) 1996-1997 Id Software, Inc.

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 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 "qtv.h"

#define MAX_INFO_KEY 64

//I apologise for this if it breaks your formatting or anything
#define HELPSTRING "\
FTEQTV proxy commands: (build "__DATE__")\n\
----------------------\n\
connect, qtv, addserver\n\
 - connect to a MVD stream (TCP)\n\
qw\n\
 - connect to a server as a player (UDP)\n\
adddemo\n\
 - play a demo from a MVD file\n\
port\n\
 - UDP port for QuakeWorld client connections\n\
mvdport\n\
 - specify TCP port for MVD broadcasting\n\
maxviewers, maxproxies\n\
- limit number of connections\n\
status, choke, late, talking, nobsp, reconnect, exec, password, master, hostname, record, stop, quit\n\n"





char *Info_ValueForKey (char *s, const char *key, char *buffer, int buffersize)
{
	char	pkey[1024];
	char	*o;

	if (*s == '\\')
		s++;
	while (1)
	{
		o = pkey;
		while (*s != '\\')
		{
			if (!*s)
			{
				*buffer='\0';
				return buffer;
			}
			*o++ = *s++;
			if (o+2 >= pkey+sizeof(pkey))	//hrm. hackers at work..
			{
				*buffer='\0';
				return buffer;
			}
		}
		*o = 0;
		s++;

		o = buffer;

		while (*s != '\\' && *s)
		{
			if (!*s)
			{
				*buffer='\0';
				return buffer;
			}
			*o++ = *s++;

			if (o+2 >= buffer+buffersize)	//hrm. hackers at work..
			{
				*buffer='\0';
				return buffer;
			}
		}
		*o = 0;

		if (!strcmp (key, pkey) )
			return buffer;

		if (!*s)
		{
			*buffer='\0';
			return buffer;
		}
		s++;
	}
}

void Info_RemoveKey (char *s, const char *key)
{
	char	*start;
	char	pkey[1024];
	char	value[1024];
	char	*o;

	if (strstr (key, "\\"))
	{
//		printf ("Key has a slash\n");
		return;
	}

	while (1)
	{
		start = s;
		if (*s == '\\')
			s++;
		o = pkey;
		while (*s != '\\')
		{
			if (!*s)
				return;
			*o++ = *s++;
		}
		*o = 0;
		s++;

		o = value;
		while (*s != '\\' && *s)
		{
			if (!*s)
				return;
			*o++ = *s++;
		}
		*o = 0;

		if (!strcmp (key, pkey) )
		{
			strcpy (start, s);	// remove this part
			return;
		}

		if (!*s)
			return;
	}

}

void Info_SetValueForStarKey (char *s, const char *key, const char *value, int maxsize)
{
	char	newv[1024], *v;
	int		c;
#ifdef SERVERONLY
	extern cvar_t sv_highchars;
#endif

	if (strstr (key, "\\") || strstr (value, "\\") )
	{
//		printf ("Key has a slash\n");
		return;
	}

	if (strstr (key, "\"") || strstr (value, "\"") )
	{
//		printf ("Key has a quote\n");
		return;
	}

	if (strlen(key) >= MAX_INFO_KEY || strlen(value) >= MAX_INFO_KEY)
	{
//		printf ("Key or value is too long\n");
		return;
	}

	// this next line is kinda trippy
/*	if (*(v = Info_ValueForKey(s, key)))
	{
		// key exists, make sure we have enough room for new value, if we don't,
		// don't change it!
		if (strlen(value) - strlen(v) + strlen(s) + 1 > maxsize)
		{
			Con_TPrintf (TL_INFOSTRINGTOOLONG);
			return;
		}
	}
*/

	Info_RemoveKey (s, key);
	if (!value || !strlen(value))
		return;

	snprintf (newv, sizeof(newv)-1, "\\%s\\%s", key, value);

	if ((int)(strlen(newv) + strlen(s) + 1) > maxsize)
	{
//		printf ("info buffer is too small\n");
		return;
	}

	// only copy ascii values
	s += strlen(s);
	v = newv;
	while (*v)
	{
		c = (unsigned char)*v++;

//		c &= 127;		// strip high bits
		if (c > 13) // && c < 127)
			*s++ = c;
	}
	*s = 0;
}






#define DEFAULT_PUNCTUATION "(,{})(\':;=!><&|+"

char *COM_ParseToken (char *data, char *out, int outsize, const char *punctuation)
{
	int		c;
	int		len;

	if (!punctuation)
		punctuation = DEFAULT_PUNCTUATION;

	len = 0;
	out[0] = 0;

	if (!data)
		return NULL;

// skip whitespace
skipwhite:
	while ( (c = *data) <= ' ')
	{
		if (c == 0)
			return NULL;			// end of file;
		data++;
	}

// skip // comments
	if (c=='/')
	{
		if (data[1] == '/')
		{
			while (*data && *data != '\n')
				data++;
			goto skipwhite;
		}
		else if (data[1] == '*')
		{
			data+=2;
			while (*data && (*data != '*' || data[1] != '/'))
				data++;
			data+=2;
			goto skipwhite;
		}
	}


// handle quoted strings specially
	if (c == '\"')
	{
		data++;
		while (1)
		{
			if (len >= outsize-1)
			{
				out[len] = '\0';
				return data;
			}
			c = *data++;
			if (c=='\"' || !c)
			{
				out[len] = 0;
				return data;
			}
			out[len] = c;
			len++;
		}
	}

// parse single characters
	if (strchr(punctuation, c))
	{
		out[len] = c;
		len++;
		out[len] = 0;
		return data+1;
	}

// parse a regular word
	do
	{
		if (len >= outsize-1)
			break;
		out[len] = c;
		data++;
		len++;
		c = *data;
		if (strchr(punctuation, c))
			break;
	} while (c>32);

	out[len] = 0;
	return data;
}





#define MAX_ARGS 8
#define ARG_LEN 512
typedef char *(*dispatchrconcommand_t)(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand);

char *Cmd_Hostname(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	strncpy(cluster->hostname, arg[1], sizeof(cluster->hostname)-1);
	return "hostname will change at start of next map\n";	//I'm too lazy to alter the serverinfo here.
}
char *Cmd_Master(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	netadr_t addr;

	strncpy(cluster->master, arg[1], sizeof(cluster->master)-1);
	cluster->mastersendtime = cluster->curtime;

	if (NET_StringToAddr(arg[1], &addr, 27000))	//send a ping like a qw server does. this is kinda pointless of course.
		NET_SendPacket (cluster, cluster->qwdsocket, 1, "k", addr);

	return "Master server set.\n";
}



char *Cmd_UDPPort(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	int news;
	int newp = atoi(arg[1]);
	news = QW_InitUDPSocket(newp);

	if (news != INVALID_SOCKET)
	{
		cluster->mastersendtime = cluster->curtime;
		closesocket(cluster->qwdsocket);
		cluster->qwdsocket = news;
		cluster->qwlistenportnum = newp;
		return "Opened udp port (all connected qw clients will time out)\n";
	}
	else
		return "Failed to open udp port\n";
}
char *Cmd_AdminPassword(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (!localcommand)
		return "Rejecting remote password change.\n";

	strncpy(cluster->adminpassword, arg[1], sizeof(cluster->adminpassword)-1);
	return "Password changed.\n";
}
char *Cmd_QTVConnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (!*arg[1])
		return "connect requires an ip:port parameter\n";

	memmove(arg[1]+4, arg[1], ARG_LEN-5);
	strncpy(arg[1], "tcp:", 4);

	if (!QTV_NewServerConnection(cluster, arg[1], arg[2], false, false, false))
		return "Failed to connect to server, connection aborted\n";
	return "Source registered\n";
}
char *Cmd_QWConnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (!*arg[1])
		return "connect requires an ip:port parameter\n";

	memmove(arg[1]+4, arg[1], ARG_LEN-5);
	strncpy(arg[1], "udp:", 4);

	if (!QTV_NewServerConnection(cluster, arg[1], arg[2], false, false, false))
		return "Failed to connect to server, connection aborted\n";
	return "Source registered\n";
}
char *Cmd_MVDConnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (!*arg[1])
		return "adddemo requires an filename parameter\n";

	if (!localcommand)
		if (*arg[1] == '\\' || *arg[1] == '/' || strstr(arg[1], "..") || arg[1][1] == ':')
			return "Absolute paths are prohibited.\n";

	memmove(arg[1]+5, arg[1], ARG_LEN-6);
	strncpy(arg[1], "file:", 5);

	if (!QTV_NewServerConnection(cluster, arg[1], arg[2], false, false, false))
		return "Failed to connect to server, connection aborted\n";
	return "Source registered\n";
}
char *Cmd_Exec(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	FILE *f;
	char line[512], *res;

	if (!localcommand)
		if (*arg[1] == '\\' || *arg[1] == '/' || strstr(arg[1], "..") || arg[1][1] == ':')
			return "Absolute paths are prohibited.\n";

	f = fopen(arg[1], "rt");
	if (!f)
	{
		snprintf(buffer, sizeofbuffer, "Couldn't exec \"%s\"\n", arg[1]);
		return buffer;
	}
	else
	{
		while(fgets(line, sizeof(line)-1, f))
		{
			res = Rcon_Command(cluster, qtv, line, buffer, sizeofbuffer, localcommand);
			Sys_Printf(cluster, "%s", res);	//this is perhaps wrong.
		}
		fclose(f);
		return "Execed\n";
	}
}

void catbuffer(char *buffer, int bufsize, char *format, ...)
{
	va_list		argptr;
	char		string[1024];

	va_start (argptr, format);
	vsnprintf (string,sizeof(string)-1, format,argptr);
	va_end (argptr);

	Q_strncatz(buffer, string, bufsize);
}
char *Cmd_Status(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{

	buffer[0] = '\0';

	catbuffer(buffer, sizeofbuffer, "%i sources\n", cluster->numservers);
	catbuffer(buffer, sizeofbuffer, "%i viewers\n", cluster->numviewers);
	catbuffer(buffer, sizeofbuffer, "%i proxies\n", cluster->numproxies);

	catbuffer(buffer, sizeofbuffer, "Options:\n");
	catbuffer(buffer, sizeofbuffer, " Hostname %s\n", cluster->hostname);
	
	if (cluster->chokeonnotupdated)
		catbuffer(buffer, sizeofbuffer, " Choke\n");
	if (cluster->lateforward)
		catbuffer(buffer, sizeofbuffer, " Late forwarding\n");
	if (!cluster->notalking)
		catbuffer(buffer, sizeofbuffer, " Talking allowed\n");
	if (cluster->nobsp)
		catbuffer(buffer, sizeofbuffer, " No BSP loading\n");
	catbuffer(buffer, sizeofbuffer, "\n");


	if (qtv)
	{
		catbuffer(buffer, sizeofbuffer, "Selected server: %s\n", qtv->server);
		if (qtv->file)
			catbuffer(buffer, sizeofbuffer, "Playing from file\n");
		if (qtv->sourcesock != INVALID_SOCKET)
			catbuffer(buffer, sizeofbuffer, "Connected\n");
		if (qtv->parsingqtvheader || qtv->parsingconnectiondata)
			catbuffer(buffer, sizeofbuffer, "Waiting for gamestate\n");
		if (qtv->controller)
		{
			catbuffer(buffer, sizeofbuffer, "Spectating through %s\n");
		}
		if (*qtv->modellist[1].name)
		{
			catbuffer(buffer, sizeofbuffer, "Map name %s\n", qtv->modellist[1].name);
		}
		if (*qtv->connectpassword)
			catbuffer(buffer, sizeofbuffer, "Using a password\n");

		if (qtv->tcpsocket != INVALID_SOCKET)
		{
			catbuffer(buffer, sizeofbuffer, "Listening for proxies (%i)\n", qtv->tcplistenportnum);
		}

		if (qtv->bsp)
		{
			catbuffer(buffer, sizeofbuffer, "BSP (%s) is loaded\n", qtv->mapname);
		}
	}

	return buffer;
}
char *Cmd_Choke(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	cluster->chokeonnotupdated = !!atoi(arg[1]);
	return "choke-until-update set\n";
}
char *Cmd_Late(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	cluster->lateforward = !!atoi(arg[1]);
	return "late forwarding set\n";
}
char *Cmd_Talking(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	cluster->notalking = !atoi(arg[1]);
	return "talking permissions set\n";
}
char *Cmd_NoBSP(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	cluster->nobsp = !!atoi(arg[1]);
	return "nobsp will change at start of next map\n";
}

char *Cmd_MaxViewers(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	cluster->maxviewers = atoi(arg[2]);
	return "maxviewers set\n";
}
char *Cmd_MaxProxies(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	cluster->maxproxies = atoi(arg[2]);
	return "maxproxies set\n";
}


char *Cmd_Ping(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	netadr_t addr;
	if (NET_StringToAddr(arg[1], &addr, 27500))
	{
		NET_SendPacket (cluster, cluster->qwdsocket, 1, "k", addr);
		return "pinged\n";
	}
	return "couldn't resolve\n";
}

char *Cmd_Help(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	return HELPSTRING;
}

char *Cmd_Echo(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	return "Poly wants a cracker.\n";
}
	
char *Cmd_Quit(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (!localcommand)
		return "Remote shutdown refused.\n";
	cluster->wanttoexit = true;
	return "Shutting down.\n";
}

















char *Cmd_Streams(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	catbuffer(buffer, sizeofbuffer, "Streams:\n");

	for (qtv = cluster->servers; qtv; qtv = qtv->next)
	{
		catbuffer(buffer, sizeofbuffer, "%i: %s\n", qtv->streamid, qtv->server);
	}
	return buffer;
}




char *Cmd_Disconnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	QTV_Shutdown(qtv);
	return "Disconnected\n";
}

char *Cmd_Record(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (!*arg[1])
		return "record requires a filename on the proxy's machine\n";

	if (!localcommand)
		if (*arg[1] == '\\' || *arg[1] == '/' || strstr(arg[1], "..") || arg[1][1] == ':')
			return "Absolute paths are prohibited.\n";

	if (Net_FileProxy(qtv, arg[1]))
		return "Recording to disk\n";
	else
		return "Failed to open file\n";
}
char *Cmd_Stop(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (Net_StopFileProxy(qtv))
		return "stopped\n";
	else
		return "not recording to disk\n";
}

char *Cmd_Reconnect(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	if (QTV_Connect(qtv, qtv->server))
		return "Reconnected\n";
	else
		return "Failed to reconnect (will keep trying)\n";
}

char *Cmd_MVDPort(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	int news;
	int newp = atoi(arg[1]);

	if (!newp)
	{
		if (qtv->tcpsocket != INVALID_SOCKET)
		{
			closesocket(qtv->tcpsocket);
			qtv->tcpsocket = INVALID_SOCKET;
			qtv->tcplistenportnum = 0;

			return "mvd port is now closed\n";
		}
		return "Already closed\n";
	}
	else
	{
		news = Net_MVDListen(newp);

		if (news != INVALID_SOCKET)
		{
			if (qtv->tcpsocket != INVALID_SOCKET)
				closesocket(qtv->tcpsocket);
			qtv->tcpsocket = news;
			qtv->disconnectwhennooneiswatching = false;
			qtv->tcplistenportnum = newp;
			return "Opened tcp port\n";
		}
		else
			return "Failed to open tcp port\n";
	}
}

char *Cmd_Commands(cluster_t *cluster, sv_t *qtv, char *arg[MAX_ARGS], char *buffer, int sizeofbuffer, qboolean localcommand)
{
	return "";
}

typedef struct rconcommands_s {
	char *name;
	qboolean serverspecific;	//works within a qtv context
	qboolean clusterspecific;	//works without a qtv context (ignores context)
	dispatchrconcommand_t func;
} rconcommands_t;

const rconcommands_t rconcommands[] =
{
	{"exec",		1, 1, Cmd_Exec},
	{"status",		1, 1, Cmd_Status},

	{"help",		0, 1, Cmd_Help},
	{"commands",	0, 1, Cmd_Commands},
	{"hostname",	0, 1, Cmd_Hostname},
	{"master",		0, 1, Cmd_Master},
	{"udpport",		0, 1, Cmd_UDPPort},
	 {"port",		0, 1, Cmd_UDPPort},
	{"adminpassword",0, 1, Cmd_AdminPassword},
	 {"rconpassword",0, 1, Cmd_AdminPassword},
	{"qtv",			0, 1, Cmd_QTVConnect},
	 {"addserver",	0, 1, Cmd_QTVConnect},
	 {"connect",	0, 1, Cmd_QTVConnect},
	{"qw",			0, 1, Cmd_QWConnect},
	 {"observe",	0, 1, Cmd_QWConnect},
	{"demo",		0, 1, Cmd_MVDConnect},
	 {"playdemo",	0, 1, Cmd_MVDConnect},
	{"choke",		0, 1, Cmd_Choke},
	{"late",		0, 1, Cmd_Late},
	{"talking",		0, 1, Cmd_Talking},
	{"nobsp",		0, 1, Cmd_NoBSP},
	{"maxviewers",	0, 1, Cmd_MaxViewers},
	{"maxproxies",	0, 1, Cmd_MaxProxies},
	{"ping",		0, 1, Cmd_Ping},
	{"reconnect",	0, 1, Cmd_Reconnect},
	{"echo",		0, 1, Cmd_Echo},
	{"quit",		0, 1, Cmd_Quit},
	{"streams",		0, 1, Cmd_Streams},




	{"disconnect",	1, 0, Cmd_Disconnect},
	{"record",		1, 0, Cmd_Record},
	{"stop",		1, 0, Cmd_Stop},
	{"tcpport",		1, 0, Cmd_MVDPort},
	 {"mvdport",	1, 0, Cmd_MVDPort},
	 
	{NULL}
};

char *Rcon_Command(cluster_t *cluster, sv_t *qtv, char *command, char *buffer, int sizeofbuffer, qboolean localcommand)
{
#define TOKENIZE_PUNCTUATION ""

	int i;
	char arg[MAX_ARGS][ARG_LEN];
	char *argptrs[MAX_ARGS];
	char *sid;

	for (sid = command; *sid; sid = sid++)
	{
		if (*sid == ':')
			break;
		if (*sid < '0' || *sid > '9')
			break;
	}
	if (*sid == ':')
	{
		i = atoi(command);
		command = sid+1;

		for (qtv = cluster->servers; qtv; qtv = qtv->next)
			if (qtv->streamid == i)
				break;
	}



	for (i = 0; i < MAX_ARGS; i++)
	{
		command = COM_ParseToken(command, arg[i], ARG_LEN, TOKENIZE_PUNCTUATION);
		argptrs[i] = arg[i];
	}

	if (!qtv && cluster->numservers==1)
		qtv = cluster->servers;

	buffer[0] = 0;

	if (qtv)
	{	//if there is a specific connection targetted

		for (i = 0; rconcommands[i].name; i++)
		{
			if (rconcommands[i].serverspecific)
				if (!strcmp(rconcommands[i].name, argptrs[0]))
					return rconcommands[i].func(cluster, qtv, argptrs, buffer, sizeofbuffer, localcommand);
		}
	}

	for (i = 0; rconcommands[i].name; i++)
	{
		if (!strcmp(rconcommands[i].name, argptrs[0]))
		{
			if (rconcommands[i].clusterspecific)
				return rconcommands[i].func(cluster, NULL, argptrs, buffer, sizeofbuffer, localcommand);
			else if (rconcommands[i].serverspecific)
			{
				snprintf(buffer, sizeofbuffer, "Command \"%s\" requires a targeted server.\n", arg[0]);
				return buffer;
			}
		}
	}


	snprintf(buffer, sizeofbuffer, "Command \"%s\" not recognised.\n", arg[0]);
	return buffer;
}