#include "xmpp.h"
#ifdef JINGLE
static struct c2c_s *JCL_JingleAddContentToSession(jclient_t *jcl, struct c2c_s *c2c, char *with, bresource_t *bres, qboolean creator, char *sid, char *cname, int method, int mediatype)
{
	struct icestate_s *ice = NULL;
	char generatedname[64];
	char stunhost[256];
	int c;

	if (!bres)
		return NULL;

	//make sure we can add more contents to this session
	//block dupe content names.
	if (c2c)
	{
		if (c2c->contents == sizeof(c2c->content) / sizeof(c2c->content[0]))
			return NULL;
		for (c = 0; c < c2c->contents; c++)
			if (!strcmp(c2c->content[c].name, cname))
				return NULL;
	}
	
	if (piceapi)
		ice = piceapi->ICE_Create(NULL, sid, with, method, mediatype);
	if (ice)
	{
		piceapi->ICE_Get(ice, "sid", generatedname, sizeof(generatedname));
		sid = generatedname;

		//the controlling role MUST be assumed by the initiator and the controlled role MUST be assumed by the responder
		piceapi->ICE_Set(ice, "controller", creator?"1":"0");

		if (creator && mediatype == ICEP_VOICE)
		{
			//note: the engine will ignore codecs it does not support.
			piceapi->ICE_Set(ice, "codec96", "speex@16000");	//wide
			piceapi->ICE_Set(ice, "codec97", "speex@8000");		//narrow
			piceapi->ICE_Set(ice, "codec98", "speex@32000");	//ultrawide
			piceapi->ICE_Set(ice, "codec99", "opus");
		}
	}
	else
		return NULL;	//no way to get the local ip otherwise, which means things won't work proper

	if (!c2c)
	{
		c2c = malloc(sizeof(*c2c) + strlen(sid));
		memset(c2c, 0, sizeof(*c2c));
		c2c->next = jcl->c2c;
		jcl->c2c = c2c;
		strcpy(c2c->sid, sid);	//safe due to trailing space.

		c2c->with = strdup(with);
		c2c->peercaps = bres->caps;
		c2c->creator = creator;
	}

	Q_strlcpy(c2c->content[c2c->contents].name, cname, sizeof(c2c->content[c2c->contents].name));
	c2c->content[c2c->contents].method = method;
	c2c->content[c2c->contents].mediatype = mediatype;

	//copy out the interesting parameters
	c2c->content[c2c->contents].ice = ice;
	c2c->contents++;


	//query dns to see if there's a stunserver hosted by the same domain
	//nslookup -querytype=SRV _stun._udp.example.com
	//for msn, live.com has one, messanger.live.com has one, but messenger.live.com does NOT. seriously, the typo has more services.  wtf microsoft?
	//google doesn't provide a stun srv entry
	//facebook doesn't provide a stun srv entry
	Q_snprintf(stunhost, sizeof(stunhost), "_stun._udp.%s", jcl->domain);
	if (NET_DNSLookup_SRV(stunhost, stunhost, sizeof(stunhost)))
		piceapi->ICE_Set(ice, "stunip", stunhost);
	else
	{
		//there is no real way to query stun servers from the xmpp server.
		//while there is some extdisco extension (aka: the 'standard' way), it has some huge big fat do-not-implement message (and googletalk does not implement it).
		//google provide their own jingleinfo extension. Which also has some huge fat 'do-not-implement' message. hardcoding the address should provide equivelent longevity. :(
		//google also don't provide stun srv records.
		//so we're basically screwed if we want to work with the googletalk xmpp service long term.
		//more methods are best, I suppose, but I'm lazy.
		//yes, hardcoding means that other services might 'borrow' googles' stun servers.
		piceapi->ICE_Set(ice, "stunport", "19302");
		piceapi->ICE_Set(ice, "stunip", "stun.l.google.com");
	}
	return c2c;
}
static qboolean JCL_JingleAcceptAck(jclient_t *jcl, xmltree_t *tree, struct iq_s *iq)
{
	int i;
	struct c2c_s *c2c;
	if (tree)
	{
		for (c2c = jcl->c2c; c2c; c2c = c2c->next)
		{
			if (c2c == iq->usrptr)
			{
				for (i = 0; i < c2c->contents; i++)
				{
					if (c2c->content[i].ice)
						piceapi->ICE_Set(c2c->content[i].ice, "state", STRINGIFY(ICE_CONNECTING));
				}
			}
		}
	}
	return true;
}


static void JCL_PopulateAudioDescription(xmltree_t *description, struct icestate_s *ice)
{
	xmltree_t *payload;
	int i;
	for (i = 96; i <= 127; i++)
	{
		char codecname[64];
		char argn[64];
		Q_snprintf(argn, sizeof(argn), "codec%i", i);
		piceapi->ICE_Get(ice, argn,  codecname, sizeof(codecname));

		if (!strcmp(codecname, "speex@8000"))
		{	//speex narrowband
			payload = XML_CreateNode(description, "payload-type", "", "");
			XML_AddParameter(payload, "channels", "1");
			XML_AddParameter(payload, "clockrate", "8000");
			XML_AddParameter(payload, "id", argn+5);
			XML_AddParameter(payload, "name", "speex");
		}
		else if (!strcmp(codecname, "speex@16000"))
		{	//speex wideband
			payload = XML_CreateNode(description, "payload-type", "", "");
			XML_AddParameter(payload, "channels", "1");
			XML_AddParameter(payload, "clockrate", "16000");
			XML_AddParameter(payload, "id", argn+5);
			XML_AddParameter(payload, "name", "speex");
		}
		else if (!strcmp(codecname, "speex@32000"))
		{	//speex ultrawideband
			payload = XML_CreateNode(description, "payload-type", "", "");
			XML_AddParameter(payload, "channels", "1");
			XML_AddParameter(payload, "clockrate", "32000");
			XML_AddParameter(payload, "id", argn+5);
			XML_AddParameter(payload, "name", "speex");
		}
		else if (!strcmp(codecname, "opus"))
		{	//opus codec.
			payload = XML_CreateNode(description, "payload-type", "", "");
			XML_AddParameter(payload, "channels", "1");
			XML_AddParameter(payload, "id", argn+5);
			XML_AddParameter(payload, "name", "opus");
		}
	}
}

enum
{
	JE_ACKNOWLEDGE,
	JE_UNSUPPORTED,
	JE_OUTOFORDER,
	JE_TIEBREAK,
	JE_UNKNOWNSESSION,
	JE_UNSUPPORTEDINFO
};
static void JCL_JingleError(jclient_t *jcl, xmltree_t *tree, char *from, char *id, int type)
{
	switch(type)
	{
	case JE_ACKNOWLEDGE:
		JCL_AddClientMessagef(jcl,
			"<iq type='result' to='%s' id='%s' />", from, id);
		break;
	case JE_UNSUPPORTED:
		JCL_AddClientMessagef(jcl,
				"<iq id='%s' to='%s' type='error'>"
				  "<error type='cancel'>"
					"<bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
				  "</error>"
				"</iq>", id, from);
		break;
	case JE_UNKNOWNSESSION:
		JCL_AddClientMessagef(jcl,
				"<iq id='%s' to='%s' type='error'>"
				  "<error type='modify'>"
					"<item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
					"<unknown-session xmlns='urn:xmpp:jingle:errors:1'/>"
				  "</error>"
				"</iq>", id, from);
		break;
	case JE_UNSUPPORTEDINFO:
		JCL_AddClientMessagef(jcl,
				"<iq id='%s' to='%s' type='error'>"
				  "<error type='modify'>"
					"<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
					"<unsupported-info xmlns='urn:xmpp:jingle:errors:1'/>"
				  "</error>"
				"</iq>", id, from);
		break;
	}
}

/*
sends a jingle message to the peer.
action should be one of multiple things:
session-terminate	- totally not acceptable. this also closes the c2c
session-accept		- details are okay. this also begins ice polling (on iq ack, once we're sure the peer got our message)

(internally generated) transport-replace	- details are okay, except we want a different transport method.
*/
static qboolean JCL_JingleSend(jclient_t *jcl, struct c2c_s *c2c, char *action)
{
	qboolean result;
	xmltree_t *jingle;
	int c;
//	struct icestate_s *ice = c2c->ice;
	qboolean wasaccept = false;
	int transportmode = ICEM_ICE;
#ifdef VOIP_LEGACY
	int cap = c2c->peercaps;
#endif

	if (!c2c->contents)
		action = "session-terminate";

#ifdef VOIP_LEGACY
	if ((cap & CAP_GOOGLE_VOICE) && !(cap & CAP_VOICE))
	{
		//legacy crap for google compatibility.
		if (!strcmp(action, "session-initiate"))
		{
			xmltree_t *session = XML_CreateNode(NULL, "session", "http://www.google.com/session", "");
			xmltree_t *description = XML_CreateNode(session, "description", "http://www.google.com/session/phone", "");

			XML_AddParameter(session, "id", c2c->sid);
			XML_AddParameter(session, "initiator", jcl->jid);
			XML_AddParameter(session, "type", "initiate");
			for (c = 0; c < c2c->contents; c++)
				JCL_PopulateAudioDescription(description, c2c->content[c].ice);

			JCL_SendIQNode(jcl, NULL, "set", c2c->with, session, true);
		}
		else if (!strcmp(action, "session-accept"))
		{
			xmltree_t *session = XML_CreateNode(NULL, "session", "http://www.google.com/session", "");
			xmltree_t *description = XML_CreateNode(session, "description", "http://www.google.com/session/phone", "");

			XML_AddParameter(session, "id", c2c->sid);
			XML_AddParameter(session, "initiator", c2c->with);
			XML_AddParameter(session, "type", "accept");
			for (c = 0; c < c2c->contents; c++)
				JCL_PopulateAudioDescription(description, c2c->content[c].ice);

			JCL_SendIQNode(jcl, JCL_JingleAcceptAck, "set", c2c->with, session, true)->usrptr = c2c;
			c2c->accepted = true;
		}
		else if (!strcmp(action, "transport-info"))
		{
/*FIXME
			struct icecandinfo_s *ca;
			while ((ca = piceapi->ICE_GetLCandidateInfo(ice)))
			{
				xmltree_t *session = XML_CreateNode(NULL, "session", "http://www.google.com/session", "");

				XML_AddParameter(session, "id", c2c->sid);
				XML_AddParameter(session, "initiator", c2c->creator?jcl->jid:c2c->with);
				XML_AddParameter(session, "type", "candidates");

				//one per message, apparently
				if (ca)
				{
					char *ctypename[]={"local", "stun", "stun", "relay"};
					char *pref[]={"1", "0.9", "0.5", "0.2"};
					xmltree_t *candidate = XML_CreateNode(session, "candidate", "", "");
					char pwd[64];
					char uname[64];

					piceapi->ICE_Get(ice, "lufrag",  uname, sizeof(uname));
					piceapi->ICE_Get(ice, "lpwd",  pwd, sizeof(pwd));

					XML_AddParameter(candidate, "address", ca->addr);
					XML_AddParameteri(candidate, "port", ca->port);
					XML_AddParameter(candidate, "name", (ca->component==1)?"rtp":"rtcp");
					XML_AddParameter(candidate, "username", uname);
					XML_AddParameter(candidate, "password", pwd);
					XML_AddParameter(candidate, "preference", pref[ca->type]);	//FIXME: c->priority
					XML_AddParameter(candidate, "protocol", "udp");
					XML_AddParameter(candidate, "type", ctypename[ca->type]);
					XML_AddParameteri(candidate, "generation", ca->generation);
					XML_AddParameteri(candidate, "network", ca->network);
				}

				JCL_SendIQNode(jcl, NULL, "set", c2c->with, session, true);
			}
*/Con_Printf("No idea how to write candidates for gingle\n");
		}
		else if (!strcmp(action, "session-terminate"))
		{
			struct c2c_s **link;
			xmltree_t *session = XML_CreateNode(NULL, "session", "http://www.google.com/session", "");

			XML_AddParameter(session, "id", c2c->sid);
			XML_AddParameter(session, "initiator", c2c->creator?jcl->jid:c2c->with);
			XML_AddParameter(session, "type", "terminate");
			JCL_SendIQNode(jcl, NULL, "set", c2c->with, session, true);

			for (link = &jcl->c2c; *link; link = &(*link)->next)
			{
				if (*link == c2c)
				{
					*link = c2c->next;
					break;
				}
			}
			for (c = 0; c < c2c->contents; c++)
			{
				if (c2c->content[c].ice)
					piceapi->ICE_Close(c2c->content[c].ice);
				c2c->content[c].ice = NULL;
			}

			return false;
		}
		else
		{
			return false;
		}
		return true;
	}
#endif

	jingle = XML_CreateNode(NULL, "jingle", "urn:xmpp:jingle:1", "");
	XML_AddParameter(jingle, "sid", c2c->sid);

	if (!strcmp(action, "session-initiate"))
	{	//these attributes are meant to only be present in initiate. for call forwarding etc. which we don't properly support.
		XML_AddParameter(jingle, "initiator", jcl->jid);
	}

	if (!strcmp(action, "session-terminate"))
	{
		struct c2c_s **link;
		for (link = &jcl->c2c; *link; link = &(*link)->next)
		{
			if (*link == c2c)
			{
				*link = c2c->next;
				break;
			}
		}
		for (c = 0; c < c2c->contents; c++)
		{
			if (c2c->content[c].ice)
				piceapi->ICE_Close(c2c->content[c].ice);
			c2c->content[c].ice = NULL;
		}

		result = false;
	}
	else
	{
		result = true;
		for (c = 0; c < c2c->contents; c++)
		{
			xmltree_t *content = XML_CreateNode(jingle, "content", "", "");
			struct icestate_s *ice = c2c->content[c].ice;
			XML_AddParameter(content, "name", c2c->content[c].name);

			if (!strcmp(action, "session-accept"))
			{
				if (c2c->content[c].method == transportmode)
					XML_AddParameter(jingle, "responder", jcl->jid);
				else
					action = "transport-replace";
			}

			{
				xmltree_t *description;
				xmltree_t *transport;
				if (transportmode == ICEM_RAW)
				{
					transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:raw-udp:1", "");
					{
						xmltree_t *candidate;
						struct icecandinfo_s *b = NULL;
						struct icecandinfo_s *c;
						while ((c = piceapi->ICE_GetLCandidateInfo(ice)))
						{
							if (!b || b->priority < c->priority)
								b = c;
						}
						if (b)
						{
							candidate = XML_CreateNode(transport, "candidate", "", "");
							XML_AddParameter(candidate, "ip", b->addr);
							XML_AddParameteri(candidate, "port", b->port);
							XML_AddParameter(candidate, "id", b->candidateid);
							XML_AddParameteri(candidate, "generation", b->generation);
							XML_AddParameteri(candidate, "component", b->component);
						}
					}
				}
				else if (transportmode == ICEM_ICE)
				{
					char val[64];
					transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:ice-udp:1", "");
					piceapi->ICE_Get(ice, "lufrag",  val, sizeof(val));
					XML_AddParameter(transport, "ufrag", val);
					piceapi->ICE_Get(ice, "lpwd",  val, sizeof(val));
					XML_AddParameter(transport, "pwd", val);
					{
						struct icecandinfo_s *c;
						while ((c = piceapi->ICE_GetLCandidateInfo(ice)))
						{
							char *ctypename[]={"host", "srflx", "prflx", "relay"};
							xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", "");
							XML_AddParameter(candidate, "type", ctypename[c->type]);
							XML_AddParameter(candidate, "protocol", "udp");	//is this not just a little bit redundant? ice-udp? seriously?
							XML_AddParameteri(candidate, "priority", c->priority);
							XML_AddParameteri(candidate, "port", c->port);
							XML_AddParameteri(candidate, "network", c->network);
							XML_AddParameter(candidate, "ip", c->addr);
							XML_AddParameter(candidate, "id", c->candidateid);
							XML_AddParameteri(candidate, "generation", c->generation);
							XML_AddParameteri(candidate, "foundation", c->foundation);
							XML_AddParameteri(candidate, "component", c->component);
						}
					}
				}
				if (strcmp(action, "transport-info"))
				{
#ifdef VOIP
					if (c2c->content[c].mediatype == ICEP_VOICE)
					{
						XML_AddParameter(content, "senders", "both");
						XML_AddParameter(content, "creator", "initiator");

						description = XML_CreateNode(content, "description", "urn:xmpp:jingle:apps:rtp:1", "");
						XML_AddParameter(description, "media", MEDIATYPE_AUDIO);

						JCL_PopulateAudioDescription(description, ice);
					}
					else if (c2c->content[c].mediatype == ICEP_VIDEO)
					{
						XML_AddParameter(content, "senders", "both");
						XML_AddParameter(content, "creator", "initiator");

						description = XML_CreateNode(content, "description", "urn:xmpp:jingle:apps:rtp:1", "");
						XML_AddParameter(description, "media", MEDIATYPE_VIDEO);

						//JCL_PopulateAudioDescription(description, ice);
					}
#endif
					else
					{
						description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, "");
						XML_AddParameter(description, "media", MEDIATYPE_QUAKE);
						if (c2c->content[c].mediatype == ICEP_QWSERVER)
							XML_AddParameter(description, "host", "me");
						else if (c2c->content[c].mediatype == ICEP_QWCLIENT)
							XML_AddParameter(description, "host", "you");
					}
				}
			}
		}
		if (!strcmp(action, "session-accept"))
			c2c->accepted = wasaccept = true;
	}

	XML_AddParameter(jingle, "action", action);

//	Con_Printf("Sending Jingle:\n");
//	XML_ConPrintTree(jingle, 1);
	JCL_SendIQNode(jcl, wasaccept?JCL_JingleAcceptAck:NULL, "set", c2c->with, jingle, true)->usrptr = c2c;

	return result;
}

void JCL_JingleTimeouts(jclient_t *jcl, qboolean killall)
{
	int c;
	struct c2c_s *c2c;
	for (c2c = jcl->c2c; c2c; c2c = c2c->next)
	{
		for (c = 0; c < c2c->contents; c++)
		{
			if (c2c->content[c].method == ICEM_ICE)
			{
				char bah[2];
				piceapi->ICE_Get(c2c->content[c].ice, "newlc", bah, sizeof(bah));
				if (atoi(bah))
				{
					Con_DPrintf("Sending updated local addresses\n");
					JCL_JingleSend(jcl, c2c, "transport-info");
					break;
				}
			}
		}
	}
}

void JCL_Join(jclient_t *jcl, char *target, char *sid, qboolean allow, int protocol)
{
	struct c2c_s *c2c = NULL, **link;
	char autotarget[256];
	buddy_t *b;
	bresource_t *br;
	int c;
	if (!jcl)
		return;

	if (!JCL_FindBuddy(jcl, target, &b, &br, true))
	{
		Con_Printf("user/resource not known\n");
		return;
	}
	if (!br)
		br = b->defaultresource;
	if (!br)
		br = b->resources;

	if (!strchr(target, '/'))
	{
		if (!br)
		{
			Con_Printf("User name not valid\n");
			return;
		}
		Q_snprintf(autotarget, sizeof(autotarget), "%s/%s", b->accountdomain, br->resource);
		target = autotarget;
	}

	for (link = &jcl->c2c; *link && !c2c; link = &(*link)->next)
	{
		if (!strcmp((*link)->with, target) && (!sid || !strcmp((*link)->sid, sid)))
		{
			if (protocol == ICEP_INVALID)
				c2c = *link;
			else
			{
				for (c = 0; c < (*link)->contents; c++)
					if ((*link)->content[c].mediatype == protocol)
						c2c = *link;
			}
		}
	}
	if (allow)
	{
		if (!c2c)
		{
			if (!sid)
			{
				char convolink[512], hanguplink[512];
				if (protocol == ICEP_INVALID)
					protocol = ICEP_QWCLIENT;
				c2c = JCL_JingleAddContentToSession(jcl, NULL, target, br, true, sid, "foobar2000", DEFAULTICEMODE, protocol);
				JCL_JingleSend(jcl, c2c, "session-initiate");

				JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, target, NULL, NULL, "%s", target);
				JCL_GenLink(jcl, hanguplink, sizeof(hanguplink), "jdeny", target, NULL, c2c->sid, "%s", "Hang Up");
				XMPP_ConversationPrintf(b->accountdomain, b->name, "%s %s %s.\n",  protocol==ICEP_VOICE?"Calling":"Requesting session with", convolink, hanguplink);
			}
			else
				XMPP_ConversationPrintf(b->accountdomain, b->name, "That session has expired.\n");
		}
		else if (c2c->creator)
		{
			char convolink[512];
			//resend initiate if they've not acked it... I dunno...
			JCL_JingleSend(jcl, c2c, "session-initiate");
			JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, target, NULL, NULL, "%s", target);
			XMPP_ConversationPrintf(b->accountdomain, b->name, "Restarting session with %s.\n", convolink);
		}
		else if (c2c->accepted)
			XMPP_ConversationPrintf(b->accountdomain, b->name, "That session was already accepted.\n");
		else
		{
			char convolink[512];
			JCL_JingleSend(jcl, c2c, "session-accept");
			JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, target, NULL, NULL, "%s", target);
			XMPP_ConversationPrintf(b->accountdomain, b->name, "Accepting session from %s.\n", convolink);
		}
	}
	else
	{
		if (c2c)
		{
			char convolink[512];
			JCL_JingleSend(jcl, c2c, "session-terminate");
			JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, target, NULL, NULL, "%s", target);
			XMPP_ConversationPrintf(b->accountdomain, b->name, "Terminating session with %s.\n", convolink);
		}
		else
			XMPP_ConversationPrintf(b->accountdomain, b->name, "That session has already expired.\n");
	}
}

static void JCL_JingleParsePeerPorts(jclient_t *jcl, struct c2c_s *c2c, xmltree_t *inj, char *from, char *sid)
{
	xmltree_t *incontent;
	xmltree_t *intransport;
	xmltree_t *incandidate;
	struct icecandinfo_s rem;
	struct icestate_s *ice;
	int i, contid;
	char *cname;

	if (!c2c->sid)
		return;

	if (strcmp(c2c->with, from) || strcmp(c2c->sid, sid))
	{
		Con_Printf("%s is trying to mess with our connections...\n", from);
		return;
	}

	//a message can contain multiple contents
	for (contid = 0; ; contid++)
	{
		incontent = XML_ChildOfTree(inj, "content", contid);
		if (!incontent)
			break;
		cname = XML_GetParameter(incontent, "name", "");

		//find which content this node refers to.
		ice = NULL;
		for (i = 0; i < c2c->contents; i++)
			if (!strcmp(c2c->content[i].name, cname))
				ice = c2c->content[i].ice;
		if (!ice)
		{
			//err... this content doesn't exist?
			continue;
		}

		intransport = XML_ChildOfTree(incontent, "transport", 0);
		if (!intransport)
			continue;	//err, I guess it wasn't a transport update then (or related).

		piceapi->ICE_Set(ice, "rufrag", XML_GetParameter(intransport, "ufrag", ""));
		piceapi->ICE_Set(ice, "rpwd", XML_GetParameter(intransport, "pwd", ""));

		for (i = 0; (incandidate = XML_ChildOfTree(intransport, "candidate", i)); i++)
		{
			char *s;
			memset(&rem, 0, sizeof(rem));
			Q_strlcpy(rem.addr, XML_GetParameter(incandidate, "ip", ""), sizeof(rem.addr));
			Q_strlcpy(rem.candidateid, XML_GetParameter(incandidate, "id", ""), sizeof(rem.candidateid));

			s = XML_GetParameter(incandidate, "type", "");
			if (s && !strcmp(s, "srflx"))
				rem.type = ICE_SRFLX;
			else if (s && !strcmp(s, "prflx"))
				rem.type = ICE_PRFLX;
			else if (s && !strcmp(s, "relay"))
				rem.type = ICE_RELAY;
			else
				rem.type = ICE_HOST;
			rem.port = atoi(XML_GetParameter(incandidate, "port", "0"));
			rem.priority = atoi(XML_GetParameter(incandidate, "priority", "0"));
			rem.network = atoi(XML_GetParameter(incandidate, "network", "0"));
			rem.generation = atoi(XML_GetParameter(incandidate, "generation", "0"));
			rem.foundation = atoi(XML_GetParameter(incandidate, "foundation", "0"));
			rem.component = atoi(XML_GetParameter(incandidate, "component", "0"));
			s = XML_GetParameter(incandidate, "protocol", "udp");
			if (s && !strcmp(s, "udp"))
				rem.transport = 0;
			else
				rem.transport = 0;
			piceapi->ICE_AddRCandidateInfo(ice, &rem);
		}
	}
}
#ifdef VOIP_LEGACY
static void JCL_JingleParsePeerPorts_GoogleSession(jclient_t *jcl, struct c2c_s *c2c, xmltree_t *inj, char *from, char *sid)
{
	xmltree_t *incandidate;
	struct icecandinfo_s rem;
	int i;
	int c;

	if (strcmp(c2c->with, from) || strcmp(c2c->sid, sid))
	{
		Con_Printf("%s is trying to mess with our connections...\n", from);
		return;
	}

	if (!c2c->sid)
		return;

	//with google's session api, every session uses a single set of ports.
	for (i = 0; (incandidate = XML_ChildOfTree(inj, "candidate", i)); i++)
	{
		char *s;
		memset(&rem, 0, sizeof(rem));
		Q_strlcpy(rem.addr, XML_GetParameter(incandidate, "address", ""), sizeof(rem.addr));
//		Q_strlcpy(rem.candidateid, XML_GetParameter(incandidate, "id", ""), sizeof(rem.candidateid));

		s = XML_GetParameter(incandidate, "type", "");
		if (s && !strcmp(s, "stun"))
			rem.type = ICE_SRFLX;
		else if (s && !strcmp(s, "stun"))
			rem.type = ICE_PRFLX;
		else if (s && !strcmp(s, "relay"))
			rem.type = ICE_RELAY;
		else
			rem.type = ICE_HOST;
		rem.port = atoi(XML_GetParameter(incandidate, "port", "0"));
		rem.priority = atoi(XML_GetParameter(incandidate, "priority", "0"));
		rem.network = atoi(XML_GetParameter(incandidate, "network", "0"));
		rem.generation = atoi(XML_GetParameter(incandidate, "generation", "0"));
		rem.foundation = atoi(XML_GetParameter(incandidate, "foundation", "0"));
		s = XML_GetParameter(incandidate, "name", "rtp");
		if (!strcmp(s, "rtp"))
			rem.component = 1;
		else if (!strcmp(s, "rtcp"))
			rem.component = 2;
		else
			rem.component = 0;
		s = XML_GetParameter(incandidate, "protocol", "udp");
		if (s && !strcmp(s, "udp"))
			rem.transport = 0;
		else
			rem.transport = 0;

		for (c = 0; c < c2c->contents; c++)
			if (c2c->content[c].ice)
				piceapi->ICE_AddRCandidateInfo(c2c->content[c].ice, &rem);
	}
}
static qboolean JCL_JingleHandleInitiate_GoogleSession(jclient_t *jcl, xmltree_t *inj, char *from)
{
	xmltree_t *indescription = XML_ChildOfTree(inj, "description", 0);
	char *descriptionxmlns = indescription?indescription->xmlns:"";
	char *sid = XML_GetParameter(inj, "id", "");

	qboolean accepted = false;
	char *response = "terminate";
	char *offer = "pwn you";
	char *autocvar = "xmpp_autoaccepthax";
	char *initiator;

	struct c2c_s *c2c = NULL;
	int mt = ICEP_INVALID;
	buddy_t *b;
	bresource_t *br;

	//FIXME: add support for session forwarding so that we might forward the connection to the real server. for now we just reject it.
	initiator = XML_GetParameter(inj, "initiator", "");
	if (strcmp(initiator, from))
		return false;

	if (indescription && !strcmp(descriptionxmlns, "http://www.google.com/session/phone"))
	{
		mt = ICEP_VOICE;
		offer = "is trying to call you";
		autocvar = "xmpp_autoacceptvoice";
	}
	if (mt == ICEP_INVALID)
		return false;

	if (!JCL_FindBuddy(jcl, from, &b, &br, true))
		return false;

	//FIXME: if both people try to establish a connection to the other simultaneously, the higher session id is meant to be canceled, and the lower accepted automagically.

	c2c = JCL_JingleAddContentToSession(jcl, NULL, from, br, false,
		sid, "the mystical magical content name",
		ICEM_ICE,
		mt
		);
	if (c2c)
	{
		int c = c2c->contents-1;
		c2c->peercaps = CAP_GOOGLE_VOICE;

		if (mt == ICEP_VOICE)
		{
			qboolean okay = false;
			int i = 0;
			xmltree_t *payload;
			//chuck it at the engine and see what sticks. at least one must...
			while((payload = XML_ChildOfTree(indescription, "payload-type", i++)))
			{
				char *name = XML_GetParameter(payload, "name", "");
				char *clock = XML_GetParameter(payload, "clockrate", "");
				char *id = XML_GetParameter(payload, "id", "");
				char parm[64];
				char val[64];
				//note: the engine will ignore codecs it does not support, returning false.
				if (!strcasecmp(name, "SPEEX"))
				{
					Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id));
					Q_snprintf(val, sizeof(val), "speex@%i", atoi(clock));
					okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, val);
				}
				else if (!strcasecmp(name, "OPUS"))
				{
					Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id));
					okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, "opus");
				}
			}
			//don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something.
			//we really ought to give a reason, but we're rude.
			if (!okay)
			{
				char convolink[512];
				JCL_JingleSend(jcl, c2c, "terminate");
				JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name);
				XMPP_ConversationPrintf(b->accountdomain, b->name, "%s does not support any compatible audio codecs, and is unable to call you.\n", convolink);
				return false;
			}
		}

		if (mt != ICEP_INVALID)
		{
			char convolink[512];
			JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name);
			if (!pCvar_GetFloat(autocvar))
			{
				char authlink[512];
				char denylink[512];
				JCL_GenLink(jcl, authlink, sizeof(authlink), "jauth", from, NULL, sid, "%s", "Accept");
				JCL_GenLink(jcl, denylink, sizeof(denylink), "jdeny", from, NULL, sid, "%s", "Reject");

				//show a prompt for it, send the reply when the user decides.
				XMPP_ConversationPrintf(b->accountdomain, b->name,
						"%s %s. %s %s\n", convolink, offer, authlink, denylink);
				pCon_SetActive(b->name);
				return true;
			}
			else
			{
				XMPP_ConversationPrintf(b->accountdomain, b->name, "Auto-accepting session from %s\n", convolink);
				response = "accept";
			}
		}
	}
	JCL_JingleSend(jcl, c2c, response);
	return true;
}
#endif
static struct c2c_s *JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char *from)
{
	char *sid = XML_GetParameter(inj, "sid", "");

	qboolean okay;
	char *initiator;

	struct c2c_s *c2c = NULL;
	int mt = ICEP_INVALID;
	int i, c;
	buddy_t *b;
	bresource_t *br;

	//FIXME: add support for session forwarding so that we might forward the connection to the real server. for now we just reject it.
	initiator = XML_GetParameter(inj, "initiator", "");
	if (strcmp(initiator, from))
		return NULL;

	//reject it if we don't know them.
	if (!JCL_FindBuddy(jcl, from, &b, &br, false))
		return NULL;

	for (i = 0; ; i++)
	{
		xmltree_t *incontent = XML_ChildOfTree(inj, "content", i);
		char *cname = XML_GetParameter(incontent, "name", "");
		xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0);
		xmltree_t *indescription = XML_ChildOfTree(incontent, "description", 0);
		char *transportxmlns = intransport?intransport->xmlns:"";
		char *descriptionxmlns = indescription?indescription->xmlns:"";
		char *descriptionmedia = XML_GetParameter(indescription, "media", "");
		if (!incontent)
			break;

		mt = ICEP_INVALID;

		if (incontent && !strcmp(descriptionmedia, MEDIATYPE_QUAKE) && !strcmp(descriptionxmlns, QUAKEMEDIAXMLNS))
		{
			char *host = XML_GetParameter(indescription, "host", "you");
			if (!strcmp(host, "you"))
				mt = ICEP_QWSERVER;
			else if (!strcmp(host, "me"))
				mt = ICEP_QWCLIENT;
		}
		if (incontent && !strcmp(descriptionmedia, MEDIATYPE_AUDIO) && !strcmp(descriptionxmlns, "urn:xmpp:jingle:apps:rtp:1"))
			mt = ICEP_VOICE;
		if (incontent && !strcmp(descriptionmedia, MEDIATYPE_VIDEO) && !strcmp(descriptionxmlns, "urn:xmpp:jingle:apps:rtp:1"))
			mt = ICEP_VIDEO;

		if (mt == ICEP_INVALID)
			continue;


		c2c = JCL_JingleAddContentToSession(jcl, NULL, from, br, false, sid, cname,
			strcmp(transportxmlns, "urn:xmpp:jingle:transports:raw-udp:1")?ICEM_ICE:ICEM_RAW,
			mt
			);
		if (!c2c)
			continue;
		c = c2c->contents-1;

		okay = false;
		if (mt == ICEP_VOICE)
		{
			int i = 0;
			xmltree_t *payload;
			//chuck it at the engine and see what sticks. at least one must...
			while((payload = XML_ChildOfTree(indescription, "payload-type", i++)))
			{
				char *name = XML_GetParameter(payload, "name", "");
				char *clock = XML_GetParameter(payload, "clockrate", "");
				char *id = XML_GetParameter(payload, "id", "");
				char parm[64];
				char val[64];
				//note: the engine will ignore codecs it does not support, returning false.
				if (!strcasecmp(name, "SPEEX"))
				{
					Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id));
					Q_snprintf(val, sizeof(val), "speex@%i", atoi(clock));
					okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, val);
				}
				else if (!strcasecmp(name, "OPUS"))
				{
					Q_snprintf(parm, sizeof(parm), "codec%i", atoi(id));
					okay |= piceapi->ICE_Set(c2c->content[c].ice, parm, "opus");
				}
			}
		}
		else
			okay = true;
		//don't do it if we couldn't successfully set any codecs, because the engine doesn't support the ones that were listed, or something.
		//we really ought to give a reason, but we're rude.
		if (!okay)
		{
			char convolink[512];
			JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name);
			XMPP_ConversationPrintf(b->accountdomain, b->name, "%s does not support any compatible codecs, and is unable to call you.\n", convolink);

			if (c2c->content[c].ice)
				piceapi->ICE_Close(c2c->content[c].ice);
			c2c->content[c].ice = NULL;
			c2c->contents--;
		}
	}
	if (!c2c)
		return NULL;

	if (!c2c->contents)
	{
		if (jcl->c2c == c2c)
		{
			jcl->c2c = c2c->next;
			free(c2c);
		}
		else
			Con_Printf("^1error in "__FILE__" %i\n", __LINE__);
		return NULL;
	}

	//if they speak this, we never want to speak gingle at them!
	c2c->peercaps &= ~(CAP_GOOGLE_VOICE);

	JCL_JingleParsePeerPorts(jcl, c2c, inj, from, sid);
	return c2c;
}

static qboolean JCL_JingleHandleSessionTerminate(jclient_t *jcl, xmltree_t *tree, struct c2c_s *c2c, struct c2c_s **link, buddy_t *b)
{
	xmltree_t *reason = XML_ChildOfTree(tree, "reason", 0);
	int c;
	if (!c2c)
	{
		XMPP_ConversationPrintf(b->accountdomain, b->name, "Received session-terminate without an active session\n");
		return false;
	}

	if (reason && reason->child)
		XMPP_ConversationPrintf(b->accountdomain, b->name, "Session ended: %s\n", reason->child->name);
	else
		XMPP_ConversationPrintf(b->accountdomain, b->name, "Session ended\n");

	//unlink it
	for (link = &jcl->c2c; *link; link = &(*link)->next)
	{
		if (*link == c2c)
		{
			*link = c2c->next;
			break;
		}
	}

//	XML_ConPrintTree(tree, 0);

	for (c = 0; c < c2c->contents; c++)
		if (c2c->content[c].ice)
			piceapi->ICE_Close(c2c->content[c].ice);
	free(c2c);
	return true;
}
static qboolean JCL_JingleHandleSessionAccept(jclient_t *jcl, xmltree_t *tree, char *from, struct c2c_s *c2c, buddy_t *b)
{
	//peer accepted our session
	//make sure it actually was ours, and not theirs. sneaky sneaky.
	//will generally contain some port info.
	if (!c2c)
	{
		Con_DPrintf("Unknown session acceptance\n");
		return false;
	}
	else if (!c2c->creator)
	{
		Con_DPrintf("Peer tried to accept a session that *they* created!\n");
		return false;
	}
	else if (c2c->accepted)
	{
		//pidgin is buggy and can dupe-accept sessions multiple times.
		Con_DPrintf("Duplicate session-accept from peer.\n");

		//XML_ConPrintTree(tree, 0);
		return false;
	}
	else
	{
		char *responder = XML_GetParameter(tree, "responder", from);
		int c;
		if (strcmp(responder, from))
		{
			return false;
		}
		XMPP_ConversationPrintf(b->accountdomain, b->name, "Session Accepted!\n");
//			XML_ConPrintTree(tree, 0);

		JCL_JingleParsePeerPorts(jcl, c2c, tree, from, XML_GetParameter(tree, "sid", ""));
		c2c->accepted = true;

		//if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted
		for (c = 0; c < c2c->contents; c++)
			if (c2c->content[c].ice)
				piceapi->ICE_Set(c2c->content[c].ice, "state", STRINGIFY(ICE_CONNECTING));
	}
	return true;
}
#ifdef VOIP_LEGACY
qboolean JCL_HandleGoogleSession(jclient_t *jcl, xmltree_t *tree, char *from, char *iqid)
{
	char *sid = XML_GetParameter(tree, "id", "");
	char *type = XML_GetParameter(tree, "type", "");

	struct c2c_s *c2c = NULL, **link;
	buddy_t *b;

	//validate+find sender
	for (link = &jcl->c2c; *link; link = &(*link)->next)
	{
		if (!strcmp((*link)->sid, sid))
		{
			c2c = *link;
			if (!c2c->accepted)
				break;
		}
	}
	if (!JCL_FindBuddy(jcl, from, &b, NULL, true))
		return false;
	if (c2c && strcmp(c2c->with, from))
	{
		Con_Printf("%s is trying to mess with our connections...\n", from);
		return false;
	}

	Con_Printf("google session with %s:\n", from);
	XML_ConPrintTree(tree, "", 0);

	if (!strcmp(type, "accept"))
	{
		if (!JCL_JingleHandleSessionAccept(jcl, tree, from, c2c, b))
			return false;
	}
	else if (!strcmp(type, "initiate"))
	{
		if (!JCL_JingleHandleInitiate_GoogleSession(jcl, tree, from))
			return false;
	}
	else if (!strcmp(type, "terminate"))
	{
		JCL_JingleHandleSessionTerminate(jcl, tree, c2c, link, b);
	}
	else if (!strcmp(type, "candidates"))
	{
		if (!c2c)
			return false;
		JCL_JingleParsePeerPorts_GoogleSession(jcl, c2c, tree, from, sid);
	}
	else
	{
		Con_Printf("Unknown google session action: %s\n", type);
//		XML_ConPrintTree(tree, 0);
		return false;
	}

	JCL_AddClientMessagef(jcl,
		"<iq type='result' to='%s' id='%s' />", from, iqid);
	return true;
}
#endif
qboolean JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id)
{
	char *action = XML_GetParameter(tree, "action", "");
	char *sid = XML_GetParameter(tree, "sid", "");

	struct c2c_s *c2c = NULL, **link;
	buddy_t *b;

	for (link = &jcl->c2c; *link; link = &(*link)->next)
	{
		if (!strcmp((*link)->sid, sid))
		{
			c2c = *link;
			if (!c2c->accepted)
				break;
		}
	}

	if (!JCL_FindBuddy(jcl, from, &b, NULL, true))
		return false;

	//validate sender
	if (c2c && strcmp(c2c->with, from))
	{
		Con_Printf("%s is trying to mess with our connections...\n", from);
		return false;
	}

	//FIXME: transport-info, transport-replace
	if (!strcmp(action, "session-terminate"))
	{
		if (c2c)
		{
			JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);
			JCL_JingleHandleSessionTerminate(jcl, tree, c2c, link, b);
		}
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "content-accept"))
	{
		//response from content-add
		if (c2c)
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "content-add"))
	{
		//FIXME: must send content-reject
		if (c2c)
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "content-modify"))
	{
		//send an error to reject it
		JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
	}
	else if (!strcmp(action, "content-reject"))
	{
		//response from content-add. an error until we actually generate content-adds...
		if (c2c)
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "content-remove"))
	{
		if (c2c)
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "description-info"))
	{
		//The description-info action is used to send informational hints about parameters related to the application type, such as the suggested height and width of a video display area or suggested configuration for an audio stream.
		//just ack and ignore it
		if (c2c)
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "security-info"))
	{
		//The security-info action is used to send information related to establishment or maintenance of security preconditions.
		//no security mechanisms supported...
		if (c2c)
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "session-info"))
	{
		if (tree->child)
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTEDINFO);
		else
			JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);	//serves as a ping.
	}

	else if (!strcmp(action, "transport-info"))
	{	//peer wants to add ports.
		if (c2c)
		{
			JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);
			JCL_JingleParsePeerPorts(jcl, c2c, tree, from, sid);
		}
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
//FIXME: we need to add support for this to downgrade to raw if someone tries calling through a SIP gateway
	else if (!strcmp(action, "transport-replace"))
	{
		if (c2c)
		{
			if (1)
			{
				JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);
				JCL_JingleSend(jcl, c2c, "transport-reject");
			}
			else
			{
				JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);
				JCL_JingleParsePeerPorts(jcl, c2c, tree, from, sid);
				JCL_JingleSend(jcl, c2c, "transport-accept");
			}
		}
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "transport-reject"))
	{
		JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);
		JCL_JingleSend(jcl, c2c, "session-terminate");
	}
	else if (!strcmp(action, "session-accept"))
	{
		if (c2c)
		{
			//response from a message we sent.
			if (JCL_JingleHandleSessionAccept(jcl, tree, from, c2c, b))
				JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);
			else
				JCL_JingleError(jcl, tree, from, id, JE_OUTOFORDER);
		}
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNKNOWNSESSION);
	}
	else if (!strcmp(action, "session-initiate"))
	{
//		Con_Printf("Peer initiating connection!\n");
//		XML_ConPrintTree(tree, 0);

		

		c2c = JCL_JingleHandleInitiate(jcl, tree, from);

		if (c2c)
		{
			qboolean voice = false, video = false, server = false, client = false;
			char *offer;
			char convolink[512];
			int c;
			qboolean doprompt = false;
			JCL_JingleError(jcl, tree, from, id, JE_ACKNOWLEDGE);

			for (c = 0; c < c2c->contents; c++)
			{
				switch(c2c->content[c].mediatype)
				{
				case ICEP_INVALID:																			break;
				case ICEP_VOICE:	voice = true; 	doprompt |= !pCvar_GetFloat("xmpp_autoacceptvoice");	break;
				case ICEP_VIDEO:	video = true; 	doprompt |= !pCvar_GetFloat("xmpp_autoacceptvoice");	break;
				case ICEP_QWSERVER: server = true; 	doprompt |= !pCvar_GetFloat("xmpp_autoacceptjoins");	break;
				case ICEP_QWCLIENT: client = true; 	doprompt |= !pCvar_GetFloat("xmpp_autoacceptinvites");	break;
				default:							doprompt |= true;										break;
				}
			}

			if (video)
			{
				if (server)
					offer = "wants to join your game (with ^1video^7)";
				else if (client)
					offer = "wants to invite you to thier game (with ^1video^7)";
				else
					offer = "is trying to ^1video^7 call you";
			}
			else if (voice)
			{
				if (server)
					offer = "wants to join your game (with voice)";
				else if (client)
					offer = "wants to invite you to thier game (with voice)";
				else
					offer = "is trying to call you";
			}
			else
			{
				if (server)
					offer = "wants to join your game";
				else if (client)
					offer = "wants to invite you to thier game";
				else
					offer = "is trying to waste your time";
			}

			JCL_GenLink(jcl, convolink, sizeof(convolink), NULL, from, NULL, NULL, "%s", b->name);
			if (doprompt)
			{
				char authlink[512];
				char denylink[512];
				JCL_GenLink(jcl, authlink, sizeof(authlink), "jauth", from, NULL, sid, "%s", "Accept");
				JCL_GenLink(jcl, denylink, sizeof(denylink), "jdeny", from, NULL, sid, "%s", "Reject");

				//show a prompt for it, send the reply when the user decides.
				XMPP_ConversationPrintf(b->accountdomain, b->name,
						"%s %s. %s %s\n", convolink, offer, authlink, denylink);
				pCon_SetActive(b->accountdomain);
			}
			else
			{
				XMPP_ConversationPrintf(b->accountdomain, b->name, "Auto-accepting session from %s\n", convolink);
				JCL_Join(jcl, from, sid, true, ICEP_INVALID);
			}
		}
		else
			JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
	}
	else
	{
		JCL_JingleError(jcl, tree, from, id, JE_UNSUPPORTED);
	}
	return true;
}
#endif