#include "aio-rtmp-server.h"
#include "aio-rtmp-transport.h"
#include "sockutil.h"
#include "sys/atomic.h"
#include "sys/locker.h"
#include "rtmp-server.h"
#include "aio-accept.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>

#define TIMEOUT_RECV 20000
#define TIMEOUT_SEND 20000

struct aio_rtmp_server_t
{
	void* listen; // listen aio socket

	struct aio_rtmp_server_handler_t handle;
	void* param;
};

struct aio_rtmp_session_t
{
	locker_t locker;
	socklen_t salen;
	struct sockaddr_storage sa;
	
	aio_rtmp_transport_t* aio; // aio rtmp transport
	aio_rtmp_userptr_t usr; // user-defined parameter(return by oncreate)
	rtmp_server_t* rtmp; // create by rtmp_server_create
	struct aio_rtmp_server_t* server;
};

static void aio_rtmp_server_onaccept(void* param, int code, socket_t socket, const struct sockaddr* sa, socklen_t salen);

static void rtmp_session_ondestroy(void* param);
static void rtmp_session_onsend(void* param, int code, size_t bytes);
static void rtmp_session_onrecv(void* param, int code, const void* data, size_t bytes);

static int rtmp_handler_send(void* param, const void* header, size_t len, const void* payload, size_t bytes);
static int rtmp_handler_onpublish(void* param, const char* app, const char* stream, const char* type);
static int rtmp_handler_onscript(void* param, const void* data, size_t bytes, uint32_t timestamp);
static int rtmp_handler_onaudio(void* param, const void* data, size_t bytes, uint32_t timestamp);
static int rtmp_handler_onvideo(void* param, const void* data, size_t bytes, uint32_t timestamp);
static int rtmp_handler_onplay(void* param, const char* app, const char* stream, double start, double duration, uint8_t reset);
static int rtmp_handler_onpause(void* param, int pause, uint32_t ms);
static int rtmp_handler_onseek(void* param, uint32_t ms);
static int rtmp_handler_ongetduration(void* param, const char* app, const char* stream, double* duration);

static void aio_rtmp_server_ondestroy(void* param)
{
	struct aio_rtmp_server_t *server;
	server = (struct aio_rtmp_server_t*)param;
	free(server);
}

struct aio_rtmp_server_t* aio_rtmp_server_create(const char* ip, int port, struct aio_rtmp_server_handler_t* handler, void* param)
{
	socket_t socket;
	struct aio_rtmp_server_t *ctx;

	// create server socket
	socket = socket_tcp_listen(0 /*AF_UNSPEC*/, ip, (u_short)port, SOMAXCONN, 0, 1);
	if (socket_invalid == socket)
	{
		printf("%s(%s, %d): create socket error: %d\n", __FUNCTION__, ip, port, socket_geterror());
		return NULL;
	}

	ctx = (struct aio_rtmp_server_t*)malloc(sizeof(*ctx));
	ctx->param = param;
	memcpy(&ctx->handle, handler, sizeof(ctx->handle));
	ctx->listen = aio_accept_start(socket, aio_rtmp_server_onaccept, ctx);
	if (!ctx->listen)
	{
		printf("%s(%s, %d) create aio transport error.\n", __FUNCTION__, ip, port);
		aio_rtmp_server_destroy(ctx);
		socket_close(socket);
		return NULL;
	}

	return ctx;
}

int aio_rtmp_server_destroy(struct aio_rtmp_server_t *server)
{
	if (server->listen)
	{
		return aio_accept_stop(server->listen, aio_rtmp_server_ondestroy, server);
	}
	else
	{
		aio_rtmp_server_ondestroy(server);
		return 0;
	}
}

int aio_rtmp_server_send_audio(struct aio_rtmp_session_t* session, const void* data, size_t bytes, uint32_t timestamp)
{
	return rtmp_server_send_audio(session->rtmp, data, bytes, timestamp);
}

int aio_rtmp_server_send_video(struct aio_rtmp_session_t* session, const void* data, size_t bytes, uint32_t timestamp)
{
	return rtmp_server_send_video(session->rtmp, data, bytes, timestamp);
}

int aio_rtmp_server_send_script(struct aio_rtmp_session_t* session, const void* data, size_t bytes, uint32_t timestamp)
{
	return rtmp_server_send_script(session->rtmp, data, bytes, timestamp);
}

size_t aio_rtmp_server_get_unsend(struct aio_rtmp_session_t* session)
{
	return aio_rtmp_transport_get_unsend(session->aio);
}

int aio_rtmp_server_get_addr(struct aio_rtmp_session_t* session, char ip[65], unsigned short* port)
{
	return socket_addr_to((struct sockaddr*)&session->sa, session->salen, ip, port);
}

static void aio_rtmp_server_onaccept(void* param, int code, socket_t socket, const struct sockaddr* sa, socklen_t salen)
{
	struct aio_rtmp_server_t* server;
	struct aio_rtmp_session_t* session;
	struct aio_rtmp_handler_t aiohandler;
	struct rtmp_server_handler_t handler;
	server = (struct aio_rtmp_server_t*)param;

	if (0 != code)
	{
		printf("aio_rtmp_server_onaccept code: %d\n", code);
		return;
	}

	socket_setnonblock(socket, 1);
	socket_setnondelay(socket, 1);

	session = (struct aio_rtmp_session_t*)malloc(sizeof(*session));
	if (session)
	{
		session->usr = NULL;
		session->server = server;
		locker_create(&session->locker);
		session->salen = salen < sizeof(session->sa) ? salen : sizeof(session->sa);
		memcpy(&session->sa, sa, session->salen);

		handler.send = rtmp_handler_send;
		handler.onplay = rtmp_handler_onplay;
		handler.onseek = rtmp_handler_onseek;
		handler.onpause = rtmp_handler_onpause;
		handler.onaudio = rtmp_handler_onaudio;
		handler.onvideo = rtmp_handler_onvideo;
		handler.onscript = rtmp_handler_onscript;
		handler.onpublish = rtmp_handler_onpublish;
		handler.ongetduration = server->handle.ongetduration ? rtmp_handler_ongetduration : NULL;
		session->rtmp = rtmp_server_create(session, &handler);

		aiohandler.onrecv = rtmp_session_onrecv;
		aiohandler.onsend = rtmp_session_onsend;
		aiohandler.ondestroy = rtmp_session_ondestroy;
		session->aio = aio_rtmp_transport_create(aio_socket_create(socket, 1), &aiohandler, session);
		aio_rtmp_transport_set_timeout(session->aio, TIMEOUT_RECV, TIMEOUT_SEND);
		aio_rtmp_transport_start(session->aio);
	}
}

static void rtmp_session_ondestroy(void* param)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	
	// notify session stop
	if(session->server->handle.onclose && session->usr)
		session->server->handle.onclose(session->usr);

	if (session->rtmp)
	{
		rtmp_server_destroy(session->rtmp);
		session->rtmp = NULL;
	}

	locker_destroy(&session->locker);
	session->aio = NULL;

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

static void rtmp_session_onrecv(void* param, int code, const void* data, size_t bytes)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;

	if(0 == code)
		code = rtmp_server_input(session->rtmp, data, bytes);
	if (0 != code)
		aio_rtmp_transport_destroy(session->aio);
}

static void rtmp_session_onsend(void* param, int code, size_t bytes)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	if (0 == code)
	{
		if (session->server->handle.onsend && session->usr)
			session->server->handle.onsend(session->usr, bytes);
	}
	else
	{
		aio_rtmp_transport_destroy(session->aio);
	}
}

static int rtmp_handler_send(void* param, const void* header, size_t len, const void* payload, size_t bytes)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	return aio_rtmp_transport_send(session->aio, header, len, payload, bytes);
}

static int rtmp_handler_onplay(void* param, const char* app, const char* stream, double start, double duration, uint8_t reset)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	session->usr = session->server->handle.onplay(session->server->param, session, app, stream, start, duration, reset);
	return session->usr ? 0 : -1;
}

static int rtmp_handler_onpause(void* param, int pause, uint32_t ms)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	if(session->server->handle.onpause && session->usr)
		return session->server->handle.onpause(session->usr, pause, ms);
	return 0;
}

static int rtmp_handler_onseek(void* param, uint32_t ms)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	if(session->server->handle.onseek && session->usr)
		return session->server->handle.onseek(session->usr, ms);
	return 0;
}

static int rtmp_handler_onpublish(void* param, const char* app, const char* stream, const char* type)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	session->usr = session->server->handle.onpublish(session->server->param, session, app, stream, type);
	return session->usr ? 0 : -1;
}

static int rtmp_handler_onscript(void* param, const void* data, size_t bytes, uint32_t timestamp)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	if (session->server->handle.onscript && session->usr)
		return session->server->handle.onscript(session->usr, data, bytes, timestamp);
	return 0;
}

static int rtmp_handler_onvideo(void* param, const void* data, size_t bytes, uint32_t timestamp)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	if(session->server->handle.onvideo && session->usr)
		return session->server->handle.onvideo(session->usr, data, bytes, timestamp);
	return 0;
}

static int rtmp_handler_onaudio(void* param, const void* data, size_t bytes, uint32_t timestamp)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	if(session->server->handle.onaudio && session->usr)
		return session->server->handle.onaudio(session->usr, data, bytes, timestamp);
	return 0;
}

static int rtmp_handler_ongetduration(void* param, const char* app, const char* stream, double* duration)
{
	struct aio_rtmp_session_t* session;
	session = (struct aio_rtmp_session_t*)param;
	if (session->server->handle.ongetduration)
		return session->server->handle.ongetduration(session->server->param, app, stream, duration);
	return -EINVAL;
}
