/*
 * 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):
 * Traun Leyden <tleyden@branchcut.com>
 * Arsen Chaloyan <achaloyan@yahoo.com>
 *
 * Contributor(s):
 *
 *
 * Description:
 * 
 * Demonstration client that connects to an MRCP server and acts as a
 * simulated client.  It connects, tells the MRCP server to start recognition,
 * streams the contents of the recognize_demo.pcm file, then tells the MRCP server
 * to stop recognition and then prints out the result.
 */

#include <apr_thread_proc.h>
#include <apr_thread_cond.h>
#include "openmrcp_client.h"
#include "mrcp_client_context.h"
#include "mrcp_recognizer.h"
#include "mrcp_generic_header.h"

#define DEMO_WAIT_TIMEOUT 5000

typedef enum {
	DEMO_EVENT_NONE,
	DEMO_EVENT_SESSION_INITIATE,
	DEMO_EVENT_SESSION_TERMINATE,
	DEMO_EVENT_CHANNEL_CREATE,
	DEMO_EVENT_CHANNEL_DESTROY,
	DEMO_EVENT_CHANNEL_MODIFY
} demo_event_t;

typedef struct demo_session_t demo_session_t;
struct demo_session_t {
	mrcp_session_t        *client_session;
	mrcp_client_channel_t *channel;

	FILE                  *audio_in;
	apr_thread_mutex_t    *audio_mutex;

	apr_pool_t            *pool;
	
	apr_thread_cond_t     *wait_object;
	apr_thread_mutex_t    *wait_object_mutex;

	demo_event_t           wait_event;
};


static apr_status_t demo_recognizer_read_frame(audio_source_t *source, media_frame_t *frame);

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


static demo_session_t* demo_session_create()
{
	demo_session_t *demo_session;
	apr_pool_t *session_pool;

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

	demo_session = apr_palloc(session_pool,sizeof(demo_session_t));
	demo_session->pool = session_pool;
	demo_session->client_session = NULL;
	demo_session->channel = NULL;
	demo_session->audio_in = NULL;
	demo_session->wait_event = DEMO_EVENT_NONE;

	apr_thread_mutex_create(&demo_session->audio_mutex,APR_THREAD_MUTEX_UNNESTED,demo_session->pool);

	apr_thread_mutex_create(&demo_session->wait_object_mutex,APR_THREAD_MUTEX_UNNESTED,demo_session->pool);
	apr_thread_cond_create(&demo_session->wait_object, demo_session->pool);

	return demo_session;
}

static mrcp_status_t demo_session_destroy(demo_session_t *demo_session)
{
	if(!demo_session) {
		return MRCP_STATUS_FAILURE;
	}
	
	if(demo_session->audio_in) {
		fclose(demo_session->audio_in);
		demo_session->audio_in = NULL;
	}

	if(demo_session->pool) {
		apr_thread_cond_destroy(demo_session->wait_object);
		apr_thread_mutex_destroy(demo_session->wait_object_mutex);

		apr_thread_mutex_destroy(demo_session->audio_mutex);

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

static apr_status_t demo_recognizer_read_frame(audio_source_t *source, media_frame_t *frame)
{
	demo_session_t *demo_session = source->object;

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

static mrcp_status_t demo_session_wait_for_event(demo_session_t *demo_session, demo_event_t demo_event, size_t timeout)
{
	mrcp_status_t status = MRCP_STATUS_SUCCESS;
	apr_thread_mutex_lock(demo_session->wait_object_mutex);
	demo_session->wait_event = demo_event;
	if(apr_thread_cond_timedwait(demo_session->wait_object,demo_session->wait_object_mutex,timeout*1000) != APR_SUCCESS) {
		status = MRCP_STATUS_FAILURE;
	}
	apr_thread_mutex_unlock(demo_session->wait_object_mutex);
	return status;
}

static mrcp_status_t demo_session_signal_event(demo_session_t *demo_session, demo_event_t demo_event)
{
	mrcp_status_t status = MRCP_STATUS_SUCCESS;
	apr_thread_mutex_lock(demo_session->wait_object_mutex);
	if(demo_session->wait_event == demo_event) {
		apr_thread_cond_signal(demo_session->wait_object);
	}
	else {
		status = MRCP_STATUS_FAILURE;
		printf("DEMO: unexpected event [%d], waiting for event [%d]\n",demo_event,demo_session->wait_event);
	}
	apr_thread_mutex_unlock(demo_session->wait_object_mutex);
	return status;
}


static mrcp_status_t demo_on_session_initiate(mrcp_client_context_t *context, mrcp_session_t *session)
{
	demo_session_t *demo_session = mrcp_client_context_session_object_get(session);
	if(!demo_session) {
		return MRCP_STATUS_FAILURE;
	}
	return demo_session_signal_event(demo_session,DEMO_EVENT_SESSION_INITIATE);
}

static mrcp_status_t demo_on_session_terminate(mrcp_client_context_t *context, mrcp_session_t *session)
{
	demo_session_t *demo_session = mrcp_client_context_session_object_get(session);
	if(!demo_session) {
		return MRCP_STATUS_FAILURE;
	}
	return demo_session_signal_event(demo_session,DEMO_EVENT_SESSION_TERMINATE);
}

static mrcp_status_t demo_on_channel_add(mrcp_client_context_t *context, mrcp_session_t *session, mrcp_client_channel_t *control_channel, mrcp_audio_channel_t *audio_channel)
{
	demo_session_t *demo_session = mrcp_client_context_session_object_get(session);
	if(!demo_session) {
		return MRCP_STATUS_FAILURE;
	}
	return demo_session_signal_event(demo_session,DEMO_EVENT_CHANNEL_CREATE);
}

static mrcp_status_t demo_on_channel_remove(mrcp_client_context_t *context, mrcp_session_t *session, mrcp_client_channel_t *control_channel)
{
	demo_session_t *demo_session = mrcp_client_context_session_object_get(session);
	if(!demo_session) {
		return MRCP_STATUS_FAILURE;
	}
	return demo_session_signal_event(demo_session,DEMO_EVENT_CHANNEL_DESTROY);
}

static mrcp_status_t demo_on_channel_modify(mrcp_client_context_t *context, mrcp_session_t *session, mrcp_message_t *mrcp_message)
{
	demo_session_t *demo_session = mrcp_client_context_session_object_get(session);
	if(!demo_session) {
		return MRCP_STATUS_FAILURE;
	}
	return demo_session_signal_event(demo_session,DEMO_EVENT_CHANNEL_MODIFY);
}


static mrcp_status_t demo_recog_start(mrcp_client_context_t *context, demo_session_t *demo_session)
{
	// data is the sample data from recognize.msg
	const char text[] = 
		"<?xml version=\"1.0\"?>\r\n"
		"<grammar xmlns=\"http://www.w3.org/2001/06/grammar\"\r\n"
		"xml:lang=\"en-US\" version=\"1.0\" root=\"request\">\r\n"
		"<rule id=\"yes\">\r\n"
		"<one-of>\r\n"
		"<item xml:lang=\"fr-CA\">oui</item>\r\n"
		"<item xml:lang=\"en-US\">yes</item>\r\n"
		"</one-of>\r\n"
		"</rule>\r\n"
		"<rule id=\"request\">\r\n"
		"may I speak to\r\n"
		"<one-of xml:lang=\"fr-CA\">\r\n"
		"<item>Michel Tremblay</item>\r\n"
		"<item>Andre Roy</item>\r\n"
		"</one-of>\r\n"
		"</rule>\r\n"
		"</grammar>\r\n";


	mrcp_generic_header_t *generic_header;
	mrcp_recognizer_header_t *recognizer_header;
	mrcp_message_t *mrcp_message = mrcp_client_context_message_get(context,demo_session->client_session,demo_session->channel,RECOGNIZER_RECOGNIZE);
	if(!mrcp_message) {
		return MRCP_STATUS_FAILURE;
	}

	recognizer_header = mrcp_resource_header_prepare(mrcp_message);
	if(!recognizer_header) {
		return MRCP_STATUS_FAILURE;
	}
	recognizer_header->cancel_if_queue = FALSE;
	mrcp_resource_header_property_add(mrcp_message,RECOGNIZER_HEADER_CANCEL_IF_QUEUE);

	generic_header = mrcp_generic_header_prepare(mrcp_message);
	if(!generic_header) {
		return MRCP_STATUS_FAILURE;
	}
	generic_header->content_type = apr_pstrdup(mrcp_message->pool,"application/srgs+xml");
	mrcp_generic_header_property_add(mrcp_message,GENERIC_HEADER_CONTENT_TYPE);
	generic_header->content_id = apr_pstrdup(mrcp_message->pool,"request1@form-level.store");
	mrcp_generic_header_property_add(mrcp_message,GENERIC_HEADER_CONTENT_ID);
	mrcp_message->body = apr_pstrdup(mrcp_message->pool,text);

	return mrcp_client_context_channel_modify(context,demo_session->client_session,mrcp_message);
}

static mrcp_status_t demo_recog_stop(mrcp_client_context_t *context, demo_session_t *demo_session)
{
	mrcp_message_t *mrcp_message = mrcp_client_context_message_get(context,demo_session->client_session,demo_session->channel,RECOGNIZER_STOP);
	if(!mrcp_message) {
		return MRCP_STATUS_FAILURE;
	}

	return mrcp_client_context_channel_modify(context,demo_session->client_session,mrcp_message);
}

static mrcp_status_t demo_recog_get_result(mrcp_client_context_t *context, demo_session_t *demo_session)
{
	mrcp_recognizer_header_t *recognizer_header;
	mrcp_message_t *mrcp_message = mrcp_client_context_message_get(context,demo_session->client_session,demo_session->channel,RECOGNIZER_GET_RESULT);
	if(!mrcp_message) {
		return MRCP_STATUS_FAILURE;
	}

	recognizer_header = mrcp_resource_header_prepare(mrcp_message);
	if(!recognizer_header) {
		return MRCP_STATUS_FAILURE;
	}

	recognizer_header->confidence_threshold = 0.9f;
	mrcp_resource_header_property_add(mrcp_message,RECOGNIZER_HEADER_CONFIDENCE_THRESHOLD);

	return mrcp_client_context_channel_modify(context,demo_session->client_session,mrcp_message);
}

static mrcp_status_t demo_speech_open(mrcp_client_context_t *context, demo_session_t *demo_session)
{
	FILE *audio_in = fopen("speech_to_recog.pcm","rb");
	if(!audio_in) {
		return MRCP_STATUS_FAILURE;
	}

	apr_thread_mutex_lock(demo_session->audio_mutex);
	demo_session->audio_in = audio_in;
	apr_thread_mutex_unlock(demo_session->audio_mutex);
	return MRCP_STATUS_SUCCESS;
}

static mrcp_status_t demo_speech_close(mrcp_client_context_t *context, demo_session_t *demo_session)
{
	FILE *audio_in;
	
	apr_thread_mutex_lock(demo_session->audio_mutex);
	audio_in = demo_session->audio_in;
	demo_session->audio_in = NULL;
	apr_thread_mutex_unlock(demo_session->audio_mutex);
	
	if(audio_in) {
		fclose(audio_in);
	}
	return MRCP_STATUS_SUCCESS;
}


static mrcp_status_t demo_recognizer_basic_run(mrcp_client_context_t *mrcp_client_context)
{
	demo_session_t *demo_session;
	audio_source_t *source;

	/* create demo session */
	printf("Demo: Create Session\n");
	demo_session = demo_session_create();
	demo_session->client_session = mrcp_client_context_session_create(mrcp_client_context,demo_session);

	/* create audio source */
	source = apr_palloc(demo_session->pool,sizeof(audio_source_t));
	source->method_set = &audio_source_method_set;
	source->object = demo_session;

	/* create recognizer channel, also starts outgoing rtp media */
	printf("Demo: Create Recognizer Channel\n");
	demo_session->channel = mrcp_client_recognizer_channel_create(mrcp_client_context,demo_session->client_session,source);
	mrcp_client_context_channel_add(mrcp_client_context,demo_session->client_session,demo_session->channel,NULL);
	/* wait for recognizer channel creation */
	if(demo_session_wait_for_event(demo_session,DEMO_EVENT_CHANNEL_CREATE,DEMO_WAIT_TIMEOUT) == MRCP_STATUS_SUCCESS) {
		/* recognize */
		printf("Demo: Start Recognizer\n");
		demo_recog_start(mrcp_client_context,demo_session);
		demo_session_wait_for_event(demo_session,DEMO_EVENT_CHANNEL_MODIFY,DEMO_WAIT_TIMEOUT);

		/* start to stream demo speech to server to recognize */
		demo_speech_open(mrcp_client_context,demo_session);

		/* wait for start of speech (optional) */
		demo_session_wait_for_event(demo_session,DEMO_EVENT_CHANNEL_MODIFY,DEMO_WAIT_TIMEOUT);

		/* wait for recognition complete */
		if(demo_session_wait_for_event(demo_session,DEMO_EVENT_CHANNEL_MODIFY,2*DEMO_WAIT_TIMEOUT) != MRCP_STATUS_SUCCESS) {
			/* timeout elapsed */
			demo_recog_stop(mrcp_client_context,demo_session);
			demo_session_wait_for_event(demo_session,DEMO_EVENT_CHANNEL_MODIFY,DEMO_WAIT_TIMEOUT);
		}

		/* stop demo speech stream */
		demo_speech_close(mrcp_client_context,demo_session);

		/* send get result request (optional) */
		demo_recog_get_result(mrcp_client_context,demo_session);
		/* wait for get result response */
		demo_session_wait_for_event(demo_session,DEMO_EVENT_CHANNEL_MODIFY,DEMO_WAIT_TIMEOUT);

#if 0
		/*problems with Sofia-SIP reinvites*/
		printf("Demo: Destroy Recognizer Channel\n");
		mrcp_client_context_channel_destroy(mrcp_client_context,demo_session->client_session,1);
		demo_session_wait_for_event(demo_session,DEMO_EVENT_CHANNEL_DESTROY,DEMO_WAIT_TIMEOUT);
#endif
	}

	/* terminate demo session */
	printf("Demo: Terminate Session\n");
	mrcp_client_context_session_terminate(mrcp_client_context,demo_session->client_session);
	/* wait for demo session termination */
	demo_session_wait_for_event(demo_session,DEMO_EVENT_SESSION_TERMINATE,DEMO_WAIT_TIMEOUT);

	/* destroy demo session */
	printf("Demo: Destroy Session\n");
	mrcp_client_context_session_destroy(mrcp_client_context,demo_session->client_session);
	demo_session_destroy(demo_session);
	return MRCP_STATUS_SUCCESS;
}

void demo_recognizer_run(openmrcp_client_options_t *options, apr_pool_t *pool)
{
	mrcp_client_event_handler_t client_event_handler;
	mrcp_client_t *mrcp_client;
	mrcp_client_context_t *mrcp_client_context;

	client_event_handler.on_session_initiate = demo_on_session_initiate;
	client_event_handler.on_session_terminate = demo_on_session_terminate;
	client_event_handler.on_channel_add = demo_on_channel_add;
	client_event_handler.on_channel_remove = demo_on_channel_remove;
	client_event_handler.on_channel_modify = demo_on_channel_modify;

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

	/* this basically starts a thread that pulls events from the event queue
	   and handles them */
	mrcp_client = openmrcp_client_start(options,mrcp_client_context);
	if(!mrcp_client) {
		mrcp_client_context_destroy(mrcp_client_context);
		return;
	}
	
	printf("Demo: Press enter to start recognizer demo\n");
	getchar();

	demo_recognizer_basic_run(mrcp_client_context);

	printf("Demo: Completed, press enter to exit\n");
	getchar();

	/* shutdown client engine */
	openmrcp_client_shutdown(mrcp_client);

	/* destroy client context */
	mrcp_client_context_destroy(mrcp_client_context);
}
