/*
 * 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_thread_proc.h>
#include "openmrcp_client.h"
#include "mrcp_client_context.h"
#include "mrcp_parser.h"
#include "mrcp_generic_header.h"
#include "mrcp_resource_set.h"
#include "mrcp_resource_manager.h"
#include "mrcp_resource.h"

#define MAX_CONSOLE_SESSION_COUNT 10
#define MAX_CHANNEL_COUNT 5

typedef struct console_session_t console_session_t;
struct console_session_t {
	size_t                 slot;
	mrcp_session_t        *client_session;

	mrcp_client_channel_t *channels[MAX_CHANNEL_COUNT];

	FILE                  *audio_in;
	FILE                  *audio_out;
	apr_thread_mutex_t    *audio_mutex;

	apr_pool_t            *pool;
};

typedef struct client_console_t client_console_t;
struct client_console_t {
	console_session_t     *session_container[MAX_CONSOLE_SESSION_COUNT];
	mrcp_client_context_t *client_context;

	mrcp_resource_container_t *resource_container;
};

static apr_status_t console_session_write_frame(audio_sink_t *sink, media_frame_t *frame);
static apr_status_t console_session_read_frame(audio_source_t *source, media_frame_t *frame);

static const audio_sink_method_set_t audio_sink_method_set = {
	NULL,
	NULL,
	NULL,
	console_session_write_frame
};

static const audio_source_method_set_t audio_source_method_set = {
	NULL,
	NULL,
	NULL,
	console_session_read_frame
};


static apr_thread_mutex_t *data_guard = NULL;


static console_session_t* console_session_create(client_console_t *console, const char *arg)
{
	console_session_t *console_session;
	size_t slot = 0;
	apr_pool_t *session_pool;
	char filename[128];
	if(!arg) {
		printf("CONSOLE: missing slot number\n");
		return NULL;
	}
	slot = atoi(arg);
	if(slot >= MAX_CONSOLE_SESSION_COUNT) {
		printf("CONSOLE[%d]: invalid slot number\n",slot);
		return NULL;
	}

	if(console->session_container[slot]) {
		printf("CONSOLE[%d]: cannot create (already exist)\n",slot);
		return NULL;
	}

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

	console_session	= apr_palloc(session_pool,sizeof(console_session_t));
	console_session->pool = session_pool;

	console_session->client_session = NULL;
	console_session->slot = slot;
	console->session_container[slot] = console_session;

	console_session->audio_in = fopen("speech_to_recog.pcm","rb");
	sprintf(filename,"synth_result_%d.pcm",slot);
	console_session->audio_out = fopen(filename,"wb");
	apr_thread_mutex_create(&console_session->audio_mutex,APR_THREAD_MUTEX_UNNESTED,console_session->pool);

	return console_session;
}

static mrcp_status_t console_session_destroy(client_console_t *console, console_session_t *console_session)
{
	if(!console_session || console_session->slot >= MAX_CONSOLE_SESSION_COUNT) {
		return MRCP_STATUS_FAILURE;
	}
	
	if(console_session->audio_out) {
		fclose(console_session->audio_out);
		console_session->audio_out = NULL;
	}
	if(console_session->audio_in) {
		fclose(console_session->audio_in);
		console_session->audio_in = NULL;
	}

	console->session_container[console_session->slot] = NULL;
	if(console_session->pool) {
		apr_thread_mutex_destroy(console_session->audio_mutex);

		apr_pool_destroy(console_session->pool);
		console_session->pool = NULL;
	}
	return MRCP_STATUS_SUCCESS;
}

static console_session_t* console_session_get_by_str(client_console_t *console, const char *arg)
{
	size_t slot = 0;
	if(!arg) {
		printf("CONSOLE: missing slot number\n");
		return NULL;
	}
	slot = atoi(arg);
	if(slot >= MAX_CONSOLE_SESSION_COUNT) {
		printf("CONSOLE[%d]: invalid slot number\n",slot);
		return NULL;
	}
	return console->session_container[slot];
}

static mrcp_status_t console_session_channel_add(console_session_t *console_session, mrcp_client_channel_t *channel, mrcp_resource_id resource_id)
{
	if(resource_id >= MAX_CHANNEL_COUNT) {
		return MRCP_STATUS_FAILURE;
	}
	console_session->channels[resource_id] = channel;
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t console_session_channel_remove(console_session_t *console_session, mrcp_client_channel_t *channel)
{
	size_t i;
	for(i=0; i<MAX_CHANNEL_COUNT; i++) {
		if(console_session->channels[i] == channel) {
			console_session->channels[i] = NULL;
		}
	}
	return MRCP_STATUS_SUCCESS;
}

static mrcp_client_channel_t* console_session_channel_get(console_session_t *console_session, mrcp_resource_id resource_id)
{
	if(resource_id >= MAX_CHANNEL_COUNT) {
		return NULL;
	}
	return console_session->channels[resource_id];
}

static apr_status_t console_session_write_frame(audio_sink_t *sink, media_frame_t *frame)
{
	console_session_t *console_session = sink->object;

	apr_thread_mutex_lock(console_session->audio_mutex);
	if(console_session->audio_out) {
		fwrite(frame->codec_frame.buffer,1,frame->codec_frame.size,console_session->audio_out);
	}
	apr_thread_mutex_unlock(console_session->audio_mutex);
	return APR_SUCCESS;
}

static apr_status_t console_session_read_frame(audio_source_t *source, media_frame_t *frame)
{
	console_session_t *console_session = source->object;

	frame->type = MEDIA_FRAME_TYPE_NONE;
	apr_thread_mutex_lock(console_session->audio_mutex);
	if(console_session->audio_in) {
		if(fread(frame->codec_frame.buffer,1,frame->codec_frame.size,console_session->audio_in) == frame->codec_frame.size) {
			frame->type = MEDIA_FRAME_TYPE_AUDIO;
		}
	}
	apr_thread_mutex_unlock(console_session->audio_mutex);
	return APR_SUCCESS;
}

static mrcp_status_t console_on_session_initiate(mrcp_client_context_t *context, mrcp_session_t *session)
{
	console_session_t *console_session;

	apr_thread_mutex_lock(data_guard);
	console_session = mrcp_client_context_session_object_get(session);
	printf("CONSOLE[%d]: session initiated\n",console_session->slot);
	apr_thread_mutex_unlock(data_guard);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t console_on_session_terminate(mrcp_client_context_t *context, mrcp_session_t *session)
{
	client_console_t *console;
	console_session_t *console_session;

	apr_thread_mutex_lock(data_guard);
	console = mrcp_client_context_object_get(context);
	console_session = mrcp_client_context_session_object_get(session);
	printf("CONSOLE[%d]: session terminated\n",console_session->slot);
	mrcp_client_context_session_destroy(context,session);
	console_session_destroy(console,console_session);
	apr_thread_mutex_unlock(data_guard);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t console_on_channel_add(mrcp_client_context_t *context, mrcp_session_t *session, mrcp_client_channel_t *control_channel, mrcp_audio_channel_t *audio_channel)
{
	console_session_t *console_session;

	apr_thread_mutex_lock(data_guard);
	console_session = mrcp_client_context_session_object_get(session);
	printf("CONSOLE[%d]: channel[%d] added\n",console_session->slot,mrcp_client_context_resource_id_get(control_channel));
	apr_thread_mutex_unlock(data_guard);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t console_on_channel_remove(mrcp_client_context_t *context, mrcp_session_t *session, mrcp_client_channel_t *control_channel)
{
	console_session_t *console_session;

	apr_thread_mutex_lock(data_guard);
	console_session = mrcp_client_context_session_object_get(session);
	printf("CONSOLE[%d]: channel[%d] removed\n",console_session->slot,mrcp_client_context_resource_id_get(control_channel));
	console_session_channel_remove(console_session,control_channel);
	mrcp_client_context_channel_destroy(context,session,control_channel);
	apr_thread_mutex_unlock(data_guard);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t console_on_channel_modify(mrcp_client_context_t *context, mrcp_session_t *session, mrcp_message_t *mrcp_message)
{
	console_session_t *console_session;

	apr_thread_mutex_lock(data_guard);
	console_session = mrcp_client_context_session_object_get(session);
	if(mrcp_message) {
		printf("CONSOLE[%d]: channel[%d] modified\n",console_session->slot,mrcp_message->channel_id.resource_id);
	}
	else {
		printf("CONSOLE[%d]: failed to modify channel\n",console_session->slot);
	}
	apr_thread_mutex_unlock(data_guard);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t console_on_resource_discover(mrcp_client_context_t *context, mrcp_descriptor_t *capabilities)
{
	apr_thread_mutex_lock(data_guard);
	if(capabilities) {
		printf("CONSOLE: resource discovered\n");
	}
	else {
		printf("CONSOLE: failed to discover resource\n");
	}
	apr_thread_mutex_unlock(data_guard);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t process_mrcp_message(client_console_t *console, console_session_t *console_session, const char *path)
{
	char buffer[2048];
	size_t size;
	apt_text_stream_t text_stream;
	mrcp_message_t *mrcp_message;
	FILE *f;

	if(!console->resource_container) {
		printf("no resource container\n");
		return MRCP_STATUS_FAILURE;
	}

	if(!path) {
		printf("cannot open null file\n");
		return MRCP_STATUS_FAILURE;
	}
	f = fopen(path, "rb");
	if(f == NULL) {
		printf("cannot open file \"%s\"\n", path);
		return MRCP_STATUS_FAILURE;
	}

	size = fread(buffer, 1, sizeof(buffer)-1, f);
	buffer[size] = '\0';

	text_stream.buffer = apr_palloc(console_session->pool,size+1);
	strcpy(text_stream.buffer,buffer);
	text_stream.size = size;
	text_stream.pos = text_stream.buffer;

	mrcp_message = mrcp_message_create(console_session->pool);
	if(mrcp_message_parse(console->resource_container,mrcp_message,&text_stream) != MRCP_STATUS_SUCCESS) {
		return MRCP_STATUS_FAILURE;
	}

	mrcp_message->start_line.length = 0;
	if(mrcp_message->header.generic_header.data) {
		mrcp_generic_header_t *generic_header = mrcp_message->header.generic_header.data;
		generic_header->content_length = 0;
	}
	mrcp_client_context_channel_modify(console->client_context,console_session->client_session,mrcp_message);
	return MRCP_STATUS_FAILURE;
}

static mrcp_client_channel_t* console_channel_create(client_console_t *console, console_session_t *console_session, mrcp_resource_id resource_id)
{
	mrcp_client_channel_t *channel = NULL;
	switch(resource_id) {
		case MRCP_RESOURCE_SYNTHESIZER:
		{
			audio_sink_t *sink = apr_palloc(console_session->pool,sizeof(audio_sink_t));
			sink->method_set = &audio_sink_method_set;
			sink->object = console_session;
			
			channel = mrcp_client_synthesizer_channel_create(console->client_context,console_session->client_session,sink);
			break;
		}
		case MRCP_RESOURCE_RECOGNIZER:
		{
			audio_source_t *source = apr_palloc(console_session->pool,sizeof(audio_source_t));
			source->method_set = &audio_source_method_set;
			source->object = console_session;
			
			channel = mrcp_client_recognizer_channel_create(console->client_context,console_session->client_session,source);
			break;
		}
	}
	return channel;
}

static char process_cmdline(client_console_t *console, char *cmdline)
{
	char* name = cmdline;
	char* arg;
	if((arg = strchr(cmdline, ' ')) != 0) {
		*arg++ = '\0';
	}

	if(strcmp(name,"create") == 0) {
		console_session_t *console_session = console_session_create(console,arg);
		if(console_session) {
			printf("CONSOLE[%d]: session create\n",console_session->slot);
			console_session->client_session = mrcp_client_context_session_create(console->client_context,console_session);
		}
	}
	else if(strcmp(name,"destroy") == 0) {
		console_session_t *console_session = console_session_get_by_str(console,arg);
		if(console_session) {
			printf("CONSOLE[%d]: session destroy\n",console_session->slot);
			mrcp_client_context_session_terminate(console->client_context,console_session->client_session);
		}
	}
	else if(strcmp(name,"init") == 0) {
		console_session_t *console_session = console_session_get_by_str(console,arg);
		if(console_session) {
			printf("CONSOLE[%d]: session init\n",console_session->slot);
			mrcp_client_context_session_initiate(console->client_context,console_session->client_session);
		}
	}
	else if(strcmp(name,"add") == 0) {
		console_session_t *console_session;
		char *resource_str = NULL;
		if((resource_str = strchr(arg, ' ')) != 0) {
			*resource_str++ = '\0';
		}
		console_session = console_session_get_by_str(console,arg);
		if(console_session) {
			mrcp_client_channel_t *channel;
			mrcp_resource_id resource_id = 0;
			if(resource_str) {
				resource_id = atoi(resource_str);
			}
			printf("CONSOLE[%d]: channel[%d] create\n",console_session->slot,resource_id);
			channel = console_channel_create(console,console_session,resource_id);
			if(channel) {
				mrcp_client_context_channel_add(console->client_context,console_session->client_session,channel,NULL);
				console_session_channel_add(console_session,channel,resource_id);
			}
		}
	}
	else if(strcmp(name,"remove") == 0) {
		console_session_t *console_session;
		char *resource_str = NULL;
		if((resource_str = strchr(arg, ' ')) != 0) {
			*resource_str++ = '\0';
		}
		console_session = console_session_get_by_str(console,arg);
		if(console_session) {
			mrcp_resource_id resource_id = 0;
			mrcp_client_channel_t *channel = NULL;
			if(resource_str) {
				resource_id = atoi(resource_str);
			}
			channel = console_session_channel_get(console_session,resource_id);
			if(channel) {
				printf("CONSOLE[%d]: channel[%d] destroy\n",console_session->slot,resource_id);
				mrcp_client_context_channel_remove(console->client_context,console_session->client_session,channel);
			}
		}
	}
	else if(strcmp(name,"msg") == 0) {
		console_session_t *console_session;
		char *path = NULL;
		if((path = strchr(arg, ' ')) != 0) {
			*path++ = '\0';
		}
		console_session = console_session_get_by_str(console,arg);
		if(console_session) {
			process_mrcp_message(console,console_session,path);
		}
	}
	else if(strcmp(name,"discover") == 0) {
		printf("CONSOLE: resource discover\n");
		mrcp_client_context_resource_discover(console->client_context);
	}
	else if(strcmp(name,"show") == 0) {
	}
	else if(strcmp(name,"loglevel") == 0) {
		if(arg) {
			apt_log_priority_set(atol(arg));
		}
	}
	else if(strcmp(name,"exit") == 0 || strcmp(name,"quit") == 0) {
		return 0;
	}
	else if(strcmp(name,"help") == 0) {
		printf("usage:\n");
		printf("- create [slot] (create session, slot is one of 0,1...9)\n");
		printf("- destroy [slot] (destroy session, slot is one of 0,1...9)\n");
		printf("- init [slot] (initiate empty session, slot is one of 0,1...9)\n");
		printf("- add [slot] [resource_id] (create channel, resource_id is one of 0-speechsynth,1-speechrecog,...)\n");
		printf("- remove [slot] [resource_id] (destroy channel, resource_id is one of 0-speechsynth,1-speechrecog,...)\n");
		printf("- msg [slot] [path] (load and send mrcp message, path to load message from)\n");
		printf("- loglevel [level] (set loglevel, one of 0,1...7)\n");
		printf("- quit, exit\n");
	}
	else {
		printf("unknown command: %s (input help for usage)\n",name);
	}
	return 1;
}


void console_cmdline_run(openmrcp_client_options_t *options, apr_pool_t *pool)
{
	size_t i;
	char running = 1;
	char cmdline[1024];
	mrcp_client_t *mrcp_client;

	mrcp_client_event_handler_t client_event_handler;
	client_console_t console;

	apr_thread_mutex_create(&data_guard, APR_THREAD_MUTEX_DEFAULT, pool);

	console.client_context = NULL;
	for(i = 0; i < MAX_CONSOLE_SESSION_COUNT; i++) {
		console.session_container[i] = NULL;
	}

	client_event_handler.on_session_initiate = console_on_session_initiate;
	client_event_handler.on_session_terminate = console_on_session_terminate;
	client_event_handler.on_channel_add = console_on_channel_add;
	client_event_handler.on_channel_remove = console_on_channel_remove;
	client_event_handler.on_channel_modify = console_on_channel_modify;
	client_event_handler.on_resource_discover = console_on_resource_discover;

	/* create client context, which must be passed to client engine */
	console.client_context = mrcp_client_context_create(&console,&client_event_handler);
	if(!console.client_context) {
		return;
	}

	/* start client engine */
	mrcp_client = openmrcp_client_start(options,console.client_context);
	if(!mrcp_client) {
		mrcp_client_context_destroy(console.client_context);
		return;
	}

	/* create resource container, which is only needed to parse template/test MRCP messages */
	console.resource_container = mrcp_parser_resource_container_create(MRCP_VERSION_2,NULL,pool);

	/* run console command line */
	do {
		printf(">");
		memset(&cmdline, 0, sizeof(cmdline));
		for(i = 0; i < sizeof(cmdline); i++) {
			cmdline[i] = (char) getchar();
			if(cmdline[i] == '\n') {
				cmdline[i] = '\0';
				break;
			}
		}
		if(*cmdline) {
			apr_thread_mutex_lock(data_guard);
			running = process_cmdline(&console,cmdline);
			apr_thread_mutex_unlock(data_guard);
		}
	}
	while(running != 0);

	if(console.resource_container) {
		mrcp_resource_container_destroy(console.resource_container);
	}
	
	/* shutdown client engine */
	openmrcp_client_shutdown(mrcp_client);

	/* destroy client context */
	mrcp_client_context_destroy(console.client_context);

	apr_thread_mutex_destroy(data_guard);
}
