/*
 * 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 "mrcp_media_agent.h"
#include "media_processor.h"
#include "codec_manager.h"
#include "rtp_session.h"

typedef struct mrcp_live_media_agent_t mrcp_live_media_agent_t;

struct mrcp_live_media_agent_t {
	mrcp_media_agent_t *media_agent;
	media_processor_t  *media_processor;

	apr_pool_t         *pool;
};

typedef struct mrcp_live_audio_channel_t mrcp_live_audio_channel_t;

struct mrcp_live_audio_channel_t {
	mrcp_audio_channel_t  *channel;
	rtp_session_t         *rtp_session;
	media_context_t       *rx_media_context;
	media_context_t       *tx_media_context;
	mrcp_audio_direction_t active_direction;

	apr_pool_t            *pool;
};

typedef enum {
	MRCP_LIVE_EVENT_AGENT_PROCESS,
	MRCP_LIVE_EVENT_AGENT_STARTED,
	MRCP_LIVE_EVENT_AGENT_TERMINATED
} mrcp_live_event_id;


typedef struct mrcp_live_event_t mrcp_live_event_t;

struct mrcp_live_event_t {
	mrcp_live_event_id           event_id;

};


static mrcp_status_t mrcp_live_agent_destroy(mrcp_module_t *module);
static mrcp_module_state_t mrcp_live_agent_open(mrcp_module_t *module);
static mrcp_module_state_t mrcp_live_agent_close(mrcp_module_t *module);
static mrcp_status_t mrcp_live_agent_signal_handler(mrcp_module_t *module, apt_task_msg_t *msg);

static const mrcp_module_method_set_t module_method_set = {
	mrcp_live_agent_destroy,
	mrcp_live_agent_open,
	mrcp_live_agent_close,
	mrcp_live_agent_signal_handler
};


static mrcp_status_t mrcp_live_agent_channel_create(mrcp_media_agent_t *agent, mrcp_audio_channel_t *channel, apr_pool_t *pool);

static const mrcp_media_agent_method_set_t media_agent_method_set = {
	mrcp_live_agent_channel_create,
};


static mrcp_status_t mrcp_live_audio_channel_destroy(mrcp_audio_channel_t *channel);
static mrcp_status_t mrcp_live_audio_channel_modify(mrcp_audio_channel_t *channel, mrcp_audio_request_t request);
static mrcp_status_t mrcp_live_audio_channel_source_connect(mrcp_audio_channel_t *channel, audio_source_t *source);
static mrcp_status_t mrcp_live_audio_channel_source_disconnect(mrcp_audio_channel_t *channel, audio_source_t *source);
static mrcp_status_t mrcp_live_audio_channel_sink_connect(mrcp_audio_channel_t *channel, audio_sink_t *sink);
static mrcp_status_t mrcp_live_audio_channel_sink_disconnect(mrcp_audio_channel_t *channel, audio_sink_t *sink);

static const mrcp_audio_channel_method_set_t audio_channel_method_set = {
	mrcp_live_audio_channel_destroy,
	mrcp_live_audio_channel_modify,
	mrcp_live_audio_channel_source_connect,
	mrcp_live_audio_channel_source_disconnect,
	mrcp_live_audio_channel_sink_connect,
	mrcp_live_audio_channel_sink_disconnect,
};

static APR_INLINE mrcp_live_media_agent_t* mrcp_live_media_agent_get(mrcp_module_t *module)
{
	return ((mrcp_media_agent_t*)module)->object;
}




mrcp_media_agent_t* mrcp_live_media_agent_create(const char *ip, unsigned short rtp_port_min, unsigned short rtp_port_max, apr_pool_t *pool)
{
	mrcp_live_media_agent_t *live_agent = apr_palloc(pool,sizeof(mrcp_live_media_agent_t));
	live_agent->pool = pool;
	live_agent->media_processor = media_processor_create(pool);
	if(!live_agent->media_processor) {
		return NULL;
	}
	live_agent->media_agent = mrcp_media_agent_create(
		&media_agent_method_set,
		&module_method_set,
		live_agent,
		pool);
	apt_log(APT_PRIO_NOTICE,"Create Media Agent %s:[%hu,%hu]\n",ip,rtp_port_min,rtp_port_max);
	if(ip) {
		live_agent->media_agent->ip = apr_pstrdup(pool,ip);
	}
	live_agent->media_agent->rtp_port_min = rtp_port_min;
	live_agent->media_agent->rtp_port_max = rtp_port_max;
	live_agent->media_agent->rtp_port_cur = rtp_port_min;

	return live_agent->media_agent;
}

static mrcp_status_t mrcp_live_agent_destroy(mrcp_module_t *module)
{
	mrcp_live_media_agent_t *live_agent = mrcp_live_media_agent_get(module);
	media_processor_destroy(live_agent->media_processor);

	apt_log(APT_PRIO_NOTICE,"Destroy Media Agent\n");

	live_agent->pool = NULL;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_module_state_t mrcp_live_agent_open(mrcp_module_t *module)
{
	mrcp_live_media_agent_t *live_agent = mrcp_live_media_agent_get(module);
	media_processor_start(live_agent->media_processor);
	return MODULE_STATE_OPENED;
}

static mrcp_module_state_t mrcp_live_agent_close(mrcp_module_t *module)
{
	mrcp_live_media_agent_t *live_agent = mrcp_live_media_agent_get(module);
	media_processor_terminate(live_agent->media_processor);
	return MODULE_STATE_CLOSED;
}

static APR_INLINE mrcp_live_media_agent_t* mrcp_live_agent_get(mrcp_audio_channel_t *channel)
{
	if(channel && channel->agent) {
		return channel->agent->object;
	}
	return NULL;
}

static mrcp_status_t mrcp_live_audio_channel_open_tx(mrcp_live_audio_channel_t *live_channel)
{
	codec_descriptor_t *codec_descriptor;
	mrcp_audio_channel_t *audio_channel = live_channel->channel;
	mrcp_live_media_agent_t *live_agent = mrcp_live_agent_get(audio_channel);
	codec_manager_t *codec_manager = media_processor_codec_manager_get(live_agent->media_processor);

	if(live_channel->active_direction & MRCP_AUDIO_SEND) {
		/* already opened */
		return MRCP_STATUS_SUCCESS;
	}

	apt_log(APT_PRIO_INFO,"Open RTP Transmit %s:%hu -> %s:%hu\n",
			audio_channel->local_media->base.ip,
			audio_channel->local_media->base.port,
			audio_channel->remote_media->base.ip,
			audio_channel->remote_media->base.port);

	if(!audio_channel->local_media->codec_count) {
		return MRCP_STATUS_FAILURE;
	}

	codec_descriptor = audio_channel->local_media->codec_list[0];

	if(audio_channel->mode != MRCP_AUDIO_CHANNEL_NONE) {
		if(!audio_channel->audio_sink) {
			const codec_manipulator_t *codec_manipulator;
			codec_manipulator = codec_manager_codec_get(codec_manager,codec_descriptor);

			audio_channel->audio_sink = rtp_session_tx_stream_create(
								live_channel->rtp_session,
								audio_channel->remote_media->base.ip,
								audio_channel->remote_media->base.port,
								codec_descriptor,
								codec_manipulator,
								audio_channel->remote_media->ptime);
		}
	}

	if(audio_channel->mode == MRCP_AUDIO_CHANNEL_PASSIVE) {
		if(!live_channel->tx_media_context) {
			live_channel->tx_media_context = media_connector_create(
				codec_descriptor->sampling_rate,
				live_channel->pool);
			media_processor_context_add(
				live_agent->media_processor,
				live_channel->tx_media_context,
				MEDIA_REQUEST_TYPE_ASYNC);
		}
	}

	if(live_channel->tx_media_context && audio_channel->audio_sink) {
		media_context_sink_add(
				live_agent->media_processor,
				live_channel->tx_media_context,
				audio_channel->audio_sink,
				MEDIA_REQUEST_TYPE_ASYNC);
	}

	live_channel->active_direction |= MRCP_AUDIO_SEND;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_close_tx(mrcp_live_audio_channel_t *live_channel)
{
	mrcp_audio_channel_t *audio_channel = live_channel->channel;

	if((live_channel->active_direction & MRCP_AUDIO_SEND) == 0) {
		/* already closed */
		return MRCP_STATUS_SUCCESS;
	}

	apt_log(APT_PRIO_INFO,"Close RTP Transmit %s:%hu -> %s:%hu\n",
			audio_channel->local_media->base.ip,
			audio_channel->local_media->base.port,
			audio_channel->remote_media->base.ip,
			audio_channel->remote_media->base.port);

	if(live_channel->tx_media_context && audio_channel->audio_sink) {
		mrcp_live_media_agent_t *live_agent = mrcp_live_agent_get(audio_channel);
		media_context_sink_remove(
				live_agent->media_processor,
				live_channel->tx_media_context,
				audio_channel->audio_sink,
				MEDIA_REQUEST_TYPE_ASYNC);
	}

	live_channel->active_direction &= ~MRCP_AUDIO_SEND;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_open_rx(mrcp_live_audio_channel_t *live_channel)
{
	codec_descriptor_t *codec_descriptor;
	mrcp_audio_channel_t *audio_channel = live_channel->channel;
	mrcp_live_media_agent_t *live_agent = mrcp_live_agent_get(audio_channel);
	codec_manager_t *codec_manager = media_processor_codec_manager_get(live_agent->media_processor);

	if(live_channel->active_direction & MRCP_AUDIO_RECEIVE) {
		/* already opened */
		return MRCP_STATUS_SUCCESS;
	}

	apt_log(APT_PRIO_INFO,"Open RTP Receive %s:%hu <- %s:%hu\n",
			audio_channel->local_media->base.ip,
			audio_channel->local_media->base.port,
			audio_channel->remote_media->base.ip,
			audio_channel->remote_media->base.port);

	if(!audio_channel->local_media->codec_count) {
		return MRCP_STATUS_FAILURE;
	}

	codec_descriptor = audio_channel->local_media->codec_list[0];

	if(audio_channel->mode != MRCP_AUDIO_CHANNEL_NONE) {
		if(!audio_channel->audio_source) {
			const codec_manipulator_t *codec_manipulator;
			codec_manipulator = codec_manager_codec_get(codec_manager,codec_descriptor);

			audio_channel->audio_source = rtp_session_rx_stream_create(
								live_channel->rtp_session,
								audio_channel->remote_media->base.ip,
								audio_channel->remote_media->base.port,
								codec_descriptor,
								codec_manipulator);
		}
	}

	if(audio_channel->mode == MRCP_AUDIO_CHANNEL_PASSIVE) {
		if(!live_channel->rx_media_context) {
			live_channel->rx_media_context = media_connector_create(
				codec_descriptor->sampling_rate,
				live_channel->pool);
			media_processor_context_add(
				live_agent->media_processor,
				live_channel->rx_media_context,
				MEDIA_REQUEST_TYPE_ASYNC);
		}
	}

	if(live_channel->rx_media_context && audio_channel->audio_source) {
		media_context_source_add(
				live_agent->media_processor,
				live_channel->rx_media_context,
				audio_channel->audio_source,
				MEDIA_REQUEST_TYPE_ASYNC);
	}

	live_channel->active_direction |= MRCP_AUDIO_RECEIVE;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_close_rx(mrcp_live_audio_channel_t *live_channel)
{
	mrcp_audio_channel_t *audio_channel = live_channel->channel;

	if((live_channel->active_direction & MRCP_AUDIO_RECEIVE) == 0) {
		/* already closed */
		return MRCP_STATUS_SUCCESS;
	}

	apt_log(APT_PRIO_INFO,"Close RTP Receive %s:%hu <- %s:%hu\n",
			audio_channel->local_media->base.ip,
			audio_channel->local_media->base.port,
			audio_channel->remote_media->base.ip,
			audio_channel->remote_media->base.port);

	if(live_channel->rx_media_context && audio_channel->audio_source) {
		mrcp_live_media_agent_t *live_agent = mrcp_live_agent_get(audio_channel);
		media_context_source_remove(
				live_agent->media_processor,
				live_channel->rx_media_context,
				audio_channel->audio_source,
				MEDIA_REQUEST_TYPE_ASYNC);
	}
	
	live_channel->active_direction &= ~MRCP_AUDIO_RECEIVE;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_on_answer(mrcp_audio_channel_t *channel)
{
	if(channel->local_media->base.state != channel->remote_media->base.state) {
		if(channel->local_media->base.state == MRCP_MEDIA_ENABLED) {
			channel->local_media->base.state = MRCP_MEDIA_DISABLED;
		}
	}

	if(channel->local_media->base.state == MRCP_MEDIA_ENABLED) {
		if(channel->local_media->direction & MRCP_AUDIO_SEND) {
			if(channel->remote_media->direction & MRCP_AUDIO_RECEIVE) {
				/* check/create tx context */
				/* open tx */
				mrcp_live_audio_channel_open_tx(channel->object);
			}
			else {
				/* close tx */
				mrcp_live_audio_channel_close_tx(channel->object);
			}
		}
		else {
			/* close tx */
			mrcp_live_audio_channel_close_tx(channel->object);
		}

		if(channel->local_media->direction & MRCP_AUDIO_RECEIVE) {
			if(channel->remote_media->direction & MRCP_AUDIO_SEND) {
				/* check/create rx context */
				/* open rx */
				mrcp_live_audio_channel_open_rx(channel->object);
			}
			else {
				/* close rx */
				mrcp_live_audio_channel_close_rx(channel->object);
			}
		}
		else {
			/* close rx */
			mrcp_live_audio_channel_close_rx(channel->object);
		}
	}
	else { /* MRCP_MEDIA_DISABLED */
		/* close tx */
		mrcp_live_audio_channel_close_tx(channel->object);
		/* close rx */
		mrcp_live_audio_channel_close_rx(channel->object);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_on_offer(mrcp_audio_channel_t *channel)
{
	if(channel->local_media->base.state != channel->remote_media->base.state) {
		channel->local_media->base.state = channel->remote_media->base.state;
	}

	if(channel->local_media->base.state == MRCP_MEDIA_ENABLED) {
		if(channel->remote_media->direction & MRCP_AUDIO_RECEIVE) {
			if(channel->local_media->direction & MRCP_AUDIO_SEND) {
				/* open (update) tx */
				mrcp_live_audio_channel_open_tx(channel->object);
			}
			else {
				channel->local_media->direction |= MRCP_AUDIO_SEND;
				/* check/create tx context */
				/* open tx */
				mrcp_live_audio_channel_open_tx(channel->object);
			}
		}
		else {
			if(channel->local_media->direction & MRCP_AUDIO_SEND) {
				channel->local_media->direction &= ~MRCP_AUDIO_SEND;
				/* close tx */
				mrcp_live_audio_channel_close_tx(channel->object);
			}
			else {
				/* close (update) tx */
				mrcp_live_audio_channel_close_tx(channel->object);
			}
		}

		if(channel->remote_media->direction & MRCP_AUDIO_SEND) {
			if(channel->local_media->direction & MRCP_AUDIO_RECEIVE) {
				/* open (update) rx */
				mrcp_live_audio_channel_open_rx(channel->object);
			}
			else {
				channel->local_media->direction |= MRCP_AUDIO_RECEIVE;
				/* check/create rx context */
				/* open rx */
				mrcp_live_audio_channel_open_rx(channel->object);
			}
		}
		else {
			if(channel->local_media->direction & MRCP_AUDIO_RECEIVE) {
				channel->local_media->direction &= ~MRCP_AUDIO_RECEIVE;
				/* close rx */
				mrcp_live_audio_channel_close_rx(channel->object);
			}
			else {
				/* close (update) rx */
				mrcp_live_audio_channel_close_rx(channel->object);
			}
		}
	}
	else { /* MRCP_MEDIA_DISABLED */
		/* close (update) tx */
		mrcp_live_audio_channel_close_tx(channel->object);
		/* close (update) rx */
		mrcp_live_audio_channel_close_rx(channel->object);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_agent_channel_create(
											mrcp_media_agent_t *agent, 
											mrcp_audio_channel_t *channel, 
											apr_pool_t *pool)
{
	mrcp_live_media_agent_t *live_agent = agent->object;
	mrcp_live_audio_channel_t *live_channel = apr_palloc(pool,sizeof(mrcp_live_audio_channel_t));

	channel->object = live_channel;
	channel->agent = agent;
	channel->method_set = &audio_channel_method_set;
	
	apt_log(APT_PRIO_INFO,"Create Audio Channel\n");
	live_channel->pool = pool;
	live_channel->channel = channel;
	live_channel->active_direction = MRCP_AUDIO_NONE;
	live_channel->rx_media_context = NULL;
	live_channel->tx_media_context = NULL;
	live_channel->rtp_session = NULL;
	if(channel->mode != MRCP_AUDIO_CHANNEL_NONE) {
		mrcp_media_agent_request(agent,channel->local_media,pool);
		live_channel->rtp_session = rtp_session_create(
									channel->local_media->base.ip,
									channel->local_media->base.port,
									live_channel->pool);
		if(!live_channel->rtp_session) {
			return MRCP_STATUS_FAILURE;
		}
	}

	if(!channel->local_media->codec_count && !channel->remote_media->codec_count) {
		codec_manager_t *codec_manager = media_processor_codec_manager_get(live_agent->media_processor);
		channel->local_media->codec_count = codec_manager_codec_list_get(
												codec_manager,channel->local_media->codec_list,
												MRCP_MAX_CODEC_COUNT,
												pool);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_destroy(mrcp_audio_channel_t *channel)
{
	mrcp_live_media_agent_t *live_agent = channel->agent->object;
	mrcp_live_audio_channel_t *live_channel = channel->object;

	apt_log(APT_PRIO_INFO,"Destroy Audio Channel\n");
	/* close (update) tx */
	mrcp_live_audio_channel_close_tx(channel->object);
	/* close (update) rx */
	mrcp_live_audio_channel_close_rx(channel->object);

	if(live_channel->tx_media_context) {
		media_processor_context_remove(
			live_agent->media_processor,
			live_channel->tx_media_context,
			MEDIA_REQUEST_TYPE_SYNC);
		media_context_destroy(live_channel->tx_media_context);
		live_channel->tx_media_context = NULL;
	}
	if(live_channel->rx_media_context) {
		media_processor_context_remove(
			live_agent->media_processor,
			live_channel->rx_media_context,
			MEDIA_REQUEST_TYPE_SYNC);
		media_context_destroy(live_channel->rx_media_context);
		live_channel->rx_media_context = NULL;
	}

	if(live_channel->rtp_session) {
		rtp_session_tx_stream_destroy(live_channel->rtp_session);
		rtp_session_rx_stream_destroy(live_channel->rtp_session);
		channel->audio_sink = NULL;
		channel->audio_source = NULL;

		rtp_session_destroy(live_channel->rtp_session);
		live_channel->rtp_session = NULL;
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_modify(mrcp_audio_channel_t *channel, mrcp_audio_request_t request)
{
	mrcp_live_media_agent_t *live_agent = channel->agent->object;
	mrcp_live_audio_channel_t *live_channel = channel->object;
	codec_manager_t *codec_manager = media_processor_codec_manager_get(live_agent->media_processor);
	apt_log(APT_PRIO_INFO,"Modify Audio Channel\n");

	if(channel->remote_media->base.state == MRCP_MEDIA_ENABLED) {
		size_t i;
		codec_descriptor_t *local_descriptor;
		codec_descriptor_t *remote_descriptor;
		channel->local_media->codec_count = 0;
		for(i=0; i<channel->remote_media->codec_count; i++) {
			remote_descriptor = channel->remote_media->codec_list[i];
			if(!remote_descriptor) {
				continue;
			}
			
			if(codec_manager_codec_get(codec_manager,remote_descriptor)) {
				local_descriptor = apr_palloc(live_channel->pool,sizeof(codec_descriptor_t));
				*local_descriptor = *remote_descriptor;

				channel->local_media->codec_list[channel->local_media->codec_count++] = local_descriptor;
			}
		}
	}

	switch(request) {
		case MRCP_AUDIO_REQUEST_RECEIVE_ANSWER:
			mrcp_live_audio_channel_on_answer(channel);
			break;
		case MRCP_AUDIO_REQUEST_RECEIVE_OFFER:
			mrcp_live_audio_channel_on_offer(channel);
			break;
	}

	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_source_connect(mrcp_audio_channel_t *channel, audio_source_t *source)
{
	mrcp_live_media_agent_t *live_agent = channel->agent->object;
	mrcp_live_audio_channel_t *live_channel = channel->object;
	if(live_channel->tx_media_context) {
		media_context_source_add(live_agent->media_processor,live_channel->tx_media_context,source,MEDIA_REQUEST_TYPE_ASYNC);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_source_disconnect(mrcp_audio_channel_t *channel, audio_source_t *source)
{
	mrcp_live_media_agent_t *live_agent = channel->agent->object;
	mrcp_live_audio_channel_t *live_channel = channel->object;
	if(live_channel->tx_media_context) {
		media_context_source_remove(live_agent->media_processor,live_channel->tx_media_context,source,MEDIA_REQUEST_TYPE_SYNC);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_sink_connect(mrcp_audio_channel_t *channel, audio_sink_t *sink)
{
	mrcp_live_media_agent_t *live_agent = channel->agent->object;
	mrcp_live_audio_channel_t *live_channel = channel->object;
	if(live_channel->rx_media_context) {
		media_context_sink_add(live_agent->media_processor,live_channel->rx_media_context,sink,MEDIA_REQUEST_TYPE_ASYNC);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_audio_channel_sink_disconnect(mrcp_audio_channel_t *channel, audio_sink_t *sink)
{
	mrcp_live_media_agent_t *live_agent = channel->agent->object;
	mrcp_live_audio_channel_t *live_channel = channel->object;
	if(live_channel->rx_media_context) {
		media_context_sink_remove(live_agent->media_processor,live_channel->rx_media_context,sink,MEDIA_REQUEST_TYPE_SYNC);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_live_agent_signal_handler(mrcp_module_t *module, apt_task_msg_t *msg)
{
	return MRCP_STATUS_SUCCESS;
}
