/*
 * 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 <assert.h>
#include "mrcp_client.h"
#include "mrcp_client_context.h"
#include "mrcp_client_defs.h"
#include "mrcp_client_session.h"
#include "mrcp_client_resource.h"
#include "mrcp_client_signaling_agent.h"
#include "mrcp_media_agent.h"
#include "mrcp_resource.h"
#include "apt_consumer_task.h"

typedef enum {
	MODULE_TYPE_CONTEXT,
	MODULE_TYPE_SIGNALING_AGENT,
	MODULE_TYPE_MRCP_AGENT,
	MODULE_TYPE_MEDIA_AGENT,
} mrcp_client_module_type;


static mrcp_status_t mrcp_client_on_module_open(mrcp_module_t *module);
static mrcp_status_t mrcp_client_on_module_close(mrcp_module_t *module);

static const mrcp_module_event_set_t module_event_set = {
	mrcp_client_on_module_open,
	mrcp_client_on_module_close
};


static mrcp_status_t mrcp_client_on_resource_discover(mrcp_signaling_agent_t *agent);

static const mrcp_signaling_agent_event_set_t signaling_agent_event_set = {
	mrcp_client_on_resource_discover
};

static mrcp_status_t mrcp_client_on_signaling_channel_answer(mrcp_signaling_channel_t *channel);
static mrcp_status_t mrcp_client_on_signaling_channel_terminate(mrcp_signaling_channel_t *channel);

static const mrcp_signaling_channel_event_set_t signaling_channel_event_set = {
	NULL, /*on_offer*/
	mrcp_client_on_signaling_channel_answer,
	mrcp_client_on_signaling_channel_terminate
};


static mrcp_status_t mrcp_client_on_server_disconnect(mrcp_connection_t *connection);
static mrcp_status_t mrcp_client_on_message_receive(mrcp_connection_t *connection, mrcp_message_t *mrcp_message);

static const mrcp_connection_event_set_t connection_event_set = {
	mrcp_client_on_server_disconnect,
	mrcp_client_on_message_receive
};



static APR_INLINE const char* mrcp_client_session_id_get(mrcp_session_t *mrcp_session)
{
	if(mrcp_session->id.hex_str) {
		return mrcp_session->id.hex_str;
	}
	else {
		static const char *empty_id = "new";
		return empty_id;
	}
}

static mrcp_status_t mrcp_client_do_signaling_channel_create(mrcp_client_t *mrcp_client, mrcp_session_t *session)
{
	if(!session->signaling_channel) {
		mrcp_signaling_channel_t *channel;
		channel = mrcp_client->signaling_agent->method_set->channel_create(mrcp_client->signaling_agent, session->pool);
		if(!channel) {
			return MRCP_STATUS_FAILURE;
		}
		channel->event_set = &signaling_channel_event_set;
		channel->session = session;
		session->signaling_channel = channel;
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_session_initiate(mrcp_client_t *mrcp_client, mrcp_session_t *session)
{
	if(!session) {
		apt_log(APT_PRIO_WARNING,"Failed to Initiate NULL MRCP Session\n");
		return MRCP_STATUS_FAILURE;
	}

	if(mrcp_client_do_signaling_channel_create(mrcp_client,session) != MRCP_STATUS_SUCCESS) {
		return MRCP_STATUS_FAILURE;
	}

	apt_log(APT_PRIO_NOTICE,"Modify MRCP Session <%s>\n",mrcp_client_session_id_get(session));
	session->signaling_channel->method_set->offer(session->signaling_channel);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_session_terminate(mrcp_client_t *mrcp_client, mrcp_session_t *session)
{
	if(!session || !session->signaling_channel) {
		apt_log(APT_PRIO_WARNING,"Failed to Terminate NULL MRCP Session\n");
		return MRCP_STATUS_FAILURE;
	}

	if(session->terminating == TRUE) {
		/* session termination is already in progress, nothing to do */
		return MRCP_STATUS_SUCCESS;
	}

	apt_log(APT_PRIO_NOTICE,"Terminate MRCP Session <%s>\n",mrcp_client_session_id_get(session));
	session->terminating = TRUE;
	session->signaling_channel->method_set->terminate(session->signaling_channel);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_audio_direction_t mrcp_client_audio_direction_get(mrcp_audio_stream_type audio_stream_type)
{
	mrcp_audio_direction_t audio_direction;
	switch(audio_stream_type) {
		case MRCP_AUDIO_STREAM_SOURCE:
			audio_direction = MRCP_AUDIO_RECEIVE;
			break;
		case MRCP_AUDIO_STREAM_SINK:
			audio_direction = MRCP_AUDIO_SEND;
			break;
		default:
			audio_direction = MRCP_AUDIO_NONE;
	}
	return audio_direction;
}

static mrcp_status_t mrcp_client_channel_add(mrcp_client_t *mrcp_client, mrcp_session_t *session, mrcp_client_channel_t *control_channel, mrcp_audio_channel_t *audio_channel)
{
	mrcp_media_control_t *control_media;
	mrcp_media_audio_t *audio_media;
	mrcp_audio_direction_t audio_direction;
	if(!session || !control_channel || !control_channel->resource) {
		apt_log(APT_PRIO_WARNING,"Failed to Add Channel: invalid params\n");
		return MRCP_STATUS_FAILURE;
	}

	if(mrcp_client_do_signaling_channel_create(mrcp_client,session) != MRCP_STATUS_SUCCESS) {
		return MRCP_STATUS_FAILURE;
	}

	apt_log(APT_PRIO_NOTICE,"Add Channel <%s@%s>\n",mrcp_client_session_id_get(session),control_channel->resource->name);
	control_channel->state = CHANNEL_STATE_PENDING_CREATE;

	control_media = mrcp_control_media_get(&session->signaling_channel->local_descriptor,control_channel->resource->name);
	if(control_media) {
		if(control_media->base.state == MRCP_MEDIA_ENABLED) {
			apt_log(APT_PRIO_WARNING,"Failed to Add Channel: already exists [%s]\n",control_channel->resource->name);
			return MRCP_STATUS_FAILURE;
		}
	}
	else {
		mrcp_media_control_t *remote_control_media;
		control_media = mrcp_control_media_create(session->pool);
		if(mrcp_descriptor_media_add(&session->signaling_channel->local_descriptor,&control_media->base) != MRCP_STATUS_SUCCESS) {
			return MRCP_STATUS_FAILURE;
		}
		remote_control_media = mrcp_control_media_create(session->pool);
		if(mrcp_descriptor_media_add(&session->signaling_channel->remote_descriptor,&remote_control_media->base) != MRCP_STATUS_SUCCESS) {
			return MRCP_STATUS_FAILURE;
		}
	}
	control_media->base.state = MRCP_MEDIA_ENABLED;
	control_media->proto = MRCP_PROTO_TCP;
	control_media->base.port = 9;
	control_media->setup_type = MRCP_CONTROL_SETUP_TYPE_ACTIVE;
	control_media->connection_type = MRCP_CONTROL_CONNECTION_TYPE_EXISTING;
	control_media->resource_name = control_channel->resource->name;

	mrcp_client_session_channel_set(session,control_channel,control_media->base.id);

	audio_direction = mrcp_client_audio_direction_get(control_channel->resource->audio_stream_type);
	audio_media = mrcp_audio_media_get_by_direction(&session->signaling_channel->local_descriptor,audio_direction);
	if(audio_media) {
		audio_channel = mrcp_client_session_channel_get(session,audio_media->base.id);
		if(!audio_channel) {
			return MRCP_STATUS_FAILURE;
		}
		audio_media->direction |= audio_direction;
		if(audio_media->base.state == MRCP_MEDIA_DISABLED) {
			audio_media->base.state = MRCP_MEDIA_ENABLED;
		}
	}
	else {
		if(!audio_channel) {
			mrcp_audio_channel_mode_t mode = MRCP_AUDIO_CHANNEL_PASSIVE;
			if(!control_channel->audio_sink && !control_channel->audio_source) {
				mode = MRCP_AUDIO_CHANNEL_ACTIVE;
			}
			audio_media = mrcp_audio_media_create(session->pool);
			audio_channel = mrcp_audio_channel_create(
									mode,
									audio_media,
									NULL,
									session->pool);
		}
		if(!audio_channel->local_media) {
			audio_channel->local_media = mrcp_audio_media_create(session->pool);
		}
		audio_media = audio_channel->local_media;
		audio_media->direction = audio_direction;
		if(mrcp_client->media_agent->method_set->channel_create(
								mrcp_client->media_agent,
								audio_channel,
								session->pool) != MRCP_STATUS_SUCCESS) {
			apt_log(APT_PRIO_WARNING,"Failed to Create Audio Channel\n");
			return MRCP_STATUS_FAILURE;
		}

		mrcp_descriptor_media_add(&session->signaling_channel->local_descriptor,&audio_channel->local_media->base);
		mrcp_descriptor_media_add(&session->signaling_channel->remote_descriptor,&audio_channel->remote_media->base);

		session->audio_media_count++;
		audio_media->mid = session->audio_media_count;

		mrcp_client_session_channel_set(session,audio_channel,audio_channel->local_media->base.id);
	}
	control_channel->audio_channel = audio_channel;
	control_media->cmid = audio_media->mid;

	return session->signaling_channel->method_set->offer(session->signaling_channel);
}

static mrcp_status_t mrcp_client_channel_remove(mrcp_client_t *mrcp_client, mrcp_session_t *session, mrcp_client_channel_t *channel)
{
	mrcp_media_control_t *control_media;
	mrcp_media_audio_t *audio_media;
	mrcp_audio_direction_t audio_direction;
	if(!session || !channel || !channel->resource || !channel->audio_channel) {
		apt_log(APT_PRIO_WARNING,"Failed to Remove Channel: invalid params\n");
		return MRCP_STATUS_FAILURE;
	}

	control_media = mrcp_control_media_get(&session->signaling_channel->local_descriptor,channel->resource->name);
	if(!control_media) {
		return MRCP_STATUS_FAILURE;
	}

	audio_media = channel->audio_channel->local_media;
	if(!audio_media) {
		return MRCP_STATUS_FAILURE;
	}

	control_media->base.state = MRCP_MEDIA_DISABLED;
	audio_direction = mrcp_client_audio_direction_get(channel->resource->audio_stream_type);
	audio_media->direction &= ~audio_direction;
	if(audio_media->direction == MRCP_AUDIO_NONE) {
		audio_media->base.state = MRCP_MEDIA_DISABLED;
	}


	apt_log(APT_PRIO_NOTICE,"Remove Channel <%s@%s>\n",mrcp_client_session_id_get(session),channel->resource->name);
	channel->state = CHANNEL_STATE_PENDING_DESTROY;

	return session->signaling_channel->method_set->offer(session->signaling_channel);
}

static mrcp_status_t mrcp_client_channel_modify(mrcp_client_t *mrcp_client, mrcp_session_t *session, mrcp_message_t *mrcp_message)
{
	apt_log(APT_PRIO_NOTICE,"Modify Channel <%s@%s>\n",mrcp_client_session_id_get(session),"");
	mrcp_message->channel_id.session_id = session->id;

	if(!session->connection) {
		apt_log(APT_PRIO_WARNING,"Failed to Modify Channel: null connection\n",mrcp_message->channel_id.session_id.hex_str);
		return MRCP_STATUS_FAILURE;
	}

	if(session->connection->method_set->send(session->connection,mrcp_message) != MRCP_STATUS_SUCCESS) {
		apt_log(APT_PRIO_WARNING,"Failed to Modify Channel\n",mrcp_message->channel_id.session_id.hex_str);
		return MRCP_STATUS_FAILURE;
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_resource_discover(mrcp_client_t *mrcp_client)
{
	apt_log(APT_PRIO_NOTICE,"Resource Discover\n");
	if(mrcp_client->signaling_agent->method_set->resource_discover) {
		mrcp_client->signaling_agent->method_set->resource_discover(mrcp_client->signaling_agent);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_context_msg_process(mrcp_client_t *mrcp_client, void *msg)
{
	mrcp_client_context_msg_t *context_msg = msg;
	mrcp_status_t status = MRCP_STATUS_SUCCESS;
	switch(context_msg->msg_id) {
		case MRCP_CLIENT_SESSION_DESTROY:
			mrcp_client_session_destroy(context_msg->session);
			break;
		case MRCP_CLIENT_SESSION_INITIATE:
			if(mrcp_client_session_initiate(mrcp_client,context_msg->session) != MRCP_STATUS_SUCCESS) {
				mrcp_client->client_context->event_handler->on_session_terminate(mrcp_client->client_context,context_msg->session);
			}
			break;
		case MRCP_CLIENT_SESSION_TERMINATE:
			if(mrcp_session_terminate(mrcp_client,context_msg->session) != MRCP_STATUS_SUCCESS) {
				mrcp_client->client_context->event_handler->on_session_terminate(mrcp_client->client_context,context_msg->session);
			}
			break;
		case MRCP_CLIENT_CHANNEL_ADD:
			if(mrcp_client_channel_add(mrcp_client,context_msg->session,context_msg->control_channel,context_msg->audio_channel) != MRCP_STATUS_SUCCESS) {
				mrcp_client->client_context->event_handler->on_channel_remove(mrcp_client->client_context,context_msg->session,context_msg->control_channel);
			}
			break;
		case MRCP_CLIENT_CHANNEL_REMOVE:
			if(mrcp_client_channel_remove(mrcp_client,context_msg->session,context_msg->control_channel) != MRCP_STATUS_SUCCESS) {
				mrcp_client->client_context->event_handler->on_channel_remove(mrcp_client->client_context,context_msg->session,context_msg->control_channel);
			}
			break;
		case MRCP_CLIENT_CHANNEL_MODIFY:
			if(mrcp_client_channel_modify(mrcp_client,context_msg->session,context_msg->mrcp_message) != MRCP_STATUS_SUCCESS) {
				mrcp_client->client_context->event_handler->on_channel_modify(mrcp_client->client_context,context_msg->session,NULL);
			}
			break;
		case MRCP_CLIENT_RESOURCE_DISCOVER:
			if(mrcp_client_resource_discover(mrcp_client) != MRCP_STATUS_SUCCESS) {
				mrcp_client->client_context->event_handler->on_resource_discover(mrcp_client->client_context,NULL);
			}
			break;
		default:
			status = MRCP_STATUS_FAILURE;
	}
	return status;
}


static APR_INLINE mrcp_client_t* mrcp_client_get(mrcp_module_t *module)
{
	return (mrcp_client_t*)module->engine;
}

static APR_INLINE mrcp_session_t* mrcp_client_session_find(mrcp_client_t *mrcp_client, const mrcp_session_id *session_id)
{
	return apr_hash_get(mrcp_client->session_table,session_id->hex_str,session_id->length);
}

static mrcp_status_t mrcp_client_on_server_disconnect(mrcp_connection_t *connection)
{
	mrcp_client_t *mrcp_client = mrcp_client_get(&connection->agent->module);
	apr_hash_index_t *hi;
	void *val;
	mrcp_session_t *session;
	for(hi=apr_hash_first(NULL,mrcp_client->session_table);hi;hi = apr_hash_next(hi)) {
		apr_hash_this(hi, NULL, NULL, &val);
		session = val;
		if(session && session->connection == connection) {
			mrcp_session_terminate(mrcp_client,session);
		}
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_on_message_receive(mrcp_connection_t *connection, mrcp_message_t *mrcp_message)
{
	mrcp_client_t *mrcp_client = mrcp_client_get(&connection->agent->module);
	mrcp_session_t *session;
	mrcp_media_control_t *control_media;
	mrcp_client_channel_t *control_channel;

	session = mrcp_client_session_find(mrcp_client,&mrcp_message->channel_id.session_id);
	if(!session) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Message: no such session <%s>\n",mrcp_message->channel_id.session_id.hex_str);
		return MRCP_STATUS_FAILURE;
	}

	control_media = mrcp_control_media_get(&session->signaling_channel->local_descriptor,mrcp_message->channel_id.resource_name);
	if(!control_media) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Message: no such media <%s@%s>\n",mrcp_message->channel_id.session_id.hex_str,mrcp_message->channel_id.resource_name);
		return MRCP_STATUS_FAILURE;
	}

	control_channel = mrcp_client_session_channel_get(session,control_media->base.id);
	if(!control_channel) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Message: no such channel <%s@%s>\n",mrcp_message->channel_id.session_id.hex_str,mrcp_message->channel_id.resource_name);
		return MRCP_STATUS_FAILURE;
	}
	
	apt_log(APT_PRIO_NOTICE,"On Modify Channel <%s@%s>\n",mrcp_client_session_id_get(session),mrcp_message->channel_id.resource_name);
	mrcp_client->client_context->event_handler->on_channel_modify(mrcp_client->client_context,session,mrcp_message);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_control_media_answer_process(
								mrcp_client_t *mrcp_client, 
								mrcp_session_t *session, 
								mrcp_media_control_t *local_media, 
								mrcp_media_control_t *remote_media)
{
	mrcp_status_t modified = MRCP_STATUS_FAILURE;
	mrcp_client_channel_t *control_channel;
	control_channel = mrcp_client_session_channel_get(session,local_media->base.id);
	if(!control_channel) {
		return MRCP_STATUS_FAILURE;
	}
	if(!session->id.length && remote_media->session_id.length) {
		session->id.hex_str = remote_media->session_id.hex_str;
		session->id.length = remote_media->session_id.length;
		apr_hash_set(mrcp_client->session_table,session->id.hex_str,session->id.length,session);
		apt_log(APT_PRIO_INFO,"Add Session <%s>\n",session->id.hex_str);
	}

	if(control_channel->state == CHANNEL_STATE_PENDING_CREATE) {
		if(remote_media->base.state == MRCP_MEDIA_ENABLED) {
			if(!session->connection) {
				session->connection = mrcp_client->proto_agent->agent_method_set->connect(
													mrcp_client->proto_agent,
													session->signaling_channel,
													remote_media->base.ip,
													remote_media->base.port);
			}
			mrcp_client->client_context->event_handler->on_channel_add(
													mrcp_client->client_context,
													session,
													control_channel,
													control_channel->audio_channel);
		}
		else {
			mrcp_audio_channel_t *audio_channel = control_channel->audio_channel;
			local_media->base.state = MRCP_MEDIA_DISABLED;
			if(audio_channel) {
				if(control_channel->audio_source) {
					audio_channel->method_set->source_disconnect(audio_channel,control_channel->audio_source);
				}
				if(control_channel->audio_sink) {
					audio_channel->method_set->sink_disconnect(audio_channel,control_channel->audio_sink);
				}
			}
			mrcp_client->client_context->event_handler->on_channel_remove(
													mrcp_client->client_context,
													session,
													control_channel);
		}
		control_channel->state = CHANNEL_STATE_NONE;
		modified = MRCP_STATUS_SUCCESS;
	}
	else if(control_channel->state == CHANNEL_STATE_PENDING_DESTROY) {
		if(remote_media->base.state == MRCP_MEDIA_DISABLED) {
			mrcp_client->client_context->event_handler->on_channel_remove(
													mrcp_client->client_context,
													session,
													control_channel);
		}
		else {
			/* error condition */
		}
		control_channel->state = CHANNEL_STATE_NONE;
		modified = MRCP_STATUS_SUCCESS;
	}
	return modified;
}

static mrcp_status_t mrcp_client_audio_media_answer_process(
								mrcp_client_t *mrcp_client, 
								mrcp_session_t *session, 
								mrcp_media_audio_t *local_media, 
								mrcp_media_audio_t *remote_media)
{
	mrcp_audio_channel_t *channel;
	channel = mrcp_client_session_channel_get(session,local_media->base.id);
	if(!channel) {
		return MRCP_STATUS_FAILURE;
	}

	channel->method_set->modify(channel,MRCP_AUDIO_REQUEST_RECEIVE_ANSWER);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_channel_assocciations_set(mrcp_session_t *session)
{
	size_t i;
	mrcp_descriptor_t *descriptor = &session->signaling_channel->local_descriptor;
	mrcp_client_channel_t *control_channel;
	mrcp_audio_channel_t *audio_channel;
	for(i=0; i<descriptor->media_count; i++) {
		if(!descriptor->media[i]) {
			continue;
		}
		if(descriptor->media[i]->type == MRCP_MEDIA_TYPE_CONTROL) {
			control_channel = mrcp_client_session_channel_get(session,i);
			audio_channel = control_channel->audio_channel;
			if(control_channel && audio_channel) {
				if(control_channel->audio_source) {
					audio_channel->method_set->source_connect(audio_channel,control_channel->audio_source);
				}
				if(control_channel->audio_sink) {
					audio_channel->method_set->sink_connect(audio_channel,control_channel->audio_sink);
				}
			}
		}
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_on_signaling_channel_answer(mrcp_signaling_channel_t *channel)
{
	mrcp_signaling_agent_t *agent = channel->agent;
	mrcp_client_t *mrcp_client = mrcp_client_get(&agent->module);
	size_t i;

	mrcp_status_t modified = MRCP_STATUS_FAILURE;
	mrcp_media_audio_t *local_audio_media;
	mrcp_media_audio_t *remote_audio_media;
	mrcp_media_control_t *local_control_media;
	mrcp_media_control_t *remote_control_media;


	apt_log(APT_PRIO_NOTICE,"On Session Initiate <%s>\n",mrcp_client_session_id_get(channel->session));
	if(channel->local_descriptor.media_count != channel->remote_descriptor.media_count) {
		return MRCP_STATUS_FAILURE;
	}

	for(i=0; i<channel->local_descriptor.media_count; i++) {
		if(!channel->local_descriptor.media[i] || !channel->remote_descriptor.media[i]) {
			continue;
		}
		if(channel->local_descriptor.media[i]->type != channel->remote_descriptor.media[i]->type) {
			continue;
		}
		switch(channel->local_descriptor.media[i]->type) {
			case MRCP_MEDIA_TYPE_CONTROL:
				local_control_media = (mrcp_media_control_t*)channel->local_descriptor.media[i];
				remote_control_media = (mrcp_media_control_t*)channel->remote_descriptor.media[i];
				modified = mrcp_client_control_media_answer_process(mrcp_client,channel->session,local_control_media,remote_control_media);
				break;
			case MRCP_MEDIA_TYPE_AUDIO:
				local_audio_media = (mrcp_media_audio_t*)channel->local_descriptor.media[i];
				remote_audio_media = (mrcp_media_audio_t*)channel->remote_descriptor.media[i];
				mrcp_client_audio_media_answer_process(mrcp_client,channel->session,local_audio_media,remote_audio_media);
				break;
		}
	}

	mrcp_client_channel_assocciations_set(channel->session);

	if(modified == MRCP_STATUS_FAILURE) {
		mrcp_client->client_context->event_handler->on_session_initiate(mrcp_client->client_context,channel->session);
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_on_signaling_channel_terminate(mrcp_signaling_channel_t *signaling_channel)
{
	mrcp_signaling_agent_t *agent = signaling_channel->agent;
	mrcp_client_t *mrcp_client = mrcp_client_get(&agent->module);
	mrcp_session_t *session = signaling_channel->session;
	mrcp_descriptor_t *descriptor;
	mrcp_audio_channel_t *audio_channel;
	size_t i;
	apt_log(APT_PRIO_NOTICE,"On Session Terminate <%s>\n",mrcp_client_session_id_get(session));
	if(session->id.length) {
		apr_hash_set(mrcp_client->session_table,session->id.hex_str,session->id.length,NULL);
		apt_log(APT_PRIO_NOTICE,"Remove Session <%s>\n",session->id.hex_str);
	}

	descriptor = &signaling_channel->local_descriptor;
	for(i=0; i<descriptor->media_count; i++) {
		if(!descriptor->media[i]) {
			continue;
		}
		
		if(descriptor->media[i]->type == MRCP_MEDIA_TYPE_AUDIO) {
			audio_channel = mrcp_client_session_channel_get(session,descriptor->media[i]->id);
			if(!audio_channel) {
				continue;
			}

			audio_channel->method_set->destroy(audio_channel);
		}
		
		mrcp_client_session_channel_set(session,NULL,i);
	}

	if(session->connection) {
		session->connection->method_set->disconnect(session->connection);
		session->connection = NULL;
	}

	mrcp_client->client_context->event_handler->on_session_terminate(mrcp_client->client_context,session);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_on_resource_discover(mrcp_signaling_agent_t *agent)
{
	mrcp_client_t *mrcp_client = mrcp_client_get(&agent->module);

	apt_log(APT_PRIO_NOTICE,"On Resource Discover\n");
	mrcp_client->client_context->event_handler->on_resource_discover(mrcp_client->client_context,&agent->server_capabilities);
	return MRCP_STATUS_SUCCESS;
}


static mrcp_status_t mrcp_client_msg_handler(void *object, apt_task_msg_t *task_msg)
{
	mrcp_client_t *mrcp_client = object;
	mrcp_module_t *module = task_msg->msg_handle;
	assert(mrcp_client);
	switch(module->type) {
		case MODULE_TYPE_CONTEXT:
			mrcp_client_context_msg_process(mrcp_client,task_msg->data);
			break;
		case MODULE_TYPE_SIGNALING_AGENT:
		case MODULE_TYPE_MRCP_AGENT:
		case MODULE_TYPE_MEDIA_AGENT:
			module->method_set->process(module,task_msg);
			break;
	}
	return MRCP_STATUS_SUCCESS;
}


static mrcp_status_t mrcp_client_context_setup(mrcp_client_t *mrcp_client, mrcp_client_context_t *context)
{
	context->msg_pool = apt_task_msg_pool_create_dynamic(sizeof(mrcp_client_context_msg_t),mrcp_client->pool);
	context->module.type = MODULE_TYPE_CONTEXT;
	mrcp_engine_module_register(&mrcp_client->engine,&context->module);

	mrcp_client->client_context = context;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_resources_setup(mrcp_client_t *mrcp_client)
{
	apt_log(APT_PRIO_INFO,"Setup MRCP Client Resources\n");
	mrcp_client->resource_container = mrcp_client->module_loader->resource_container_create(mrcp_client->module_loader,mrcp_client->pool);
	if(!mrcp_client->resource_container) {
		apt_log(APT_PRIO_DEBUG,"Failed to Create Client Resources\n");
		return MRCP_STATUS_FAILURE;
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_signaling_agent_setup(mrcp_client_t *mrcp_client)
{
	mrcp_signaling_agent_t *agent;
	apt_log(APT_PRIO_INFO,"Setup MRCP Signaling Agent\n");
	agent = mrcp_client->module_loader->signaling_agent_create(mrcp_client->module_loader,mrcp_client->pool);
	if(!agent) {
		apt_log(APT_PRIO_DEBUG,"Failed to Create Signaling Agent\n");
		return MRCP_STATUS_FAILURE;
	}
	agent->event_set = &signaling_agent_event_set;
	if(mrcp_client_signaling_agent_validity_check(agent) != MRCP_STATUS_SUCCESS) {
		apt_log(APT_PRIO_DEBUG,"Failed to Pass Validity Check for Signaling Agent\n");
		return MRCP_STATUS_FAILURE;
	}

	agent->module.type = MODULE_TYPE_SIGNALING_AGENT;
	agent->module.event_set = &module_event_set;
	mrcp_engine_module_register(&mrcp_client->engine,&agent->module);

	mrcp_client->signaling_agent = agent;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_proto_agent_setup(mrcp_client_t *mrcp_client)
{
	mrcp_proto_agent_t *agent;
	apt_log(APT_PRIO_INFO,"Setup MRCP Proto Agent\n");
	agent = mrcp_client->module_loader->proto_agent_create(mrcp_client->module_loader,mrcp_client->signaling_agent,mrcp_client->pool);
	if(!agent) {
		apt_log(APT_PRIO_DEBUG,"Failed to Create Proto Agent\n");
		return MRCP_STATUS_FAILURE;
	}
	agent->connection_event_set = &connection_event_set;
	if(mrcp_client_proto_agent_validity_check(agent) != MRCP_STATUS_SUCCESS) {
		apt_log(APT_PRIO_DEBUG,"Failed to Pass Validity Check for Proto Agent\n");
		return MRCP_STATUS_FAILURE;
	}

	agent->module.type = MODULE_TYPE_MRCP_AGENT;
	agent->module.event_set = &module_event_set;
	mrcp_engine_module_register(&mrcp_client->engine,&agent->module);

	agent->resource_container = mrcp_client->resource_container;

	mrcp_client->proto_agent = agent;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_media_agent_setup(mrcp_client_t *mrcp_client)
{
	mrcp_media_agent_t *agent;
	apt_log(APT_PRIO_INFO,"Setup MRCP Media Agent\n");
	agent = mrcp_client->module_loader->media_agent_create(mrcp_client->module_loader,mrcp_client->pool);
	if(!agent) {
		apt_log(APT_PRIO_DEBUG,"Failed to Create Media Agent\n");
		return MRCP_STATUS_FAILURE;
	}
	if(mrcp_media_agent_validity_check(agent) != MRCP_STATUS_SUCCESS) {
		apt_log(APT_PRIO_DEBUG,"Failed to Pass Validity Check for Media Agent\n");
		return MRCP_STATUS_FAILURE;
	}

	agent->module.type = MODULE_TYPE_MEDIA_AGENT;
	agent->module.event_set = &module_event_set;
	mrcp_engine_module_register(&mrcp_client->engine,&agent->module);

	mrcp_client->media_agent = agent;
	return MRCP_STATUS_SUCCESS;
}

static void mrcp_client_on_start_in_progress(void *data)
{
	mrcp_client_t *mrcp_client = data;
	assert(mrcp_client);
	apt_log(APT_PRIO_INFO,"Initialize MRCP Client\n");

	mrcp_client->session_table = apr_hash_make(mrcp_client->pool);

	if(mrcp_client_resources_setup(mrcp_client) != MRCP_STATUS_SUCCESS) {
		apt_consumer_task_running_set(mrcp_client->engine.consumer_task,FALSE);
		apt_log(APT_PRIO_WARNING,"Failed to Setup Client Resources\n");
		return;
	}
	if(mrcp_client_signaling_agent_setup(mrcp_client) != MRCP_STATUS_SUCCESS) {
		apt_consumer_task_running_set(mrcp_client->engine.consumer_task,FALSE);
		apt_log(APT_PRIO_WARNING,"Failed to Setup Signaling Agent\n");
		return;
	}
	if(mrcp_client_proto_agent_setup(mrcp_client) != MRCP_STATUS_SUCCESS) {
		apt_consumer_task_running_set(mrcp_client->engine.consumer_task,FALSE);
		apt_log(APT_PRIO_WARNING,"Failed to Setup MRCP Proto Agent\n");
		return;
	}
	if(mrcp_client_media_agent_setup(mrcp_client) != MRCP_STATUS_SUCCESS) {
		apt_consumer_task_running_set(mrcp_client->engine.consumer_task,FALSE);
		apt_log(APT_PRIO_WARNING,"Failed to Setup Media Agent\n");
		return;
	}

	mrcp_engine_open(&mrcp_client->engine);
	
	apr_thread_mutex_lock(mrcp_client->sync_object_mutex);
	apr_thread_cond_signal(mrcp_client->sync_object);
	apr_thread_mutex_unlock(mrcp_client->sync_object_mutex);
}

static void mrcp_client_on_terminate_in_progress(void *data)
{
	mrcp_client_t *mrcp_client = data;
	assert(mrcp_client);

	apt_consumer_task_running_set(mrcp_client->engine.consumer_task,TRUE);
	
	if(mrcp_engine_close(&mrcp_client->engine) == MODULE_STATE_CLOSED) {
		apt_consumer_task_running_set(mrcp_client->engine.consumer_task,FALSE);
	}
}

static void mrcp_client_on_terminate_complete(void *data)
{
	mrcp_client_t *mrcp_client = data;
	assert(mrcp_client);
	apt_log(APT_PRIO_INFO,"Destroy MRCP Client\n");

	if(mrcp_client->resource_container) {
		mrcp_resource_container_destroy(mrcp_client->resource_container);
		mrcp_client->resource_container = NULL;
	}

	mrcp_engine_destroy(&mrcp_client->engine);
}

static mrcp_status_t mrcp_client_on_module_open(mrcp_module_t *module)
{
	module->state = MODULE_STATE_OPENED;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t mrcp_client_on_module_close(mrcp_module_t *module)
{
	module->state = MODULE_STATE_CLOSED;
	if(mrcp_engine_is_closed(module->engine) == MRCP_STATUS_SUCCESS) {
		apt_consumer_task_running_set(module->engine->consumer_task,FALSE);
	}
	return MRCP_STATUS_SUCCESS;
}



/* starts message processing loop */
mrcp_client_t* mrcp_client_start(mrcp_client_module_loader_t *module_loader, mrcp_client_context_t *client_context)
{
	mrcp_client_t* mrcp_client;
	apt_task_t *task;
	apr_pool_t *pool;
	
	if(!module_loader || !module_loader->signaling_agent_create || !module_loader->proto_agent_create /*|| !module_loader->resources_create*/) {
		apt_log(APT_PRIO_DEBUG,"Invalid Module Loader\n");
		return NULL;
	}

	if(apr_pool_create(&pool,NULL) != APR_SUCCESS) {
		return NULL;
	}

	apt_log(APT_PRIO_NOTICE,"Starting MRCP Client\n");
	mrcp_client = apr_palloc(pool,sizeof(mrcp_client_t));
	mrcp_client->pool = pool;
	mrcp_client->resource_container = NULL;
	mrcp_client->signaling_agent = NULL;
	mrcp_client->proto_agent = NULL;
	mrcp_client->session_table = NULL;
	mrcp_client->module_loader = module_loader;

	mrcp_engine_init(&mrcp_client->engine);
	mrcp_client->engine.consumer_task = apt_consumer_task_create(mrcp_client, mrcp_client_msg_handler,pool);
	if(!mrcp_client->engine.consumer_task) {
		apt_log(APT_PRIO_DEBUG,"Failed to Create Client Consumer Task\n");
		apr_pool_destroy(pool);
		return NULL;
	}

	if(mrcp_client_context_setup(mrcp_client,client_context) != MRCP_STATUS_SUCCESS) {
		apt_log(APT_PRIO_WARNING,"Failed to Setup Context\n");
		apr_pool_destroy(pool);
		return NULL;
	}

	task = apt_consumer_task_get(mrcp_client->engine.consumer_task);
	apt_task_event_handler_set(task,TASK_STATE_START_IN_PROGRESS,mrcp_client,mrcp_client_on_start_in_progress);
	apt_task_event_handler_set(task,TASK_STATE_TERMINATE_IN_PROGRESS,mrcp_client,mrcp_client_on_terminate_in_progress);
	apt_task_event_handler_set(task,TASK_STATE_TERMINATE_COMPLETED,mrcp_client,mrcp_client_on_terminate_complete);

	apr_thread_mutex_create(&mrcp_client->sync_object_mutex,APR_THREAD_MUTEX_UNNESTED,mrcp_client->pool);
	apr_thread_cond_create(&mrcp_client->sync_object,mrcp_client->pool);

	apr_thread_mutex_lock(mrcp_client->sync_object_mutex);
	apt_consumer_task_start(mrcp_client->engine.consumer_task);
	apr_thread_cond_wait(mrcp_client->sync_object,mrcp_client->sync_object_mutex);
	apr_thread_mutex_unlock(mrcp_client->sync_object_mutex);

	apr_thread_cond_destroy(mrcp_client->sync_object);
	apr_thread_mutex_destroy(mrcp_client->sync_object_mutex);

	apt_log(APT_PRIO_DEBUG,"Ready\n");
	return mrcp_client;
}

/* terminates message processing loop */
mrcp_status_t mrcp_client_shutdown(mrcp_client_t* mrcp_client)
{
	assert(mrcp_client);
	apt_log(APT_PRIO_NOTICE,"Shutting Down MRCP Client\n");
	if(apt_consumer_task_terminate(mrcp_client->engine.consumer_task) != TRUE) {
		apt_log(APT_PRIO_DEBUG,"Failed to Terminate Client Consumer Task\n");
		return MRCP_STATUS_FAILURE;
	}
	apt_consumer_task_destroy(mrcp_client->engine.consumer_task);
	mrcp_client->engine.consumer_task = NULL;

	if(mrcp_client->client_context) {
		if(mrcp_client->client_context->msg_pool) {
			apt_task_msg_pool_destroy(mrcp_client->client_context->msg_pool);
			mrcp_client->client_context->msg_pool = NULL;
		}
		mrcp_client->client_context = NULL;
	}

	apr_pool_destroy(mrcp_client->pool);
	return MRCP_STATUS_SUCCESS;
}
