/*
 * 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 "apt_unique_id.h"
#include "mrcp_server_session.h"
#include "mrcp_signaling_channel.h"
#include "mrcp_connection.h"
#include "mrcp_audio_channel.h"

#define MRCP_SESSION_ID_HEX_STRING_LENGTH 16

static mrcp_server_channel_state_t mrcp_server_channels_destroy(mrcp_session_t *session);

/* creates mrcp server session */
mrcp_session_t* mrcp_server_session_create(mrcp_session_id *session_id)
{
	size_t i;
	apr_pool_t *session_pool;
	mrcp_session_t* session;

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

	session	= apr_palloc(session_pool,sizeof(mrcp_session_t));
	session->pool = session_pool;
	session->signaling_channel = NULL;
	session->terminating = MRCP_STATUS_FAILURE;

	for(i=0; i<MRCP_MAX_MEDIA_COUNT; i++) {
		session->channels[i] = NULL;
	}
	
	if(session_id) {
		char *session_id_str = apr_pstrdup(session_pool,session_id->hex_str);
		session->id.hex_str = session_id_str;
		session->id.length = session_id->length;
	}
	else {
		apt_unique_id_generate(&session->id,MRCP_SESSION_ID_HEX_STRING_LENGTH,session_pool);
	}
	apt_log(APT_PRIO_NOTICE,"Create Session <%s>\n",session->id.hex_str);
	return session;
}

/* destroys mrcp server session */
mrcp_status_t mrcp_server_session_destroy(mrcp_session_t *session)
{
	session->terminating = MRCP_STATUS_SUCCESS;
	if(mrcp_server_channels_destroy(session) != MRCP_CHANNEL_STATE_CLOSED) {
		apt_log(APT_PRIO_NOTICE,"Destroy Session <%s> [inprogress] \n",session->id.hex_str);
		return MRCP_STATUS_SUCCESS;
	}

	apt_log(APT_PRIO_NOTICE,"Destroy Session <%s>\n",session->id.hex_str);

	if(session->signaling_channel) {
		session->signaling_channel->method_set->destroy(session->signaling_channel);
		session->signaling_channel = NULL;
	}
	
	if(session->pool) {
		apr_pool_destroy(session->pool);
		session->pool = NULL;
	}
	return MRCP_STATUS_SUCCESS;
}

/* creates mrcp channel */
mrcp_server_channel_t* mrcp_server_channel_create(mrcp_session_t *session, mrcp_server_resource_t *resource)
{
	mrcp_server_channel_t *channel;
	apt_log(APT_PRIO_NOTICE,"Create Control Channel <%s@%s>\n",session->id.hex_str,resource->base.name);
	channel = apr_palloc(session->pool,sizeof(mrcp_server_channel_t));
	channel->state = MRCP_CHANNEL_STATE_NONE;
	channel->session = session;
	channel->connection = NULL;
	channel->resource = resource;
	channel->audio_source = NULL;
	channel->audio_sink = NULL;
	mrcp_message_header_init(&channel->properties);
	channel->properties.generic_header.accessor = resource->base.generic_header_accessor;
	channel->properties.resource_header.accessor = &resource->base.header_accessor;
	
	if(resource->channel_open) {
		resource->channel_open(channel,session->pool);
	}

	return channel;
}

/* destroys mrcp channel */
mrcp_server_channel_state_t mrcp_server_channel_destroy(mrcp_server_channel_t *channel)
{
	if(!channel || !channel->resource) {
		return MRCP_CHANNEL_STATE_NONE;
	}

//	apt_log(APT_PRIO_NOTICE,"Destroy Control Channel <%s@%s>\n",session->id.hex_str,channel->resource->base.name);
	if(channel->resource->channel_close) {
		channel->resource->channel_close(channel);
	}
	return channel->state;
}

/* destroys all mrcp channels */
static mrcp_server_channel_state_t mrcp_server_channels_destroy(mrcp_session_t *session)
{
	mrcp_server_channel_state_t state = MRCP_CHANNEL_STATE_CLOSED;
	mrcp_server_channel_t *channel;
	mrcp_audio_channel_t *audio_channel;
	mrcp_descriptor_t *descriptor = &session->signaling_channel->local_descriptor;
	size_t i;
	for(i=0; i<descriptor->media_count; i++) {
		if(!descriptor->media[i]) {
			continue;
		}
		
		switch(descriptor->media[i]->type) {
			case MRCP_MEDIA_TYPE_CONTROL:
				channel = mrcp_server_session_channel_get(session,descriptor->media[i]->id);
				if(!channel) {
					continue;
				}

				apt_log(APT_PRIO_NOTICE,"Remove Channel <%s@%s>\n",session->id.hex_str,channel->resource->base.name);
				if(channel->resource && channel->resource->channel_close) {
					channel->resource->channel_close(channel);
				}
				
				if(channel->state == MRCP_CHANNEL_STATE_CLOSE_REQUESTED) {
					state = MRCP_CHANNEL_STATE_CLOSE_REQUESTED;
				}
				else {
					mrcp_server_session_channel_set(session,NULL,descriptor->media[i]->id);
				}
	
				if(channel->connection) {
					channel->connection->method_set->disconnect(channel->connection);
					channel->connection = NULL;
				}
				break;
			case MRCP_MEDIA_TYPE_AUDIO:
				audio_channel = mrcp_server_session_channel_get(session,descriptor->media[i]->id);
				if(!audio_channel) {
					continue;
				}

				audio_channel->method_set->destroy(audio_channel);
				mrcp_server_session_channel_set(session,NULL,descriptor->media[i]->id);
				break;
		}
	}
	return state;
}

/** Set Channel to MRCP Server Session */
mrcp_status_t mrcp_server_session_channel_set(mrcp_session_t *session, void *channel, size_t index)
{
	if(index >= MRCP_MAX_MEDIA_COUNT) {
		return MRCP_STATUS_FAILURE;
	}
	session->channels[index] = channel;
	return MRCP_STATUS_SUCCESS;
}

/** Get Channel from MRCP Server Session */
void* mrcp_server_session_channel_get(mrcp_session_t *session, size_t index)
{
	if(index >= MRCP_MAX_MEDIA_COUNT) {
		return NULL;
	}
	return session->channels[index];
}


mrcp_status_t mrcp_server_channel_event_handler(mrcp_server_channel_t *channel, mrcp_message_t *event_message)
{
	mrcp_server_resource_t *resource;
	mrcp_server_event_handler_t event_handler;
	mrcp_session_t *session = channel->session;

	if(!session) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Event: no session\n");
		return MRCP_STATUS_FAILURE;
	}
	
	resource = channel->resource;
	if(!resource) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Event: not registered resource\n");
		return MRCP_STATUS_FAILURE;
	}

	if(event_message->start_line.method_id >= resource->base.event_count) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Event: invalid event_id=%lu\n",event_message->start_line.method_id);
		return MRCP_STATUS_FAILURE;
	}
	event_handler = resource->event_array[event_message->start_line.method_id];
	if(event_handler(channel,event_message) != MRCP_STATUS_SUCCESS) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Event\n");
		return MRCP_STATUS_FAILURE;
	}
	
	event_message->start_line.message_type = MRCP_MESSAGE_TYPE_EVENT;
	event_message->channel_id.resource_id = resource->base.id;
	event_message->channel_id.session_id = session->id;
	channel->connection->method_set->send(channel->connection,event_message);
	return MRCP_STATUS_SUCCESS;
}

mrcp_status_t mrcp_server_channel_response_handler(mrcp_server_channel_t *channel, mrcp_message_t *response_message)
{
	mrcp_server_resource_t *resource;
	mrcp_session_t *session = channel->session;

	if(!session) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Response: no session\n");
		return MRCP_STATUS_FAILURE;
	}
	
	resource = channel->resource;
	if(!resource) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Response: not registered resource\n");
		return MRCP_STATUS_FAILURE;
	}

	if(resource->response_handler) {
		if(resource->response_handler(channel,response_message) != MRCP_STATUS_SUCCESS) {
			apt_log(APT_PRIO_WARNING,"Failed to Process Response: not registered resource\n");
			return MRCP_STATUS_FAILURE;
		}
	}

	if(session->terminating == MRCP_STATUS_SUCCESS) {
		/*pending for removal session*/
		mrcp_server_session_destroy(session);
	}
	return MRCP_STATUS_SUCCESS;
}

mrcp_status_t mrcp_server_message_process(mrcp_session_t *session, mrcp_message_t *mrcp_message, mrcp_message_t *response_message)
{
	mrcp_server_channel_t *channel;
	mrcp_server_method_handler_t method_handler;

	mrcp_media_control_t *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);
		response_message->start_line.status_code = MRCP_STATUS_CODE_NOT_FOUND;
		return MRCP_STATUS_FAILURE;
	}

	channel = mrcp_server_session_channel_get(session,control_media->base.id);
	if(!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);
		response_message->start_line.status_code = MRCP_STATUS_CODE_NOT_FOUND;
		return MRCP_STATUS_FAILURE;
	}

	if(!channel->resource) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Message: no such resource <%s@%s>\n",mrcp_message->channel_id.session_id.hex_str,mrcp_message->channel_id.resource_name);
		response_message->start_line.status_code = MRCP_STATUS_CODE_NOT_FOUND;
		return MRCP_STATUS_FAILURE;
	}

	if(mrcp_message->start_line.method_id >= channel->resource->base.method_count) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Message: no such method <%s>\n",mrcp_message->start_line.method_name);
		response_message->start_line.status_code = MRCP_STATUS_CODE_METHOD_NOT_ALLOWED;
		return MRCP_STATUS_FAILURE;
	}
	method_handler = channel->resource->method_array[mrcp_message->start_line.method_id];
	if(!method_handler) {
		apt_log(APT_PRIO_WARNING,"Failed to Process Message: no such method handler <%s>\n",mrcp_message->start_line.method_name);
		response_message->start_line.status_code = MRCP_STATUS_CODE_METHOD_NOT_ALLOWED;
		return MRCP_STATUS_FAILURE;
	}

	return method_handler(channel,mrcp_message,response_message,channel->resource->plugin->method_plugin_array[mrcp_message->start_line.method_id]);
}

mrcp_status_t mrcp_server_channel_assocciations_set(mrcp_session_t *session)
{
	size_t i;
	mrcp_descriptor_t *descriptor = &session->signaling_channel->local_descriptor;
	mrcp_server_channel_t *control_channel;
	mrcp_audio_channel_t *audio_channel;
	mrcp_media_control_t *control_media;
	mrcp_media_audio_t *audio_media;
	for(i=0; i<descriptor->media_count; i++) {
		if(!descriptor->media[i]) {
			continue;
		}
		if(descriptor->media[i]->type == MRCP_MEDIA_TYPE_CONTROL) {
			control_media = (mrcp_media_control_t*)descriptor->media[i];
			audio_media = mrcp_audio_media_get(descriptor,control_media->cmid);
			if(audio_media) {
				audio_channel = mrcp_server_session_channel_get(session,audio_media->base.id);
				control_channel = mrcp_server_session_channel_get(session,control_media->base.id);
				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;
}


void mrcp_server_channel_handle_set(mrcp_server_channel_t *channel, void *handle)
{
	channel->handle = handle;
}
void* mrcp_server_channel_handle_get(mrcp_server_channel_t *channel)
{
	return channel->handle;
}

void mrcp_server_channel_audio_source_set(mrcp_server_channel_t *channel, audio_source_t* audio_source)
{
	channel->audio_source = audio_source;
}

audio_source_t* mrcp_server_channel_audio_source_get(mrcp_server_channel_t *channel)
{
	return channel->audio_source;
}

void mrcp_server_channel_audio_sink_set(mrcp_server_channel_t *channel, audio_sink_t* audio_sink)
{
	channel->audio_sink = audio_sink;
}

audio_sink_t* mrcp_server_channel_audio_sink_get(mrcp_server_channel_t *channel)
{
	return channel->audio_sink;
}
