#include "rtsp-server-aio.h"
#include "aio-socket.h"
#include "sys/atomic.h"
#include "sys/locker.h"
#include "sockutil.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>

struct rtsp_udp_transport_t
{
	int32_t ref;
	int running;
	socket_t socket;
	aio_socket_t aio;

	size_t size; // udp buffer size

	struct rtsp_handler_t handler;
	void* param;
};

struct rtsp_udp_session_t
{
	struct rtsp_udp_transport_t* transport;
	struct rtsp_server_t *rtsp;
	struct sockaddr_storage addr;
	socklen_t addrlen;
};

static void rtsp_transport_udp_release(struct rtsp_udp_transport_t* t);
static void rtsp_transport_udp_recv(struct rtsp_udp_transport_t* t);
static void rtsp_transport_udp_onrecv(void* param, int code, size_t bytes, const struct sockaddr* addr, socklen_t addrlen);
static int rtsp_transport_udp_send(void* param, const void* data, size_t bytes); 

void* rtsp_transport_udp_create(const char* ip, int port, struct rtsp_handler_t* handler, void* param)
{
	struct rtsp_udp_transport_t* t;
	t = (struct rtsp_udp_transport_t*)calloc(1, sizeof(*t));
	if (t)
	{
		t->ref = 1;
		t->size = 4 * 1024; // 2k
		t->running = 1;
		t->param = param;
		memcpy(&t->handler, handler, sizeof(t->handler));
		handler->send = rtsp_transport_udp_send;

		t->socket = socket_udp_bind(0 /*AF_UNSPEC*/, ip, (u_short)port, 0, 1);
		if (socket_invalid == t->socket)
		{
			free(t);
			return NULL;
		}

		t->aio = aio_socket_create(t->socket, 1);

		rtsp_transport_udp_recv(t);
	}

	return t;
}

void rtsp_transport_udp_destroy(void* transport)
{
	struct rtsp_udp_transport_t* t;
	t = (struct rtsp_udp_transport_t*)transport;
	t->running = 0;
	rtsp_transport_udp_release(t);
}

static struct rtsp_udp_session_t* rtsp_udp_session_create(struct rtsp_udp_transport_t* t)
{
	struct rtsp_udp_session_t* session;
	session = (struct rtsp_udp_session_t*)malloc(sizeof(*session) + t->size /*udp recv buffer*/);
	if (session)
	{
		atomic_increment32(&t->ref);
		session->transport = t;
	}
	return session;
}

static void rtsp_udp_session_destroy(struct rtsp_udp_session_t* session)
{
	struct rtsp_udp_transport_t* transport;
	transport = (struct rtsp_udp_transport_t*)session->transport;
	rtsp_transport_udp_release(transport);

	// TODO: reuse rtsp server
	if (session->rtsp)
	{
		rtsp_server_destroy(session->rtsp);
		session->rtsp = NULL;
	}

	free(session);
}

static void rtsp_transport_udp_ondestroy(void* param)
{
	struct rtsp_udp_transport_t* t;
	t = (struct rtsp_udp_transport_t*)param;
	free(t);
}

static void rtsp_transport_udp_release(struct rtsp_udp_transport_t* t)
{
	if (0 == atomic_decrement32(&t->ref))
	{
		assert(invalid_aio_socket != t->aio);
		aio_socket_destroy(t->aio, rtsp_transport_udp_ondestroy, t);
	}
}

static void rtsp_transport_udp_recv(struct rtsp_udp_transport_t* t)
{
	void* buffer;
	struct rtsp_udp_session_t* session;
	session = rtsp_udp_session_create(t);
	if (session)
	{
		buffer = session + 1;
		if (!t->running || 0 != aio_socket_recvfrom(t->aio, buffer, t->size, rtsp_transport_udp_onrecv, session))
			rtsp_udp_session_destroy(session);
	}
	else
	{
		// do nothing
	}
}

static void rtsp_transport_udp_onrecv(void* param, int code, size_t bytes, const struct sockaddr* addr, socklen_t addrlen)
{
	char ip[65];
	unsigned short port;
	struct rtsp_udp_session_t* session;
	struct rtsp_udp_transport_t* transport;
	session = (struct rtsp_udp_session_t*)param;
	transport = (struct rtsp_udp_transport_t*)session->transport;

	if (0 == code && bytes > 0)
	{
		size_t remain = bytes;
		rtsp_transport_udp_recv(transport); //  recv more

		socket_addr_to(addr, addrlen, ip, &port);
		assert(addrlen <= sizeof(session->addr));
		session->addrlen = addrlen < sizeof(session->addr) ? addrlen : sizeof(session->addr);
		memcpy(&session->addr, addr, session->addrlen); // save client ip/port
		session->rtsp = rtsp_server_create(ip, port, &session->transport->handler, session->transport->param, session);
		code = rtsp_server_input(session->rtsp, session + 1, &remain);
	}

	if (0 != code || bytes < 1)
	{
		assert(0);
		rtsp_udp_session_destroy(session);
	}
}

static void rtsp_transport_udp_onsend(void* param, int code, size_t bytes)
{
	struct rtsp_udp_session_t* session;
	session = (struct rtsp_udp_session_t*)param;
//	session->server->onsend(session, code, bytes);
	rtsp_udp_session_destroy(session);
}

static int rtsp_transport_udp_send(void* param, const void* data, size_t bytes)
{
	int r;
	struct rtsp_udp_session_t* session;
	struct rtsp_udp_transport_t* transport;
	session = (struct rtsp_udp_session_t*)param;
	transport = (struct rtsp_udp_transport_t*)session->transport;
	if (transport->running)
		r = socket_sendto(transport->socket, data, bytes, 0, (struct sockaddr*)&session->addr, session->addrlen);
	else
		r = -1;
	rtsp_transport_udp_onsend(session, r, r);
	return 0;
}
