#include "rtsp-server-aio.h"
#include "aio-transport.h"
#include "rtp-over-rtsp.h"
#include "sys/sock.h"
#include "sys/atomic.h"
#include "sys/system.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#define TIMEOUT_RECV 65000
#define TIMEOUT_SEND 10000

struct rtsp_session_t
{
	socket_t socket;
	aio_transport_t* aio;
	struct rtp_over_rtsp_t rtp;
	int rtsp_need_more_data;
	uint8_t buffer[4 * 1024];

	struct rtsp_server_t *rtsp;
	struct sockaddr_storage addr;
	socklen_t addrlen;

	void (*onerror)(void* param, rtsp_server_t* rtsp, int code);
	void (*onrtp)(void* param, uint8_t channel, const void* data, uint16_t bytes);
	void* param;
};

static void rtsp_session_ondestroy(void* param)
{
	struct rtsp_session_t *session;
	session = (struct rtsp_session_t *)param;

	// user call rtsp_server_destroy
	if (session->rtsp)
	{
		rtsp_server_destroy(session->rtsp);
		session->rtsp = NULL;
	}

	if (session->rtp.data)
	{
		assert(session->rtp.capacity > 0);
		free(session->rtp.data);
		session->rtp.data = NULL;
		session->rtp.capacity = 0;
	}

#if defined(_DEBUG) || defined(DEBUG)
	memset(session, 0xCC, sizeof(*session));
#endif
	free(session);
}

static void rtsp_session_onrecv(void* param, int code, size_t bytes)
{
	size_t remain;
	const uint8_t* p, *end;
	struct rtsp_session_t *session;
	session = (struct rtsp_session_t *)param;

	if (0 == code && 0 == bytes)
		code = ECONNRESET;

	if (0 == code)
	{
		p = session->buffer;
		end = session->buffer + bytes;
		do
		{
			if (0 == session->rtsp_need_more_data && ('$' == *p || 0 != session->rtp.state))
			{
				p = rtp_over_rtsp(&session->rtp, p, end);
			}
			else
			{
				remain = end - p;
				code = rtsp_server_input(session->rtsp, p, &remain);
				session->rtsp_need_more_data = code;
				if (0 == code)
				{
					// TODO: pipeline remain data
					assert(bytes > remain);
					assert(0 == remain || '$' == *(end - remain));
				}
				p = end - remain;
			}
		} while (p < end && 0 == code);
		
		if (code >= 0)
		{
			// need more data
			code = aio_transport_recv(session->aio, session->buffer, sizeof(session->buffer));
		}
	}

	// error or peer closed
	if (0 != code || 0 == bytes)
	{
		session->onerror(session->param, session->rtsp, code ? code : ECONNRESET);
		aio_transport_destroy(session->aio);
	}
}

static void rtsp_session_onsend(void* param, int code, size_t bytes)
{
	struct rtsp_session_t *session;
	session = (struct rtsp_session_t *)param;
//	session->server->onsend(session, code, bytes);
	if (0 != code)
	{
		session->onerror(session->param, session->rtsp, code);
		aio_transport_destroy(session->aio);
	}
	(void)bytes;
}

static int rtsp_session_send(void* ptr, const void* data, size_t bytes)
{
	struct rtsp_session_t *session;
	session = (struct rtsp_session_t *)ptr;
	//return aio_tcp_transport_send(session->aio, data, bytes);

	// TODO: send multiple rtp packet once time
	return bytes == socket_send(session->socket, data, bytes, 0) ? 0 : -1;
}

int rtsp_transport_tcp_create(socket_t socket, const struct sockaddr* addr, socklen_t addrlen, struct aio_rtsp_handler_t* handler, void* param)
{
	char ip[65];
	unsigned short port;
	struct rtsp_session_t *session;
	struct rtsp_handler_t rtsphandler;
	struct aio_transport_handler_t h;

	memset(&h, 0, sizeof(h));
	h.ondestroy = rtsp_session_ondestroy;
	h.onrecv = rtsp_session_onrecv;
	h.onsend = rtsp_session_onsend;

	memcpy(&rtsphandler, &handler->base, sizeof(rtsphandler));
	rtsphandler.send = rtsp_session_send;

	session = (struct rtsp_session_t*)calloc(1, sizeof(*session));
	if (!session) return -ENOMEM;

	session->socket = socket;
	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->param = param;
	session->onerror = handler->onerror;
	session->aio = aio_transport_create(socket, &h, session);
	session->rtsp = rtsp_server_create(ip, port, &rtsphandler, param, session); // reuse-able, don't need create in every link
	if (!session->rtsp || !session->aio)
	{
		rtsp_session_ondestroy(session);
		return -ENOMEM;
	}
	
	session->rtp.param = param;
	session->rtp.onrtp = handler->onrtp;
	aio_transport_set_timeout(session->aio, TIMEOUT_RECV, TIMEOUT_SEND);
	if (0 != aio_transport_recv(session->aio, session->buffer, sizeof(session->buffer)))
	{
		rtsp_session_ondestroy(session);
		return -ENOTCONN;
	}

	return 0;
}
