#include "rtsp-media.h"
#include "sdp.h"
#include "sdp-options.h"
#include "sdp-a-fmtp.h"
#include "sdp-a-rtpmap.h"
#include "sys/path.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>

static inline int scopy(struct rtsp_media_t* medias, char** dst, const char* src)
{
	int n;
	n = snprintf(medias->ptr + medias->offset, sizeof(medias->ptr) - medias->offset, "%s", src);
	if (n < 0 || n >= (int)sizeof(medias->ptr) - medias->offset)
		return -1;
	*dst = medias->ptr + medias->offset;
	medias->offset += n + 1; // with '\0'
	return 0;
}

//static inline int vscopy(struct rtsp_media_t* medias, char** dst, const char* fmt, ...)
//{
//	int n;
//	va_list args;
//	va_start(args, fmt);
//	n = vsnprintf(medias->ptr + medias->offset, sizeof(medias->ptr) - medias->offset, fmt, args);
//	va_end(args);
//
//	if (n < 0 || n >= (int)sizeof(medias->ptr) - medias->offset)
//		return -1;
//	*dst = medias->ptr + medias->offset;
//	medias->offset += n + 1; // with '\0'
//	return 0;
//}
//
//static inline int rtsp_media_aggregate_control_enable(void *sdp)
//{
//	const char* control;
//
//	// rfc 2326 C.1.1 Control URL (p80)
//	// If found at the session level, the attribute indicates the URL for aggregate control
//	control = sdp_attribute_find(sdp, "control");
//	return (control && *control) ? 1 : 0;
//}

// rfc 2326 C.1.1 Control URL (p81)
// look for a base URL in the following order:
// 1. The RTSP Content-Base field
// 2. The RTSP Content-Location field
// 3. The RTSP request URL
int rtsp_media_set_url(struct rtsp_media_t* m, const char* base, const char* location, const char* request)
{
	int r;
	char buffer[256] = { 0 };

	// C.1.1 Control URL (p81)
	// If this attribute contains only an asterisk (*), then the URL is
	// treated as if it were an empty embedded URL, and thus inherits the entire base URL.
	if (m->session_uri[0] && '*' != m->session_uri[0])
	{
		snprintf(buffer, sizeof(buffer)-1, "%s", m->session_uri);
		r = path_resolve2(m->session_uri, sizeof(m->session_uri)-1, buffer, base, location, request);
	}
	else if('*' == m->session_uri[0])
	{
		r = snprintf(m->session_uri, sizeof(m->session_uri) - 1, "%s", request);
	}
	else
	{
		// keep session uri empty
		r = 0;
	}

	if ('*' != m->uri[0])
	{
		snprintf(buffer, sizeof(buffer) - 1, "%s", m->uri);
		r = path_resolve2(m->uri, sizeof(m->uri) - 1, buffer, base, location, request);
	}
	else
	{
		r = snprintf(m->uri, sizeof(m->uri) - 1, "%s", request);
	}

	return r >= 0 ? 0 : r;
}

// RFC 6184 RTP Payload Format for H.264 Video
// 8.2.1. Mapping of Payload Type Parameters to SDP
// m=video 49170 RTP/AVP 98
// a=rtpmap:98 H264/90000
// a=fmtp:98 profile-level-id=42A01E;
//			 packetization-mode=1;
//			 sprop-parameter-sets=<parameter sets data>
static void rtsp_media_onattr(void* param, const char* name, const char* value)
{
	int i, n;
	int payload = -1;
	struct rtsp_media_t* media;

	media = (struct rtsp_media_t*)param;

	if (name)
	{
		if (0 == strcmp("rtpmap", name))
		{
			int rate = 0;
			char channel[64];
			char encoding[16 + sizeof(media->avformats[i].encoding)];
			if (strlen(value) < sizeof(encoding)) // make sure encoding have enough memory space
			{
				channel[0] = '\0';
				sdp_a_rtpmap(value, &payload, encoding, &rate, channel);
				for (i = 0; i < media->avformat_count; i++)
				{
					if (media->avformats[i].fmt == payload)
					{
						media->avformats[i].rate = rate;
						media->avformats[i].channel = *channel ? atoi(channel) : 1; // default 1-channel
						snprintf(media->avformats[i].encoding, sizeof(media->avformats[i].encoding), "%s", encoding);
						break;
					}
				}
			}
		}
		else if (0 == strcmp("fmtp", name))
		{
			n = (int)strlen(value);
			payload = atoi(value);
			for (i = 0; i < media->avformat_count && media->offset + n + 1 < sizeof(media->ptr); i++)
			{
				if (media->avformats[i].fmt != payload)
					continue;

				media->avformats[i].fmtp = media->ptr + media->offset;
				strcpy(media->avformats[i].fmtp, value);
				media->offset += n + 1;
				//if(0 == strcmp("H264", media->avformats[i].encoding))
				//{
				//	struct sdp_a_fmtp_h264_t h264;
				//	memset(&h264, 0, sizeof(h264));
				//	sdp_a_fmtp_h264(value, &payload, &h264);
				//	if(h264.flags & SDP_A_FMTP_H264_SPROP_PARAMETER_SETS)
				//		snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h264.sprop_parameter_sets);
				//}
				//else if (0 == strcmp("H265", media->avformats[i].encoding))
				//{
				//	struct sdp_a_fmtp_h265_t h265;
				//	memset(&h265, 0, sizeof(h265));
				//	sdp_a_fmtp_h265(value, &payload, &h265);
				//	//if (h265.flags & SDP_A_FMTP_H265_SPROP_VPS)
				//	//	snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h265.sprop_vps);
				//	//if (h265.flags & SDP_A_FMTP_H265_SPROP_SPS)
				//	//	snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h265.sprop_sps); 
				//	//if (h265.flags & SDP_A_FMTP_H265_SPROP_PPS)
				//	//	snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h265.sprop_pps);
				//}
				//else if(0 == strcmp("mpeg4-generic", media->avformats[i].encoding))
				//{
				//	struct sdp_a_fmtp_mpeg4_t mpeg4;
				//	memset(&mpeg4, 0, sizeof(mpeg4));
				//	sdp_a_fmtp_mpeg4(value, &payload, &mpeg4);
				//}
				break;
			}
		}
		else if (0 == strcmp("etag", name))
		{
			// C.1.8 Entity Tag
		}
		else if (0 == strcmp("rtcp", name))
		{
			// rfc3605 Real Time Control Protocol (RTCP) attribute in Session Description Protocol (SDP)
			// "a=rtcp:" port [nettype space addrtype space connection-address] CRLF
			// a=rtcp:53020 IN IP6 2001:2345:6789:ABCD:EF01:2345:6789:ABCD
			assert(media->nport <= 2 && sizeof(media->port)/sizeof(media->port[0]) >= 2);
			if (1 == media->nport)
				media->nport++;
			media->port[1] = atoi(value);

			// TODO: rtcp address
		}
		else if (0 == strcmp("rtcp-mux", name))
		{
			if (1 == media->nport)
				media->nport++;
			media->port[1] = media->port[0];
		}
		else if (0 == strcmp("rtcp-xr", name))
		{
			// rfc3611 RTP Control Protocol Extended Reports (RTCP XR)
			// "a=rtcp-xr:" [xr-format *(SP xr-format)] CRLF
		}
		else if (0 == strcmp("ice-pwd", name))
		{
			scopy(media, &media->ice.pwd, value);
		}
		else if (0 == strcmp("ice-ufrag", name))
		{
			scopy(media, &media->ice.ufrag, value);
		}
		else if (0 == strcmp("ice-lite", name))
		{
			media->ice.lite = 1;
		}
		else if (0 == strcmp("ice-mismatch", name))
		{
			media->ice.mismatch = 1;
		}
		else if (0 == strcmp("ice-pacing", name))
		{
			media->ice.pacing = atoi(value);
		}
		else if (0 == strcmp("candidate", name))
		{
			if (media->ice.candidate_count + 1 < sizeof(media->ice.candidates) / sizeof(media->ice.candidates[0]) && media->offset + sizeof(*media->ice.candidates[0]) <= sizeof(media->ptr))
			{
				media->ice.candidates[media->ice.candidate_count] = (struct sdp_candidate_t*)(media->ptr + media->offset);
				if (7 == sscanf(value, "%32s %hu %7s %u %63s %hu typ %7s%n",
					media->ice.candidates[media->ice.candidate_count]->foundation,
					&media->ice.candidates[media->ice.candidate_count]->component,
					media->ice.candidates[media->ice.candidate_count]->transport,
					&media->ice.candidates[media->ice.candidate_count]->priority,
					media->ice.candidates[media->ice.candidate_count]->address,
					&media->ice.candidates[media->ice.candidate_count]->port,
					media->ice.candidates[media->ice.candidate_count]->candtype, &n))
				{
					sscanf(value + n, " raddr %63s rport %hu", media->ice.candidates[media->ice.candidate_count]->reladdr, &media->ice.candidates[media->ice.candidate_count]->relport);
					media->offset += sizeof(*media->ice.candidates[0]);
					++media->ice.candidate_count;
				}
			}
		}
		else if (0 == strcmp("remote-candidates", name))
		{
			while (media->ice.remote_count + 1 < sizeof(media->ice.remotes) / sizeof(media->ice.remotes[0]) && media->offset + sizeof(*media->ice.remotes[0]) <= sizeof(media->ptr))
			{
				media->ice.remotes[media->ice.remote_count] = (struct sdp_candidate_t*)(media->ptr + media->offset);
				if (!value || 3 != sscanf(value, "%hu %63s %hu%n", &media->ice.remotes[media->ice.remote_count]->component, media->ice.remotes[media->ice.remote_count]->address, &media->ice.remotes[media->ice.remote_count]->port, &n))
					break;

				value += n;
				++media->ice.remote_count;
				media->offset += sizeof(*media->ice.remotes[0]);
			}
		}
		else if (0 == strcmp("setup", name))
		{
			media->setup = sdp_option_setup_from(value);
		}
		else if (0 == strcmp("ssrc", name))
		{
			media->ssrc.ssrc = (uint32_t)strtoul(value, NULL, 10);
			// TODO: ssrc attribute
		}
		else if (0 == strcmp("ssrc-group", name))
		{
			// TODO
		}
	}
}

/*
v=0
o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4
s=SDP Seminar
i=A Seminar on the session description protocol
u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps
e=mjh@isi.edu (Mark Handley)
c=IN IP4 224.2.17.12/127
t=2873397496 2873404696
a=recvonly
m=audio 3456 RTP/AVP 0
m=video 2232 RTP/AVP 31
m=whiteboard 32416 UDP WB
a=orient:portrait
*/
int rtsp_media_sdp(const char* s, int len, struct rtsp_media_t* medias, int count)
{
	int i, j, n;
	int formats[16];
	const char* control;
	const char* start, *stop;
	const char* iceufrag, *icepwd;
	const char* username, *session, *version;
	const char* network, *addrtype, *address, *source;
	struct rtsp_media_t* m;
	struct rtsp_header_range_t range;
	sdp_t* sdp;

	sdp = sdp_parse(s, len);
	if (!sdp)
		return -1;

	// rfc 2326 C.1.1 Control URL (p80)
	// If found at the session level, the attribute indicates the URL for aggregate control
	control = sdp_attribute_find(sdp, "control");

	// C.1.5 Range of presentation
	// The "a=range" attribute defines the total time range of the stored session.
	memset(&range, 0, sizeof(range));
	s = sdp_attribute_find(sdp, "range");
	if(s) rtsp_header_range(s, &range);

	// C.1.6 Time of availability
	start = stop = NULL;
	for (i = 0; i < sdp_timing_count(sdp); i++)
	{
		sdp_timing_get(sdp, i, &start, &stop);
	}

	// C.1.7 Connection Information
	network = addrtype = source = NULL;
	if (0 != sdp_connection_get(sdp, &network, &addrtype, &source) || 0 == strcmp("0.0.0.0", source))
		sdp_origin_get(sdp, &username, &session, &version, &network, &addrtype, &source);

	// session ice-ufrag/ice-pwd
	iceufrag = sdp_attribute_find(sdp, "ice-ufrag");
	icepwd = sdp_attribute_find(sdp, "ice-pwd");

	for (i = 0; i < sdp_media_count(sdp) && i < count; i++)
	{
		m = medias + i;
		memset(m, 0, sizeof(struct rtsp_media_t));
		memcpy(&m->range, &range, sizeof(m->range));
		if (control)
			snprintf(m->session_uri, sizeof(m->session_uri), "%s", control);
		if (start && stop)
		{
			m->start = strtoull(start, NULL, 10);
			m->stop = strtoull(stop, NULL, 10);
		}
        
		if(0 == sdp_media_get_connection(sdp, i, &network, &addrtype, &address))
        {
			if (0 == strcmp("IP4", addrtype) && 0 == strcmp("0.0.0.0", address) && source && *source)
				address = source;
            snprintf(m->source, sizeof(m->source), "%s", source && *source ? source : "");
            snprintf(m->network, sizeof(m->network), "%s", network);
            snprintf(m->address, sizeof(m->address), "%s", address);
            snprintf(m->addrtype, sizeof(m->addrtype), "%s", addrtype);
        }
		//media->cseq = rand();
		
		m->nport = sdp_media_port(sdp, i, m->port, sizeof(m->port)/sizeof(m->port[0]));
		snprintf(m->media, sizeof(m->media), "%s", sdp_media_type(sdp, i));
		snprintf(m->proto, sizeof(m->proto), "%s", sdp_media_proto(sdp, i));
		if (1 == m->nport && 0 == strncmp("RTP/", m->proto, 4))
			m->port[m->nport++] = m->port[0] + 1;

		// media control url
		s = sdp_media_attribute_find(sdp, i, "control");
		if(s)
			snprintf(m->uri, sizeof(m->uri), "%s", s);

		// media format
		j = sizeof(m->avformats) / sizeof(m->avformats[0]);
		assert(sizeof(formats) / sizeof(formats[0]) >= j);
		n = sdp_media_formats(sdp, i, formats, j);
		m->avformat_count = n > j ? j : n;
		for (j = 0; j < m->avformat_count; j++)
			m->avformats[j].fmt = formats[j];

		// TODO: plan-B streams
		m->mode = sdp_media_mode(sdp, i);
		m->setup = SDP_A_SETUP_NONE;

		// update media encoding
		sdp_media_attribute_list(sdp, i, NULL, rtsp_media_onattr, m);

		// use default ice-ufrag/pwd
		if(NULL == m->ice.ufrag && iceufrag)
			scopy(medias, &m->ice.ufrag, iceufrag);
		if(NULL == m->ice.pwd && icepwd)
			scopy(medias, &m->ice.pwd, icepwd);
	}

	count = sdp_media_count(sdp);
	sdp_destroy(sdp);
	return count; // should check return value
}

/// @return -0-no media, >0-ok, <0-error
int rtsp_media_to_sdp(const struct rtsp_media_t* m, char* line, int bytes)
{
	int i, n;
	int setup = 0;
	int port = m->port[0];

	if (SDP_M_PROTO_TEST_TCP(sdp_option_proto_from(m->proto)))
	{
		// try to set tcp active for sender side
		setup = (SDP_A_SETUP_NONE == m->setup || SDP_A_SETUP_ACTPASS == m->setup) ? SDP_A_SETUP_ACTIVE : m->setup;
		//if (SDP_A_SETUP_PASSIVE == setup || SDP_A_SETUP_ACTPASS == setup)
		//	port = options->m[i].port[0];
	}

	n = snprintf(line, bytes, "m=%s %d %s", m->media, port, m->proto);
	for (i = 0; i < m->avformat_count; i++)
	{
		if (m->avformats[i].fmt >= 96 && !m->avformats[i].encoding[0])
			continue; // ignore empty encoding
		n += snprintf(line + n, bytes - n, " %d", m->avformats[i].fmt);
	}
	n += snprintf(line + n, bytes - n, "\n");

	for (i = 0; i < m->avformat_count && n >= 0 && n < bytes; i++)
	{
		if (!m->avformats[i].encoding[0])
			continue;

		if (SDP_M_MEDIA_VIDEO == sdp_option_media_from(m->media))
		{
			n += snprintf(line + n, bytes - n, "a=rtpmap:%d %s/%d\n", m->avformats[i].fmt, m->avformats[i].encoding, m->avformats[i].rate ? m->avformats[i].rate : 90000);
			if(n >= 0 && n < bytes && m->avformats[i].fmtp && m->avformats[i].fmtp[0])
				n += snprintf(line + n, bytes - n, "a=fmtp:%s\n", m->avformats[i].fmtp);
		}
		else if (SDP_M_MEDIA_AUDIO == sdp_option_media_from(m->media))
		{
			if(m->avformats[i].channel > 0)
				n += snprintf(line + n, bytes - n, "a=rtpmap:%d %s/%d/%d\n", m->avformats[i].fmt, m->avformats[i].encoding, m->avformats[i].rate, m->avformats[i].channel);
			else
				n += snprintf(line + n, bytes - n, "a=rtpmap:%d %s/%d\n", m->avformats[i].fmt, m->avformats[i].encoding, m->avformats[i].rate);

			if (n >= 0 && n < bytes && m->avformats[i].fmtp && m->avformats[i].fmtp[0])
				n += snprintf(line + n, bytes - n, "a=fmtp:%s\n", m->avformats[i].fmtp);
		}
	}

	//for (int j = 0; j < 128 && j < 8 * sizeof(m->payloads) / sizeof(m->payloads[0]); j++)
	//{
	//	if(m->payloads[j/8] & (1<<(j%8)))
	//		n += snprintf(answer+n, sizeof(answer)-n, " %d", j);
	//}

	if (SDP_M_PROTO_TEST_TCP(sdp_option_proto_from(m->proto)))
	{
		n += snprintf(line + n, bytes - n, "a=setup:%s\n", sdp_option_setup_to(setup));
	}

	if (m->nport < 2 || m->port[0] == m->port[1])
	{
		n += snprintf(line + n, bytes - n, "a=rtcp-mux\n");
	}

	n += snprintf(line + n, bytes - n, "a=%s\n", sdp_option_mode_to(m->mode));

	if (m->ssrc.ssrc)
	{
		n += snprintf(line + n, bytes - n, "a=ssrc:%u\n", m->ssrc.ssrc);
	}

	return n > 0 && n < bytes ? n : -1;
}
