/*
 * OpenMRCP - Open Source Media Resource Control Protocol Stack
 * Copyright (C) 2007, Cepstral LLC
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Author(s):
 * Arsen Chaloyan <achaloyan@yahoo.com>
 *
 * Contributor(s):
 *
 */

#include <apr_poll.h>
#include "rtsp_connection.h"
#include "apt_task.h"
#include "apt_log.h"

#define RTSP_MESSAGE_MAX_SIZE 2048
#define RTSP_CLIENT_MAX_COUNT 10

typedef struct rtsp_server_connection_t rtsp_server_connection_t;

struct rtsp_server_connection_t {
	rtsp_connection_t base;

	apr_socket_t     *sock; /* accepted socket */
	apr_pollfd_t      sock_pfd;
};


typedef struct rtsp_server_agent_t rtsp_server_agent_t;

struct rtsp_server_agent_t {
	rtsp_connection_agent_t base;

	apt_task_t             *task;

	apr_pool_t             *pool;
	apr_pollset_t          *pollset;

	apr_sockaddr_t         *sockaddr;
	apr_socket_t           *listen_sock; /* listening socket */
	apr_pollfd_t            listen_sock_pfd;

	char                   *resource_location;
};

static apt_bool_t rtsp_agent_destroy(rtsp_connection_agent_t *agent);
static apt_bool_t rtsp_agent_open(rtsp_connection_agent_t *agent);
static apt_bool_t rtsp_agent_close(rtsp_connection_agent_t *agent);
static rtsp_connection_t* rtsp_agent_connect(rtsp_connection_agent_t *agent);
static apt_bool_t rtsp_agent_disconnect(rtsp_connection_t *connection);
static apt_bool_t rtsp_agent_send(rtsp_connection_t *connection, rtsp_message_t *message);
static apt_bool_t rtsp_agent_connection_destroy(rtsp_connection_t *connection);

static const rtsp_connection_agent_method_set_t rtsp_server_agent_method_set = {
	rtsp_agent_destroy,
	rtsp_agent_open,
	rtsp_agent_close,
	rtsp_agent_connect,
	rtsp_agent_disconnect,
	rtsp_agent_send,
	rtsp_agent_connection_destroy
};

static void rtsp_agent_on_terminate_request(void *data);
static void rtsp_agent_on_terminate_complete(void *data);

static void rtsp_agent_main_loop(void *data);

static apt_bool_t rtsp_agent_socket_create(rtsp_server_agent_t *agent);


/** Create RTSP server connection agent. */
rtsp_connection_agent_t* rtsp_server_agent_create(rtsp_connection_event_handler_t *event_handler,
												  const char *listen_ip, 
												  apr_port_t listen_port, 
												  const char *resource_location,
												  apr_pool_t *pool)
{
	rtsp_server_agent_t *agent;
	
	apt_log(APT_PRIO_NOTICE,"Create RTSP Connection Agent %s:%hu\n",listen_ip,listen_port);
	agent = apr_palloc(pool,sizeof(rtsp_server_agent_t));
	agent->pool = pool;
	agent->listen_sock = NULL;
	agent->pollset = NULL;
	agent->sockaddr = NULL;

	apr_sockaddr_info_get(&agent->sockaddr,listen_ip,APR_INET,listen_port,0,agent->pool);
	if(!agent->sockaddr) {
		return NULL;
	}

	agent->resource_location = apr_pstrdup(pool,resource_location);

	agent->task = apt_task_create(agent,rtsp_agent_main_loop,agent->pool);
	if(agent->task) {
		apt_task_event_handler_set(agent->task,TASK_STATE_TERMINATE_REQUESTED,agent,rtsp_agent_on_terminate_request);
		apt_task_event_handler_set(agent->task,TASK_STATE_TERMINATE_COMPLETED,agent,rtsp_agent_on_terminate_complete);
	}
	
	agent->base.method_set = &rtsp_server_agent_method_set;
	agent->base.event_handler = event_handler;
	return &agent->base;
}

static apt_bool_t rtsp_agent_destroy(rtsp_connection_agent_t *agent)
{
	rtsp_server_agent_t *server_agent = (rtsp_server_agent_t*)agent;
	apt_log(APT_PRIO_NOTICE,"Destroy RTSP Connection Agent\n");
	if(server_agent->task) {
		apt_task_destroy(server_agent->task);
	}

	return TRUE;
}

static apt_bool_t rtsp_agent_open(rtsp_connection_agent_t *agent)
{
	rtsp_server_agent_t *server_agent = (rtsp_server_agent_t*)agent;
	apt_log(APT_PRIO_INFO,"Open RTSP Connection Agent\n");
	if(rtsp_agent_socket_create(server_agent) != TRUE) {
		apt_log(APT_PRIO_WARNING,"Failed to Open RTSP Connection Agent\n");
		return FALSE;
	}
	apt_task_start(server_agent->task);
	return TRUE;
}

static apt_bool_t rtsp_agent_close(rtsp_connection_agent_t *agent)
{
	rtsp_server_agent_t *server_agent = (rtsp_server_agent_t*)agent;
	apt_log(APT_PRIO_INFO,"Close RTSP Connection Agent\n");
	apt_task_terminate(server_agent->task,TRUE);
	return TRUE;
}

static rtsp_connection_t* rtsp_agent_connect(rtsp_connection_agent_t *agent)
{
	/* nothing to do for the server connection */
	return NULL;
}

static apt_bool_t rtsp_agent_disconnect(rtsp_connection_t *connection)
{
	/* nothing to do for the server connection */
	return TRUE;
}

static apt_bool_t rtsp_agent_send(rtsp_connection_t *connection, rtsp_message_t *message)
{
	char buffer[RTSP_MESSAGE_MAX_SIZE];
	apt_text_stream_t text_stream;
	size_t size = sizeof(buffer)-1;

	rtsp_server_connection_t *server_connection = (rtsp_server_connection_t *)connection;
	
	if(!server_connection || !server_connection->sock) {
		return FALSE;
	}

	text_stream.buffer = buffer;
	text_stream.size = size;
	text_stream.pos = text_stream.buffer;

	if(rtsp_message_generate(message,&text_stream) != TRUE) {
		apt_log(APT_PRIO_WARNING,"Failed to Generate RTSP Message\n");
		return FALSE;
	}
	*text_stream.pos = '\0';
	apt_log(APT_PRIO_DEBUG,"Send RTSP Message size=%lu\n%s\n",text_stream.size,text_stream.buffer);
	apr_socket_send(server_connection->sock,text_stream.buffer,&text_stream.size);
	return TRUE;
}

static apt_bool_t rtsp_agent_connection_destroy(rtsp_connection_t *connection)
{
	if(!connection || !connection->pool) {
		return FALSE;
	}

	apr_pool_destroy(connection->pool);
	connection->pool = NULL;
	return TRUE;
}


static apt_bool_t rtsp_agent_socket_create(rtsp_server_agent_t *agent)
{
	apr_status_t status;

	if(!agent->sockaddr) {
		return FALSE;
	}

	status = apr_socket_create(&agent->listen_sock, agent->sockaddr->family, SOCK_STREAM, APR_PROTO_TCP, agent->pool);
	if(status != APR_SUCCESS) {
		return FALSE;
	}

	apr_socket_opt_set(agent->listen_sock, APR_SO_NONBLOCK, 0);
	apr_socket_timeout_set(agent->listen_sock, -1);
	apr_socket_opt_set(agent->listen_sock, APR_SO_REUSEADDR, 1);

	status = apr_socket_bind(agent->listen_sock, agent->sockaddr);
	if(status != APR_SUCCESS) {
		apr_socket_close(agent->listen_sock);
		agent->listen_sock = NULL;
		return FALSE;
	}
	status = apr_socket_listen(agent->listen_sock, SOMAXCONN);
	if(status != APR_SUCCESS) {
		apr_socket_close(agent->listen_sock);
		agent->listen_sock = NULL;
		return FALSE;
	}

	status = apr_pollset_create(&agent->pollset, RTSP_CLIENT_MAX_COUNT + 2, agent->pool, 0);
	if(status != APR_SUCCESS) {
		apr_socket_close(agent->listen_sock);
		agent->listen_sock = NULL;
		return FALSE;
	}
	
	agent->listen_sock_pfd.desc_type = APR_POLL_SOCKET;
	agent->listen_sock_pfd.reqevents = APR_POLLIN;
	agent->listen_sock_pfd.desc.s = agent->listen_sock;
	agent->listen_sock_pfd.client_data = agent->listen_sock;
	status = apr_pollset_add(agent->pollset, &agent->listen_sock_pfd);
	if(status != APR_SUCCESS) {
		apr_socket_close(agent->listen_sock);
		agent->listen_sock = NULL;
		apr_pollset_destroy(agent->pollset);
		agent->pollset = NULL;
		return FALSE;
	}

	return TRUE;
}

static apt_bool_t rtsp_agent_socket_destroy(rtsp_server_agent_t *agent)
{
	if(agent->listen_sock) {
		apr_pollset_remove(agent->pollset,&agent->listen_sock_pfd);
		apr_socket_close(agent->listen_sock);
		agent->listen_sock = NULL;
	}
	if(agent->pollset) {
		apr_pollset_destroy(agent->pollset);
		agent->pollset = NULL;
	}

	return TRUE;
}

static apt_bool_t rtsp_agent_connection_accept(rtsp_server_agent_t *agent)
{
	apr_socket_t *sock;
	apr_status_t status;
	apr_pool_t *pool;
	rtsp_server_connection_t *server_connection;

	if(apr_pool_create(&pool,/*NULL*/agent->pool) != APR_SUCCESS) {
		return FALSE;
	}

	status = apr_socket_accept(&sock, agent->listen_sock, agent->pool);
	if(status != APR_SUCCESS) {
		apr_pool_destroy(pool);
		return FALSE;
	}

	apt_log(APT_PRIO_NOTICE,"RTSP Client Connected\n");
	server_connection = apr_palloc(pool,sizeof(rtsp_server_connection_t));
	server_connection->base.pool = pool;
	server_connection->base.agent = &agent->base;
	server_connection->base.ref_count = 0;

	server_connection->sock = sock;
	server_connection->sock_pfd.desc_type = APR_POLL_SOCKET;
	server_connection->sock_pfd.reqevents = APR_POLLIN;
	server_connection->sock_pfd.desc.s = server_connection->sock;
	server_connection->sock_pfd.client_data = server_connection;
	apr_pollset_add(agent->pollset, &server_connection->sock_pfd);

	if(agent->base.event_handler) {
		agent->base.event_handler->event_set->on_connect(&server_connection->base);
	}
	return TRUE;
}

static apt_bool_t rtsp_agent_connection_close(rtsp_server_agent_t *agent, rtsp_server_connection_t *server_connection)
{
	apt_log(APT_PRIO_NOTICE,"RTSP Client Disconnected\n");
	apr_pollset_remove(agent->pollset,&server_connection->sock_pfd);
	apr_socket_close(server_connection->sock);
	server_connection->sock = NULL;

	if(agent->base.event_handler) {
		agent->base.event_handler->event_set->on_disconnect(&server_connection->base);
	}
	return TRUE;
}

static apt_bool_t rtsp_agent_messsage_receive(rtsp_server_agent_t *agent, rtsp_server_connection_t *server_connection)
{
	char buffer[RTSP_MESSAGE_MAX_SIZE];
	apr_size_t size = sizeof(buffer)-1;
	int more_messages_on_buffer = FALSE;
	apr_status_t status;
	apt_text_stream_t text_stream;
	rtsp_message_t *message;

	if(!server_connection || !server_connection->sock) {
		return FALSE;
	}
	
	status = apr_socket_recv(server_connection->sock, buffer, &size);
	if(status == APR_EOF || size == 0) {
		return rtsp_agent_connection_close(agent,server_connection);
	}
	buffer[size] = '\0';

	text_stream.buffer = buffer;
	text_stream.size = size;
	text_stream.pos = buffer;

	apt_log(APT_PRIO_DEBUG,"Receive RTSP Message size=%lu\n%s\n",text_stream.size,text_stream.buffer);
	do {
		message = rtsp_message_create(RTSP_MESSAGE_TYPE_UNKNOWN,server_connection->base.pool);
		if(rtsp_message_parse(message,&text_stream) == TRUE) {
			if(agent->base.event_handler) {
				apt_log(APT_PRIO_DEBUG,"Signal RTSP Message\n");
				agent->base.event_handler->event_set->on_receive(&server_connection->base,message);
			}
		}
		else {
			apt_log(APT_PRIO_WARNING,"Failed to Parse RTSP Message\n");
			if(message->start_line.message_type == RTSP_MESSAGE_TYPE_REQUEST) {
				rtsp_message_t *response = rtsp_response_create(message,
					RTSP_STATUS_CODE_BAD_REQUEST,RTSP_REASON_PHRASE_BAD_REQUEST,message->pool);
				rtsp_agent_send(&server_connection->base,response);
			}
			
			/* skip to the end of the buffer (support incomplete) */
			text_stream.pos = text_stream.buffer + text_stream.size;
		}

		more_messages_on_buffer = FALSE;
		if(text_stream.size > (size_t)(text_stream.pos - text_stream.buffer)) {
			/* there are more RTSP messages to signal */
			more_messages_on_buffer = TRUE;
			text_stream.size -= text_stream.pos - text_stream.buffer;
			text_stream.buffer = text_stream.pos;
			apt_log(APT_PRIO_DEBUG,"Saving remaining buffer for next message...\n");
		}
	}
	while(more_messages_on_buffer);
	
	return TRUE;
}


static void rtsp_agent_main_loop(void *data)
{
	rtsp_server_agent_t *agent = data;
	apr_status_t status;
	apr_int32_t num;
	const apr_pollfd_t *ret_pfd;
	int i;

	if(!agent || !agent->pollset) {
		apt_log(APT_PRIO_WARNING,"Failed to Start RTSP Agent\n");
		return;
	}

	while(apt_task_state_get(agent->task) != TASK_STATE_TERMINATE_REQUESTED) {
		status = apr_pollset_poll(agent->pollset, -1, &num, &ret_pfd);
		if(status != APR_SUCCESS) {
			continue;
		}
		for(i = 0; i < num; i++) {
			if(ret_pfd[i].desc.s == agent->listen_sock) {
				if(apt_task_state_get(agent->task) == TASK_STATE_TERMINATE_REQUESTED) {
					break;
				}

				rtsp_agent_connection_accept(agent);
				continue;
			}

			rtsp_agent_messsage_receive(agent,ret_pfd[i].client_data);
		}
	}
}


static void rtsp_agent_on_terminate_request(void *data)
{
	rtsp_server_agent_t *agent = data;

	if(agent->listen_sock) {
		apr_socket_t *tsock; /* terminating socket */
		if(apr_socket_create(&tsock, agent->sockaddr->family, SOCK_STREAM, APR_PROTO_TCP, agent->pool) == APR_SUCCESS) {
			apr_socket_connect(tsock, agent->sockaddr);
			apr_socket_close(tsock);
		}
	}
}

static void rtsp_agent_on_terminate_complete(void *data)
{
	rtsp_server_agent_t *agent = data;

	rtsp_agent_socket_destroy(agent);
}
