#include <map>
#include <memory>
#include <string>
#include "sockutil.h"
#include "sys/atomic.h"
#include "sys/thread.h"
#include "sys/system.h"
#include "sys/pollfd.h"
#include "port/network.h"
#include "port/ip-route.h"
#include "sip-uac.h"
#include "sip-uas.h"
#include "sip-message.h"
#include "sip-transport.h"
#include "sip-timer.h"
#include "port/ip-route.h"
#include "http-parser.h"
#include "http-header-auth.h"
#include "rtp.h"
#include "rtp-profile.h"
#include "rtp-payload.h"
#include "mov-format.h"
#include "mpeg4-aac.h"
#include "mpeg4-avc.h"
#include "mpeg4-hevc.h"
#include "stun-proto.h"
#include "ice-agent.h"
#include "stun-agent.h"
#include "uri-parse.h"
#include "cstringext.h"
#include "base64.h"
#include "time64.h"
#include "sdp.h"
#include "md5.h"
#include "aio-timeout.h"
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>

#include "rtsp-media.h"
#include "rtp-sender.h"
#include "../test/ice-transport.h"
#include "../test/media/mp4-file-reader.h"

#define SIP_PWD "1234"
#define SIP_HOST "119.23.15.234"
#define SIP_FROM "sip:1002@127.0.0.1:5061"
#define SIP_PEER "sip:1001@192.168.1.100"
#define SIP_EXPIRED 60
#define TURN_SERVER NULL
#define TURN_USR "test"
#define TURN_PWD "123456"

extern "C" void rtp_receiver_test(socket_t rtp[2], const char* peer, int peerport[2], int payload, const char* encoding);
static int rtp_packet_send(void* param, const void *packet, int bytes, uint32_t timestamp, int flags);

struct sip_uac_transport_address_t
{
	socket_t udp;
	socket_t tcp;
	socklen_t addrlen;
	struct sockaddr_storage addr;
};

struct sip_uac_test2_t;
struct sip_uac_test2_session_t : public VodFileSource::IListener
{
	struct sip_uac_test2_t *ctx;
    char buffer[2 * 1024 * 1024];
	std::string user;
	std::string from;
	
	struct sip_uas_transaction_t* t;
	std::shared_ptr<struct sip_uac_transaction_t> tuac;
	
	struct ice_transport_t* avt;
	struct sip_uac_transport_address_t transport;

	struct rtsp_media_t medias[3]; // peer sdp media
	int nmedia;

	struct av_media_t
	{
		int stream; // stream index, base 0
		int connected;
		struct sip_uac_test2_session_t* s;

		char sdp[1204];
		struct rtsp_media_t* m;
		int fmt;

		enum AVPACKET_CODEC_ID codec;
		void* decoder;

		struct rtp_sender_t sender;
		time64_t clock;

		int track;
		FILE* fp;

		union
		{
			struct mpeg4_aac_t aac;
			struct mpeg4_avc_t avc;
			struct mpeg4_hevc_t hevc;
		} u;
	} audio, video;

	bool running;
	pthread_t th;
	std::shared_ptr<AVPacketQueue> pkts;
	std::shared_ptr<VodFileSource> source;

	virtual int OnPacket(struct avpacket_t* pkt) {
		return pkts->Push(pkt);
	}
};

struct sip_uac_test2_t
{
	bool running;
	char usr[64];
	char local[65];

	socket_t udp;
	socket_t tcp;
	struct sip_agent_t* sip;
	struct sip_uac_transport_address_t transport; // for register
	struct http_header_www_authenticate_t auth;
	int nonce_count;
	char callid[64];
	int cseq;
    
	ThreadLocker locker;
	typedef std::map<std::string, std::shared_ptr<sip_uac_test2_session_t> > TSessions;
	TSessions sessions;
};

static int sip_uac_transport_via(void* transport, const char* destination, char protocol[16], char local[128], char dns[128])
{
	int r;
	char ip[65];
	u_short port;
	struct uri_t* uri;

	struct sip_uac_transport_address_t *t = (struct sip_uac_transport_address_t *)transport;

	// rfc3263 4.1 Selecting a Transport Protocol
	// 1. If the URI specifies a transport protocol in the transport parameter,
	//    that transport protocol SHOULD be used. Otherwise, if no transport 
	//    protocol is specified, but the TARGET(maddr) is a numeric IP address, 
	//    the client SHOULD use UDP for a SIP URI, and TCP for a SIPS URI.
	// 2. if no transport protocol is specified, and the TARGET is not numeric, 
	//    but an explicit port is provided, the client SHOULD use UDP for a SIP URI, 
	//    and TCP for a SIPS URI
	// 3. Otherwise, if no transport protocol or port is specified, and the target 
	//    is not a numeric IP address, the client SHOULD perform a NAPTR query for 
	//    the domain in the URI.

	// The client SHOULD try the first record. If an attempt should fail, based on 
	// the definition of failure in Section 4.3, the next SHOULD be tried, and if 
	// that should fail, the next SHOULD be tried, and so on.

	t->addrlen = sizeof(t->addr);
	memset(&t->addr, 0, sizeof(t->addr));
	strcpy(protocol, socket_invalid == t->tcp ? "UDP" : "TCP");

	uri = uri_parse(destination, strlen(destination));
	if (!uri)
		return -1; // invalid uri

	// rfc3263 4-Client Usage (p5)
	// once a SIP server has successfully been contacted (success is defined below), 
	// all retransmissions of the SIP request and the ACK for non-2xx SIP responses 
	// to INVITE MUST be sent to the same host.
	// Furthermore, a CANCEL for a particular SIP request MUST be sent to the same 
	// SIP server that the SIP request was delivered to.

	// TODO: sips port
	r = socket_addr_from(&t->addr, &t->addrlen, uri->host, uri->port ? uri->port : SIP_PORT);
	if (0 == r)
	{
		socket_addr_to((struct sockaddr*)&t->addr, t->addrlen, ip, &port);
		socket_getname(socket_invalid == t->tcp ? t->udp : t->tcp, local, &port);
		r = ip_route_get(ip, local);
		if (0 == r)
		{
			dns[0] = 0;
			struct sockaddr_storage ss;
			socklen_t len = sizeof(ss);
			if (0 == socket_addr_from(&ss, &len, local, port))
				socket_addr_name((struct sockaddr*)&ss, len, dns, 128);

			if (SIP_PORT != port)
				snprintf(local + strlen(local), 128 - strlen(local), ":%hu", port);

			if (NULL == strchr(dns, '.'))
				snprintf(dns, 128, "%s", local); // don't have valid dns
		}
	}

	uri_free(uri);
	return r;
}

static int sip_uac_transport_send(void* transport, const void* data, size_t bytes)
{
	struct sip_uac_transport_address_t *t = (struct sip_uac_transport_address_t *)transport;

	//char p1[1024];
	//char p2[1024];
	((char*)data)[bytes] = 0;
	//printf("%s\n", (const char*)data);
	int r = socket_sendto(socket_invalid == t->tcp ? t->udp : t->tcp, data, bytes, 0, (struct sockaddr*)&t->addr, t->addrlen);
	return r == bytes ? 0 : -1;
}

static int sip_uas_transport_send(void* param, const struct cstring_t* /*protocol*/, const struct cstring_t* peer, const struct cstring_t* received, int rport, const void* data, int bytes)
{
	struct sip_uac_test2_t *test = (struct sip_uac_test2_t *)param;

	std::shared_ptr<uri_t> uri(uri_parse(peer->p, peer->n), uri_free);
	if (!uri.get())
		return -1; // invalid uri

	socklen_t addrlen;
	struct sockaddr_storage addr;
	int r = socket_addr_from(&addr, &addrlen, cstrvalid(received) ? received->p : uri->host, rport > 0 ? rport : (uri->port ? uri->port : SIP_PORT));
	if (0 != r)
		return -1; // invalid

	//char p1[1024];
	//char p2[1024];
	((char*)data)[bytes] = 0;
	//printf("%s\n\n", (const char*)data);
	r = socket_sendto(socket_invalid == test->tcp ? test->udp : test->tcp, data, bytes, 0, (struct sockaddr*)&addr, addrlen);
	return r == bytes ? 0 : -1;
}

static int sdp_media_negotiate(const struct rtsp_media_t* m)
{
	int i, j;
    const int payloads[] = { RTP_PAYLOAD_PCMA, RTP_PAYLOAD_PCMU };
    const char* payloads2[] = { "H264", "H265", "MP4V-ES", "MP4A-LATM", "MPEG4-GENERIC", "opus" };
    
	assert(0 == strcasecmp("IP4", m->addrtype) || 0 == strcasecmp("IP6", m->addrtype));
	for (i = 0; i < m->avformat_count; i++)
	{
		if(m->avformats[i].fmt < RTP_PAYLOAD_DYNAMIC)
        {
            //for(j = 0; j < sizeof(payloads)/sizeof(payloads[0]); j++)
            //{
            //    if(payloads[j] == m->avformats[i].fmt)
            //        return i;
            //}
        }
        else
        {
            for(j = 0; j < sizeof(payloads2)/sizeof(payloads2[0]); j++)
            {
                if(0 == strcmp(m->avformats[i].encoding, payloads2[j]))
                    return i;
            }
        }
	}
	return -1;
}

//static uint8_t sdp_media_find(const struct rtsp_media_t* medias, int count, const char* media, uint8_t payload, const char* encoding)
//{
//	int i, j;
//	const struct rtsp_media_t* m;
//	for (i = 0; i < count; i++)
//	{
//		m = medias + i;
//		if(0 != strcmp(m->media, media))
//			continue;
//
//		if (payload < RTP_PAYLOAD_DYNAMIC)
//		{
//			for (j = 0; j < m->avformat_count; j++)
//			{
//				if (payload == m->avformats[j].fmt)
//					return payload;
//			}
//		}
//		else
//		{
//			for (j = 0; j < m->avformat_count; j++)
//			{
//				if (0 == strcmp(m->avformats[j].encoding, encoding))
//					return m->avformats[j].fmt;
//			}
//		}
//	}
//	return payload; // invalid
//}

static void mp4_onvideo(void* param, uint32_t track, uint8_t object, int width, int height, const void* extra, size_t bytes)
{
	sip_uac_test2_session_t* s = (sip_uac_test2_session_t*)param;
	s->video.track = track;
	s->video.fp = fopen("sipvideo.h264", "wb");
	s->video.s = s;
	s->video.sender.onpacket = rtp_packet_send;
	s->video.sender.param = &s->video;

	char ip[SOCKET_ADDRLEN];
	u_short port;
	struct sockaddr_storage addr[2];
	assert(0 == ice_transport_getaddr(s->avt, s->video.stream, 1, &addr[0]));
	assert(0 == ice_transport_getaddr(s->avt, s->video.stream, 2, &addr[1]));
	socket_addr_to((struct sockaddr*)&addr[0], socket_addr_len((struct sockaddr*)&addr[0]), ip, &port);

	if (MOV_OBJECT_H264 == object)
	{
		s->video.codec = AVCODEC_VIDEO_H264;
		mpeg4_avc_decoder_configuration_record_load((const uint8_t*)extra, bytes, &s->video.u.avc);
		rtp_sender_init_video(&s->video.sender, "RTP/AVP", port, RTP_PAYLOAD_H264, "H264", 90000, extra, bytes);
	}
	else if (MOV_OBJECT_HEVC == object)
	{
		s->video.codec = AVCODEC_VIDEO_H265;
        mpeg4_hevc_decoder_configuration_record_load((const uint8_t*)extra, bytes, &s->video.u.hevc);
        rtp_sender_init_video(&s->video.sender, "RTP/AVP", port, RTP_PAYLOAD_H265, "H265", 90000, extra, bytes);
	}
	else if (MOV_OBJECT_MP4V == object)
	{
		s->video.codec = AVCODEC_VIDEO_MPEG4;
        rtp_sender_init_video(&s->video.sender, "RTP/AVP", port, RTP_PAYLOAD_MP4V, "MP4V-ES", 90000, extra, bytes);
	}
	else
	{
		assert(0);
		return;
	}

	int n = snprintf(s->video.sdp, sizeof(s->video.sdp), "%s", (char*)s->video.sender.buffer);
	n += ice_transport_getsdp(s->avt, s->video.stream, (char*)s->video.sender.buffer + n, sizeof(s->video.sender.buffer) - n);
}

static void mp4_onaudio(void* param, uint32_t track, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void* extra, size_t bytes)
{
	sip_uac_test2_session_t* s = (sip_uac_test2_session_t*)param;
	s->audio.track = track;
	s->audio.fp = fopen("sipaudio.pcm", "wb");
	s->audio.s = s;
	s->audio.sender.onpacket = rtp_packet_send;
	s->audio.sender.param = &s->audio;

	char ip[SOCKET_ADDRLEN];
	u_short port;
	struct sockaddr_storage addr[2];
	assert(0 == ice_transport_getaddr(s->avt, s->audio.stream, 1, &addr[0]));
	assert(0 == ice_transport_getaddr(s->avt, s->audio.stream, 2, &addr[1]));
	socket_addr_to((struct sockaddr*)&addr[0], socket_addr_len((struct sockaddr*)&addr[0]), ip, &port);

	if (MOV_OBJECT_AAC == object || MOV_OBJECT_AAC_LOW == object)
	{
        s->audio.codec = AVCODEC_AUDIO_AAC;
        mpeg4_aac_audio_specific_config_load((const uint8_t*)extra, bytes, &s->audio.u.aac);
        rtp_sender_init_audio(&s->audio.sender, "RTP/AVP", port, RTP_PAYLOAD_LATM, "MP4A-LATM", sample_rate, channel_count, extra, bytes);
	}
	else if (MOV_OBJECT_OPUS == object)
	{
        s->audio.codec = AVCODEC_AUDIO_OPUS;
        rtp_sender_init_audio(&s->audio.sender, "RTP/AVP", port, RTP_PAYLOAD_OPUS, "opus", sample_rate, channel_count, extra, bytes);
	}
	else if (MOV_OBJECT_G711u == object)
	{
        s->audio.codec = AVCODEC_AUDIO_PCM;
        rtp_sender_init_audio(&s->audio.sender, "RTP/AVP", port, RTP_PAYLOAD_PCMU, "", sample_rate, channel_count, extra, bytes);
	}
    else if (MOV_OBJECT_G711a == object)
    {
        s->audio.codec = AVCODEC_AUDIO_PCM;
        rtp_sender_init_audio(&s->audio.sender, "RTP/AVP", port, RTP_PAYLOAD_PCMA, "", sample_rate, channel_count, extra, bytes);
    }
    else
	{
		assert(0);
		return;
	}

	int n = snprintf(s->audio.sdp, sizeof(s->audio.sdp), "%s", (char*)s->audio.sender.buffer);
	n += ice_transport_getsdp(s->avt, s->audio.stream, (char*)s->audio.sender.buffer + n, sizeof(s->audio.sender.buffer) - n);
}

static void ice_transport_onconnected(void* param, uint64_t flags, uint64_t mask)
{
	struct sip_uac_test2_session_t* s = (struct sip_uac_test2_session_t*)param;

	for (int stream = 0; stream < 2; stream++)
	{
		sip_uac_test2_session_t::av_media_t* av = s->video.stream == stream ? &s->video : &s->audio;
		av->connected = (flags & ((int64_t)1 << stream)) ? 1 : 0;
		//for (int component = 0; component < 2; component++)
		//{
		//	assert(0 == ice_transport_get_candidate(s->ice, av->stream, component + 1, &av->local[component]));
		//}
	}

	printf("ice_transport_onconnected 0x%x\n", (unsigned int)flags);
	s->source->Play();

	// TODO: reinvite
}

static void ice_transport_onbind(void* param, int code)
{
	const char* pattern = "v=0\n"
		"o=%s 0 0 IN IP4 %s\n"
		"s=Talk\n"
		"c=IN IP4 %s\n"
		"t=0 0\n"
		"%s%s"; // audio/video

	char reply[4*1024];
	struct sip_uac_test2_session_t* s = (struct sip_uac_test2_session_t*)param;

	if (0 == code)
	{
		std::shared_ptr<MP4FileReader> reader(new MP4FileReader("e:\\video\\mp4\\name.opus.mp4"));
		struct mov_reader_trackinfo_t info = { mp4_onvideo, mp4_onaudio };
		reader->GetInfo(&info, s);
		std::shared_ptr<sip_uac_test2_session_t> listener(s); // fixme: s->addref()
		std::shared_ptr<VodFileSource> source(new VodFileSource(reader, listener));
		s->source = source;

		// default connect address
		u_short port;
		char host[SOCKET_ADDRLEN];
		struct sockaddr_storage addr;
		ice_transport_getaddr(s->avt, 0, 1, &addr);
		socket_addr_to((struct sockaddr*)&addr, socket_addr_len((struct sockaddr*)&addr), host, &port);

		// TODO: PRACK add 100rel/precondiation
		//sip_uac_add_header(s->tuac, "Supported", "100ref");
		//sip_uac_add_header(s->tuac, "Supported", "precondition");
		// TODO: add Recv-Info
		//sip_uac_add_header(s->tuac, "Recv-Info", "");
		// TODO: add Allow-Events
		//sip_uac_add_header(s->tuac, "Allow-Events", "");

		sip_uas_add_header(s->t, "Content-Type", "application/sdp");
		//sip_uas_add_header(s->t, "Contact", "sip:" SIP_USR "@" LOCAL_HOST);
		snprintf(reply, sizeof(reply), pattern, s->user.c_str(), host, host, s->audio.m ? (char*)s->audio.sender.buffer : "", s->video.m ? (char*)s->video.sender.buffer : "");
		assert(0 == sip_uas_reply(s->t, 200, reply, strlen(reply), s->ctx));

		ice_transport_connect(s->avt, s->medias, s->nmedia);
	}
	else
	{
		assert(0 == sip_uas_reply(s->t, 501, "Internal Server Error", 21, s->ctx));
	}
}

static void ice_transport_ondata(void* param, int stream, int component, const void* data, int bytes)
{
	struct sip_uac_test2_session_t* s = (struct sip_uac_test2_session_t*)param;
	sip_uac_test2_session_t::av_media_t* av = s->audio.stream == stream ? &s->audio : &s->video;
	if (1 == component)
	{
		assert(rtp_payload_decode_input(av->decoder, data, bytes) >= 0);
		//assert(0 == rtp_onreceived(av->sender.rtp, data, bytes));
	}
	else
	{
		//assert(0 == rtp_onreceived_rtcp(av->decoder, data, bytes));
	}
}

static int rtp_packet_onrecv(void* param, const void *packet, int bytes, uint32_t timestamp, int flags)
{
	sip_uac_test2_session_t::av_media_t* av = (sip_uac_test2_session_t::av_media_t*)param;
	return bytes == fwrite(packet, 1, bytes, av->fp) ? 0 : ferror(av->fp);
}

static int rtp_packet_send(void* param, const void *packet, int bytes, uint32_t timestamp, int flags)
{
	sip_uac_test2_session_t::av_media_t* av = (sip_uac_test2_session_t::av_media_t*)param;
	return ice_transport_send(av->s->avt, av->stream, 1, packet, bytes);
}

static int rtcp_report(struct sip_uac_test2_session_t::av_media_t* av, time64_t clock)
{
	int r;
	int interval = rtp_rtcp_interval(av->sender.rtp);
	if (av->clock + interval < clock)
	{
		r = rtp_rtcp_report(av->sender.rtp, av->sender.buffer, sizeof(av->sender.buffer));
		r = ice_transport_send(av->s->avt, av->stream, 2, av->sender.buffer, r);
		//printf("rtcp_report: %d\n", r);
		av->clock = clock;
	}
	return 0;
}

static int STDCALL sip_work_thread(void* param)
{
	time64_t clock;

	struct sip_uac_test2_session_t* s = (struct sip_uac_test2_session_t*)param;

	while (s->running)
	{
		clock = time64_now();

		// RTCP report
		if (s->audio.sender.rtp && s->audio.connected)
			rtcp_report(&s->audio, clock);
		if (s->video.sender.rtp && s->video.connected)
			rtcp_report(&s->video, clock);

		for (std::shared_ptr<avpacket_t> pkt(s->pkts->FrontWait(0), avpacket_release); pkt.get(); pkt.reset(s->pkts->FrontWait(0), avpacket_release))
		{
			if (pkt->stream->stream == s->audio.track && s->audio.m)
			{
				uint32_t timestamp = s->audio.sender.timestamp + (uint32_t)(pkt->pts * (s->audio.sender.frequency / 1000) /*kHz*/);
				rtp_payload_encode_input(s->audio.sender.encoder, pkt->data, pkt->size, timestamp);
				//printf("send audio[%d] packet pts: %" PRId64 ", timestamp: %u\n", s->audio.sender.frequency, pkt->pts, timestamp);
			}
			else if (pkt->stream->stream == s->video.track && s->video.m)
			{
				int n = h264_mp4toannexb(&s->video.u.avc, pkt->data, pkt->size, s->buffer, sizeof(s->buffer));
				uint32_t timestamp = s->video.sender.timestamp + (uint32_t)(pkt->pts * (s->video.sender.frequency / 1000) /*kHz*/);
				rtp_payload_encode_input(s->video.sender.encoder, s->buffer, n, timestamp);
				//printf("send video[%d] packet pts: %" PRId64 ", timestamp: %u\n", s->video.sender.frequency, pkt->pts, timestamp);
			}
			else
			{
				//assert(0);
				// ignore
			}

			s->pkts->Pop();
		}
	}

	//size_t n = rtp_rtcp_bye(m->rtp, rtcp, sizeof(rtcp));
	s->source->Pause();
	return 0;
}

static int sip_uas_oninvite_indialog(sip_uac_test2_t* ctx, sip_uac_test2_session_t* s, const struct sip_message_t* req, struct sip_uas_transaction_t* t, struct sip_dialog_t* dialog, const void* data, int bytes, void** session)
{
	const char* pattern = "v=0\n"
		"o=%s 0 0 IN IP4 %s\n"
		"s=Talk\n"
		"c=IN IP4 %s\n"
		"t=0 0\n"
		"%s%s"; // audio/video

	// default connect address
	u_short port;
	char host[SOCKET_ADDRLEN];
	struct sockaddr_storage addr;
	ice_transport_getaddr(s->avt, 0, 1, &addr);
	socket_addr_to((struct sockaddr*)&addr, socket_addr_len((struct sockaddr*)&addr), host, &port);

	char reply[4 * 1024];
	int n = snprintf(reply, sizeof(reply), pattern, s->user.c_str(), host, host, "", "");
	if (s->audio.m)
	{
		n += snprintf(reply + n, sizeof(reply) - n, "%s", s->audio.sdp);
		n += ice_transport_getsdp(s->avt, s->audio.stream, reply + n, sizeof(reply) - n);
	}
	if (s->video.m)
	{
		n += snprintf(reply + n, sizeof(reply) - n, "%s", s->video.sdp);
		n += ice_transport_getsdp(s->avt, s->video.stream, reply + n, sizeof(reply) - n);
	}

	*session = s;
	s->t = t;
	sip_uas_add_header(t, "Content-Type", "application/sdp");
	//sip_uas_add_header(s->t, "Contact", "sip:" SIP_USR "@" LOCAL_HOST);
	assert(0 == sip_uas_reply(t, 200, reply, strlen(reply), ctx));
	return 0;
}

static int sip_uas_oninvite(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, struct sip_dialog_t* dialog, const void* data, int bytes, void** session)
{
    sip_uac_test2_t* ctx = (sip_uac_test2_t*)param;

	const cstring_t* h = sip_message_get_header_by_name(req, "Content-Type");
	if (0 != cstrcasecmp(h, "application/sdp"))
	{
		assert(0);
		return 0; // discard
	}

	std::shared_ptr<sip_uac_test2_session_t> s(new sip_uac_test2_session_t());
	s->from = std::string(req->from.uri.host.p, req->from.uri.host.n);
	{
		sip_uac_test2_t::TSessions::iterator it;
		AutoThreadLocker locker(ctx->locker);
		it = ctx->sessions.find(s->from);
		if (ctx->sessions.end() != it)
		{
			if(!dialog)
				return 0; // ignore

			// in dialog
			return sip_uas_oninvite_indialog(ctx, it->second.get(), req, t, dialog, data, bytes, session);
		}
		ctx->sessions.insert(std::make_pair(s->from, s));
	}

	struct ice_transport_handler_t handler = {
		ice_transport_ondata,
		ice_transport_onbind,
		ice_transport_onconnected,
	};

	s->pkts = std::shared_ptr<AVPacketQueue>(new AVPacketQueue(200));
	s->avt = ice_transport_create(0, &handler, s.get());
	memset(&s->audio, 0, sizeof(s->audio));
	memset(&s->video, 0, sizeof(s->video));
	s->user = ctx->usr;
	s->ctx = ctx;
	s->t = t;

	s->nmedia = rtsp_media_sdp((const char*)data, bytes, s->medias, sizeof(s->medias) / sizeof(s->medias[0]));
	for (int i = 0; i < s->nmedia; i++)
	{
		struct rtsp_media_t* m = s->medias + i;

		if (0 == strcmp("audio", m->media))
		{
			int j = sdp_media_negotiate(m);
			if(-1 == j)
				continue;

			struct rtp_payload_t handler;
			handler.alloc = NULL;
			handler.free = NULL;
			handler.packet = rtp_packet_onrecv;
			s->audio.decoder = rtp_payload_decode_create(m->avformats[j].fmt, m->avformats[j].encoding, &handler, &s->audio);
			if (NULL == s->audio.decoder)
			{
				assert(0);
				continue; // ignore
			}

			s->audio.m = m;
			s->audio.fmt = j;
			s->audio.stream = i;
            //socket_addr_from(&s->audio.remote[0], &addrlen, m->address, m->port[0]);
			//socket_addr_from(&s->audio.remote[1], &addrlen, m->address, m->port[1]);
		}
		else if (0 == strcmp("video", m->media))
		{
			int j = sdp_media_negotiate(m);
			if (-1 == j)
				continue;

            struct rtp_payload_t handler;
            handler.alloc = NULL;
            handler.free = NULL;
            handler.packet = rtp_packet_onrecv;
            s->video.decoder = rtp_payload_decode_create(m->avformats[j].fmt, m->avformats[j].encoding, &handler, &s->video);
			if (NULL == s->video.decoder)
			{
				assert(0);
				continue; // ignore
			}

			s->video.m = m; 
			s->video.fmt = j;
			s->video.stream = i;
			//socket_addr_from(&s->video.remote[0], &addrlen, m->address, m->port[0]);
			//socket_addr_from(&s->video.remote[1], &addrlen, m->address, m->port[1]);
		}
	}

	struct sockaddr_storage stun;
	memset(&stun, 0, sizeof(stun));
	socket_addr_from_ipv4((struct sockaddr_in*)&stun, TURN_SERVER, STUN_PORT);
	ice_transport_bind(s->avt, s->nmedia, 2, TURN_SERVER ? (struct sockaddr*)&stun : NULL, 0, TURN_USR, TURN_PWD);

//      std::shared_ptr<AVPacketQueue> pkts(new AVPacketQueue(200));
//      std::shared_ptr<MP4FileReader> reader(new MP4FileReader("/Users/ireader/video/opus.mp4"));
//      struct mov_reader_trackinfo_t info = { mp4_onvideo, mp4_onaudio};
//      reader->GetInfo(&info, s.get());
//      std::shared_ptr<VodFileSource> source(new VodFileSource(reader, pkts));
//      
//      s->pkts = pkts;
	//s->source = source;

	*session = s.get();
	//sip_uas_add_header(t, "Content-Type", "application/sdp");
	//sip_uas_add_header(t, "Contact", "sip:" SIP_USR "@" LOCAL_HOST);
//      snprintf(reply, sizeof(reply), pattern, SIP_USR, LOCAL_HOST, LOCAL_HOST, s->audio.decoder?(char*)s->audio.sender.buffer:"", s->video.decoder?(char*)s->video.sender.buffer:"");
	//assert(0 == sip_uas_reply(t, 200, reply, strlen(reply)));
	s->running = true;
	thread_create(&s->th, sip_work_thread, s.get());
    return 0;
}

/// @param[in] code 0-ok, other-sip status code
/// @return 0-ok, other-error
static int sip_uas_onack(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session, struct sip_dialog_t* dialog, int code, const void* data, int bytes)
{
	struct sip_uac_test2_t* ctx = (struct sip_uac_test2_t*)param; assert(ctx);
	sip_uac_test2_session_t* s = (sip_uac_test2_session_t*)session; assert(s);
	printf("sip_uas_onack[%p]: %d\n", s, code);

	if (200 <= code && code < 300)
	{
		//if(s->source.get())
		//	s->source->Play();
	}
	else
	{
		s->running = false;
		if (s->source.get())
			s->source->Pause();
		thread_destroy(s->th);

		AutoThreadLocker locker(ctx->locker);
		ctx->sessions.erase(s->from);
	}
	return 0;
}

/// on terminating a session(dialog)
static int sip_uas_onbye(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session)
{
	struct sip_uac_test2_t* ctx = (struct sip_uac_test2_t*)param;
	sip_uac_test2_session_t* p = (sip_uac_test2_session_t*)session;

	std::shared_ptr<sip_uac_test2_session_t> s;
	sip_uac_test2_t::TSessions::iterator it;
	{
		AutoThreadLocker locker(ctx->locker);
		it = ctx->sessions.find(p->from);
		if (it == ctx->sessions.end())
			return sip_uas_reply(t, 481, NULL, 0, param);

		s = it->second;
		ctx->sessions.erase(it);
	}

	s->running = false;
	s->source->Pause();
	thread_destroy(s->th);

	return sip_uas_reply(t, 200, NULL, 0, param);
}

/// cancel a transaction(should be an invite transaction)
static int sip_uas_oncancel(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session)
{
	return sip_uas_reply(t, 200, NULL, 0, param);
}

/// @param[in] expires in seconds
static int sip_uas_onregister(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, const char* user, const char* location, int expires)
{
	return sip_uas_reply(t, 200, NULL, 0, param);
}

static int sip_uas_onmessage(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session, const void* payload, int bytes)
{
	return sip_uas_reply(t, 200, NULL, 0, param);
}

static void sip_uac_ice_transport_onbind(void* param, int code)
{
	const char* pattern = "v=0\n"
		"o=%s 0 0 IN IP4 %s\n"
		"s=Talk\n"
		"c=IN IP4 %s\n"
		"t=0 0\n"
		"%s%s"; // audio/video

	char buffer[4*1024];
	struct sip_uac_test2_session_t* s = (struct sip_uac_test2_session_t*)param;

	if (0 == code)
	{
		std::shared_ptr<MP4FileReader> reader(new MP4FileReader("e:\\video\\mp4\\name.opus.mp4"));
		struct mov_reader_trackinfo_t info = { mp4_onvideo, mp4_onaudio };
		reader->GetInfo(&info, s);
		std::shared_ptr<sip_uac_test2_session_t> listener(s); // fixme: s->addref()
		std::shared_ptr<VodFileSource> source(new VodFileSource(reader, listener));
		s->source = source;

		// default connect address
		u_short port;
		char host[SOCKET_ADDRLEN];
		struct sockaddr_storage addr;
		ice_transport_getaddr(s->avt, 0, 1, &addr);
		socket_addr_to((struct sockaddr*)&addr, socket_addr_len((struct sockaddr*)&addr), host, &port);

		// TODO: PRACK add 100rel/precondiation
		//sip_uac_add_header(s->tuac, "Supported", "100ref");
		//sip_uac_add_header(s->tuac, "Supported", "precondition");
		// TODO: add Recv-Info
		//sip_uac_add_header(s->tuac, "Recv-Info", "");
		// TODO: add Allow-Events
		//sip_uac_add_header(s->tuac, "Allow-Events", "");

		sip_uac_add_header(s->tuac.get(), "Content-Type", "application/sdp");
		int n = snprintf(buffer, sizeof(buffer), pattern, s->user.c_str(), host, host, (char*)s->audio.sender.buffer, (char*)s->video.sender.buffer);

		struct sip_transport_t t = {
			sip_uac_transport_via,
			sip_uac_transport_send,
		};
		assert(0 == sip_uac_send(s->tuac.get(), buffer, n, &t, &s->transport));
	}
	else
	{
		assert(0);
	}
}

static int sip_uac_oninvited(void* param, const struct sip_message_t* reply, struct sip_uac_transaction_t* t, struct sip_dialog_t* dialog, int code, void** session)
{
	const cstring_t* h;
	std::shared_ptr<struct sip_uac_test2_session_t> s;
	struct sip_uac_test2_t *ctx = (struct sip_uac_test2_t*)param;

	if (reply)
	{
		sip_uac_test2_t::TSessions::iterator it;
		AutoThreadLocker locker(ctx->locker);
		it = ctx->sessions.find(SIP_PEER);
		if (it == ctx->sessions.end())
			return 0; // ignore
		s = it->second;
	}

	// TODO: handle reply->recv_info

	if (200 <= code && code < 300)
	{
		h = sip_message_get_header_by_name(reply, "Content-Type");
		if (!h || 0 != cstrcasecmp(h, "application/sdp"))
		{
			assert(0);
			return 0;
		}

		s->nmedia = rtsp_media_sdp((const char*)reply->payload, reply->size, s->medias, sizeof(s->medias) / sizeof(s->medias[0]));
		assert(0 == ice_transport_connect(s->avt, s->medias, s->nmedia));
		for (int i = 0; i < s->nmedia; i++)
		{
			struct rtsp_media_t* m = s->medias + i;

			if (0 == strcmp("audio", m->media))
			{
				int j = sdp_media_negotiate(m);
				if (-1 == j)
					continue;

				struct rtp_payload_t handler;
				handler.alloc = NULL;
				handler.free = NULL;
				handler.packet = rtp_packet_onrecv;
				s->audio.decoder = rtp_payload_decode_create(m->avformats[j].fmt, m->avformats[j].encoding, &handler, &s->audio);
				if (NULL == s->audio.decoder)
				{
					assert(0);
					continue; // ignore
				}

				assert(i == s->audio.stream);
				s->audio.m = m;
				s->audio.fmt = j;
			}
			else if (0 == strcmp("video", m->media))
			{
				int j = sdp_media_negotiate(m);
				if (-1 == j)
					continue;

				struct rtp_payload_t handler;
				handler.alloc = NULL;
				handler.free = NULL;
				handler.packet = rtp_packet_onrecv;
				s->video.decoder = rtp_payload_decode_create(m->avformats[j].fmt, m->avformats[j].encoding, &handler, &s->video);
				if (NULL == s->video.decoder)
				{
					assert(0);
					continue; // ignore
				}

				assert(i == s->video.stream);
				s->video.m = m;
				s->video.fmt = j;
			}
		}

		s->from = SIP_PEER;
		s->running = true;
		thread_create(&s->th, sip_work_thread, s.get());

		*session = s.get();
		sip_uac_ack(t, NULL, 0);
		return 0;
	}
	else if (code == 183)
	{
		h = sip_message_get_header_by_name(reply, "Require");
		if (!h || (0 != cstrcasecmp(h, "100rel") && 0 != cstrcasecmp(h, "precondition")))
		{
			assert(0);
			return 0;
		}

		assert(dialog);
		//struct sip_uac_transaction_t* prack = sip_uac_prack(ctx->sip, dialog, sip_uac_onprack, ctx);
		//sip_uac_add_header(prack, "Supported", "precondition");
		//sip_uac_send(prack, sdp, 0, transport, param);
		return 0;
	}
	else if (code >= 300)
	{
		// TODO: delete session
		printf("sip_uac_oninvited code: %d\n", code);
		return 0;
	}
	else
	{
		// trying
		return 0;
	}
}

static void sip_uac_invite_test(struct sip_uac_test2_t *ctx)
{
    char buffer[1024];
	std::shared_ptr<sip_uac_transaction_t> t(sip_uac_invite(ctx->sip, SIP_FROM, SIP_PEER, sip_uac_oninvited, ctx), sip_uac_transaction_release);
	if (HTTP_AUTHENTICATION_DIGEST == ctx->auth.scheme)
	{
		++ctx->auth.nc;
		snprintf(ctx->auth.uri, sizeof(ctx->auth.uri), "%s", SIP_PEER);
		snprintf(ctx->auth.username, sizeof(ctx->auth.username), "%s", ctx->usr);
		http_header_auth(&ctx->auth, SIP_PWD, "INVITE", NULL, 0, buffer, sizeof(buffer));
		sip_uac_add_header(t.get(), "Proxy-Authorization", buffer);
	}

	struct ice_transport_handler_t handler = {
		ice_transport_ondata,
		sip_uac_ice_transport_onbind,
		ice_transport_onconnected,
	};
	std::shared_ptr<sip_uac_test2_session_t> s(new sip_uac_test2_session_t());
	s->pkts = std::shared_ptr<AVPacketQueue>(new AVPacketQueue(200));
	s->avt = ice_transport_create(0, &handler, s.get());
	memset(&s->audio, 0, sizeof(s->audio));
	memset(&s->video, 0, sizeof(s->video));
	s->user = ctx->usr;
	s->tuac = t;

	{
		AutoThreadLocker locker(ctx->locker);
		if (ctx->sessions.end() != ctx->sessions.find(SIP_PEER))
			return; // ignore
		ctx->sessions.insert(std::make_pair(SIP_PEER, s));
	}

	s->audio.stream = 0;
	s->video.stream = 1;
	s->transport.udp = ctx->udp;
	s->transport.tcp = ctx->tcp;
	struct sockaddr_storage stun;
	memset(&stun, 0, sizeof(stun));
	assert(0 == socket_addr_from_ipv4((struct sockaddr_in*)&stun, TURN_SERVER, STUN_PORT));
	ice_transport_bind(s->avt, 2, 2, TURN_SERVER ? (struct sockaddr*)&stun : NULL, 0, TURN_USR, TURN_PWD);
}

static void sip_uac_register_test(struct sip_uac_test2_t *test);
static int sip_uac_onregister(void* param, const struct sip_message_t* reply, struct sip_uac_transaction_t* t, int code)
{
	struct sip_uac_test2_t *test = (struct sip_uac_test2_t *)param;

	const cstring_t* h;
	if (0 == test->callid[0] && reply)
	{
		h = sip_message_get_header_by_name(reply, "Call-ID");
		if (h)
		{
			snprintf(test->callid, sizeof(test->callid), "%.*s", (int)h->n, h->p);
			h = sip_message_get_header_by_name(reply, "CSeq");
			test->cseq = atoi(h->p);
		}
	}

	if (200 <= code && code < 300)
	{
		printf("Register OK\n");
		//sip_uac_invite_test(test);
	}
	else if (401 == code)
	{
		// Unauthorized
		memset(&test->auth, 0, sizeof(test->auth));
		h = sip_message_get_header_by_name(reply, "WWW-Authenticate");
		assert(0 == http_header_www_authenticate(h->p, &test->auth));
		test->nonce_count = 0;
		switch (test->auth.scheme)
		{
		case HTTP_AUTHENTICATION_DIGEST:
			sip_uac_register_test(test);
			break;

		case HTTP_AUTHENTICATION_BASIC:
			assert(0);
			break;

		default:
			assert(0);
		}
	}
	else
	{
	}
	return 0;
}

static void sip_uac_register_test(struct sip_uac_test2_t *test)
{
	char buffer[256];
	//t = sip_uac_register(uac, "Bob <sip:bob@biloxi.com>", "sip:registrar.biloxi.com", 7200, sip_uac_message_onregister, test);
	std::shared_ptr<sip_uac_transaction_t> t(sip_uac_register(test->sip, SIP_FROM, "sip:" SIP_HOST, SIP_EXPIRED, sip_uac_onregister, test), sip_uac_transaction_release);

	if (test->callid[0])
	{
		// All registrations from a UAC SHOULD use the same Call-ID
		sip_uac_add_header(t.get(), "Call-ID", test->callid);

		snprintf(buffer, sizeof(buffer), "%u REGISTER", ++test->cseq);
		sip_uac_add_header(t.get(), "CSeq", buffer);
	}

	if (HTTP_AUTHENTICATION_DIGEST == test->auth.scheme)
	{
		// https://blog.csdn.net/yunlianglinfeng/article/details/81109380
		// http://www.voidcn.com/article/p-oqqbqgvd-bgn.html
		++test->auth.nc;
		snprintf(test->auth.uri, sizeof(test->auth.uri), "sip:%s", SIP_HOST);
		snprintf(test->auth.username, sizeof(test->auth.username), "%s", test->usr);
		http_header_auth(&test->auth, SIP_PWD, "REGISTER", NULL, 0, buffer, sizeof(buffer));
		sip_uac_add_header(t.get(), "Authorization", buffer);
	}

	struct sip_transport_t transport = {
			sip_uac_transport_via,
			sip_uac_transport_send,
	};
	assert(0 == sip_uac_send(t.get(), NULL, 0, &transport, &test->transport));
}

static int STDCALL TimerThread(void* param)
{
	uint64_t clock = 0;
	struct sip_uac_test2_t* ctx = (struct sip_uac_test2_t*)param;
	
	while (ctx->running)
	{
		uint64_t now = time64_now();
		if (clock + SIP_EXPIRED * 1000 < now)
		{
			clock = now;
			sip_uac_register_test(ctx);
		}

		system_sleep(5);
	}
	return 0;
}

static int sip_uac_test_process(struct sip_uac_test2_t* test)
{
    uint8_t buffer[4 * 1024];
    http_parser_t* parser;
	http_parser_t* request;
	http_parser_t* response;

	request = http_parser_create(HTTP_PARSER_RESPONSE, NULL, NULL);
	response = http_parser_create(HTTP_PARSER_REQUEST, NULL, NULL);

    do
    {
		socklen_t addrlen;
		struct sockaddr_storage addr;
		addrlen = sizeof(addr);
		memset(buffer, 0, sizeof(buffer));
		int r = socket_recvfrom(socket_invalid==test->tcp ? test->udp : test->tcp, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr, &addrlen);
		if (-1 == r && EINTR == errno)
			continue;

		printf("\n%s\n", buffer);
		parser = 0 == strncasecmp("SIP", (char*)buffer, 3) ? request : response;

		size_t n = r;
		assert(0 == http_parser_input(parser, buffer, &n));
		struct sip_message_t* msg = sip_message_create(parser == response ? SIP_MESSAGE_REQUEST : SIP_MESSAGE_REPLY);
		assert(0 == sip_message_load(msg, parser));
		assert(0 == sip_agent_input(test->sip, msg, test));
		sip_message_destroy(msg);
		http_parser_clear(parser);
    } while (1);
    
	http_parser_destroy(request);
	http_parser_destroy(response);
    return 0;
}

static int sip_uas_onsubscribe(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, sip_subscribe_t* subscribe, void** sub)
{
	return 0;
}
static int sip_uas_onnotify(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session, const struct cstring_t* event)
{
	return 0;
}
static int sip_uas_onpublish(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, const struct cstring_t* event)
{
	return 0;
}

static int sip_uas_onrefer(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session)
{
	return 0;
}

static int sip_uas_onprack(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session, struct sip_dialog_t* dialog, const void* data, int bytes)
{
	return 0;
}

static int sip_uas_onupdate(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session, struct sip_dialog_t* dialog, const void* data, int bytes)
{
	return 0;
}

static int sip_uas_oninfo(void* param, const struct sip_message_t* req, struct sip_uas_transaction_t* t, void* session, struct sip_dialog_t* dialog, const struct cstring_t* package, const void* data, int bytes)
{
    return 0;
}

void sip_uac_test2(void)
{
	socket_init();
	sip_timer_init();

	struct sip_uac_test2_t test;
	test.running = true;
	test.callid[0] = 0;
    struct sip_uas_handler_t handler = {
		sip_uas_transport_send,
		sip_uas_onregister,
		sip_uas_oninvite,
		sip_uas_onack,
		sip_uas_onprack,
		sip_uas_onupdate,
		sip_uas_oninfo,
        sip_uas_onbye,
		sip_uas_oncancel,
		sip_uas_onsubscribe,
		sip_uas_onnotify,
		sip_uas_onpublish,
		sip_uas_onmessage,
		sip_uas_onrefer,
	};

	assert(1 == sscanf(SIP_FROM, "sip:%[^@]", test.usr));
	ip_route_get(SIP_HOST, test.local);
	
	test.udp = socket_udp();
	//test.tcp = socket_connect_host(SIP_HOST, SIP_PORT, 2000);
	//if(socket_invalid != test.tcp)
	//	socket_setnonblock(test.tcp, 0);
	test.tcp = socket_invalid;
	test.sip = sip_agent_create(&handler);
    socket_bind_any(test.udp, SIP_PORT);
	test.transport.udp = test.udp;
	test.transport.tcp = test.tcp;

	pthread_t th;
	thread_create(&th, TimerThread, &test);

	//sip_uac_register_test(&test);
    sip_uac_test_process(&test);

	thread_destroy(th);
	sip_agent_destroy(test.sip);
	socket_close(test.udp);
	socket_cleanup();
	sip_timer_cleanup();
}
