/* 
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com>
 *
 * 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.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthmct@yahoo.com>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * Anthony Minessale II <anthmct@yahoo.com>
 *
 *
 * mod_spidermonkey_teletone.c -- TeleTone Javascript Module
 *
 */
#include "mod_spidermonkey.h"
#include <libteletone.h>

static const char modname[] = "TeleTone";

typedef enum {
	TTF_DTMF = (1 << 0)
} teletone_flag_t;


struct teletone_obj {
	teletone_generation_session_t ts;
	JSContext *cx;
	JSObject *obj;
	switch_core_session_t *session;
	switch_codec_t codec;
	switch_buffer_t *audio_buffer;
	switch_buffer_t *loop_buffer;
	switch_memory_pool_t *pool;
	switch_timer_t *timer;
	switch_timer_t timer_base;
	JSFunction *function;
	jsval arg;
	jsval ret;
	unsigned int flags;
};


static int teletone_handler(teletone_generation_session_t *ts, teletone_tone_map_t *map)
{
	struct teletone_obj *tto = ts->user_data;
	int wrote;

	if (!tto) {
		return -1;
	}
	wrote = teletone_mux_tones(ts, map);
	switch_buffer_write(tto->audio_buffer, ts->buffer, wrote * 2);

	return 0;
}


/* TeleTone Object */
/*********************************************************************************/
static JSBool teletone_construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	JSObject *session_obj;
	struct teletone_obj *tto = NULL;
	struct js_session *jss = NULL;
	switch_codec_t *read_codec;
	switch_memory_pool_t *pool;
	char *timer_name = NULL;

	if (argc > 0) {
		if (JS_ValueToObject(cx, argv[0], &session_obj)) {
			if (!(jss = JS_GetPrivate(cx, session_obj))) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot Find Session [1]\n");
				return JS_FALSE;
			}
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot Find Session [2]\n");
			return JS_FALSE;
		}
	} else {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing Session Arg\n");
		return JS_FALSE;
	}
	if (argc > 1) {
		timer_name = JS_GetStringBytes(JS_ValueToString(cx, argv[1]));
	}

	switch_core_new_memory_pool(&pool);

	if (!(tto = switch_core_alloc(pool, sizeof(*tto)))) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error\n");
		return JS_FALSE;
	}

	read_codec = switch_core_session_get_read_codec(jss->session);

	if (switch_core_codec_init(&tto->codec,
							   "L16",
							   NULL,
							   read_codec->implementation->samples_per_second,
							   read_codec->implementation->microseconds_per_frame / 1000,
							   read_codec->implementation->number_of_channels,
							   SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
							   NULL, pool) == SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activated\n");
	} else {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activation Failed\n");
		return JS_FALSE;
	}

	if (timer_name) {
		unsigned int ms = read_codec->implementation->microseconds_per_frame / 1000;
		if (switch_core_timer_init(&tto->timer_base,
								   timer_name,
								   ms,
								   (read_codec->implementation->samples_per_second / 50) * read_codec->implementation->number_of_channels,
								   pool) == SWITCH_STATUS_SUCCESS) {
			tto->timer = &tto->timer_base;
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Timer INIT Success %u\n", ms);
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Timer INIT Failed\n");
		}
	}

	switch_buffer_create_dynamic(&tto->audio_buffer, JS_BLOCK_SIZE, JS_BUFFER_SIZE, 0);
	tto->pool = pool;
	tto->obj = obj;
	tto->cx = cx;
	tto->session = jss->session;
	teletone_init_session(&tto->ts, 0, teletone_handler, tto);
	JS_SetPrivate(cx, obj, tto);

	return JS_TRUE;
}

static void teletone_destroy(JSContext *cx, JSObject *obj)
{
	struct teletone_obj *tto = JS_GetPrivate(cx, obj);
	switch_memory_pool_t *pool;
	if (tto) {
		if (tto->timer) {
			switch_core_timer_destroy(tto->timer);
		}
		teletone_destroy_session(&tto->ts);
		switch_buffer_destroy(&tto->audio_buffer);
		switch_buffer_destroy(&tto->loop_buffer);
		switch_core_codec_destroy(&tto->codec);
		pool = tto->pool;
		tto->pool = NULL;
		if (pool) {
			switch_core_destroy_memory_pool(&pool);
		}
	}
}

static JSBool teletone_add_tone(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	struct teletone_obj *tto = JS_GetPrivate(cx, obj);
	if (argc > 2) { 
		int x;
		char *fval;
		char *map_str;
		map_str = JS_GetStringBytes(JS_ValueToString(cx, argv[0]));

		for(x = 1; x < TELETONE_MAX_TONES; x++) {
			fval = JS_GetStringBytes(JS_ValueToString(cx, argv[x]));
			tto->ts.TONES[(int)*map_str].freqs[x-1] = strtod(fval, NULL);
		}
		return JS_TRUE;
	}
	
	return JS_FALSE;
}

static JSBool teletone_on_dtmf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	struct teletone_obj *tto = JS_GetPrivate(cx, obj);
	if (argc > 0) {
		tto->function = JS_ValueToFunction(cx, argv[0]);
		if (argc > 1) {
			tto->arg = argv[1];
		}
		switch_set_flag(tto, TTF_DTMF);
	}
	return JS_TRUE;
}

static JSBool teletone_generate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	struct teletone_obj *tto = JS_GetPrivate(cx, obj);
	int32 loops = 0;

	if (argc > 0) {
		char *script;
		switch_core_session_t *session;
		switch_frame_t write_frame = {0};
		unsigned char *fdata[1024];
		switch_frame_t *read_frame;
		int stream_id;
		switch_core_thread_session_t thread_session;
		switch_channel_t *channel;

		if (argc > 1) {
			if (!JS_ValueToInt32(cx, argv[1], &loops)) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot Convert to INT\n");
				return JS_FALSE;
			}
			loops--;
			if (!tto->loop_buffer) {
				switch_buffer_create_dynamic(&tto->loop_buffer, JS_BLOCK_SIZE, JS_BUFFER_SIZE, 0);
			}
		} 

		if (tto->audio_buffer) {
			switch_buffer_zero(tto->audio_buffer);
		}
		if (tto->loop_buffer) {
			switch_buffer_zero(tto->loop_buffer);
		}
		tto->ts.debug = 1;
		tto->ts.debug_stream = switch_core_get_console();

		script = JS_GetStringBytes(JS_ValueToString(cx, argv[0]));
		teletone_run(&tto->ts, script);

		session = tto->session;
		write_frame.codec = &tto->codec;
		write_frame.data = fdata;

		channel = switch_core_session_get_channel(session);
		
		if (tto->timer) {
			for (stream_id = 0; stream_id < switch_core_session_get_stream_count(session); stream_id++) {
				switch_core_service_session(session, &thread_session, stream_id);
			}
		}

		for(;;) {

			if (switch_test_flag(tto, TTF_DTMF)) {
				char dtmf[128];
				char *ret;

				if (switch_channel_has_dtmf(channel)) {
					uintN aargc = 0;
					jsval aargv[4];

					switch_channel_dequeue_dtmf(channel, dtmf, sizeof(dtmf));
					aargv[aargc++] = STRING_TO_JSVAL (JS_NewStringCopyZ(cx, dtmf));
					JS_CallFunction(cx, obj, tto->function, aargc, aargv, &tto->ret);
					ret = JS_GetStringBytes(JS_ValueToString(cx, tto->ret));
					if (strcmp(ret, "true") && strcmp(ret, "undefined")) {
						*rval = tto->ret;
						return JS_TRUE;
					}
				}
			}
			
			if (tto->timer) {
				if (switch_core_timer_next(tto->timer)< 0) {
					break;
				}

			} else {
				switch_status_t status;
				status = switch_core_session_read_frame(session, &read_frame, -1, 0);
				
				if (!SWITCH_READ_ACCEPTABLE(status)) {
					break;
				}
			}
			if ((write_frame.datalen = (uint32_t)switch_buffer_read(tto->audio_buffer, fdata, write_frame.codec->implementation->bytes_per_frame)) <= 0) {
				if (loops) { 
					switch_buffer_t *tmp;

					/* Switcharoo*/
					tmp = tto->audio_buffer;
					tto->audio_buffer = tto->loop_buffer;
					tto->loop_buffer = tmp;
					loops--;
					/* try again */
					if ((write_frame.datalen = 
						 (uint32_t)switch_buffer_read(tto->audio_buffer, fdata, write_frame.codec->implementation->bytes_per_frame)) <= 0) {
						break;
					}
				} else {
					break;
				}
			}

			write_frame.samples = write_frame.datalen / 2;
			for (stream_id = 0; stream_id < switch_core_session_get_stream_count(session); stream_id++) {
				if (switch_core_session_write_frame(session, &write_frame, -1, stream_id) != SWITCH_STATUS_SUCCESS) {
					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Bad Write\n");
					break;
				}
			}
			if (tto->loop_buffer && loops) {
				switch_buffer_write(tto->loop_buffer, write_frame.data, write_frame.datalen);
			}
		}

		if (tto->timer) {
			switch_core_thread_session_end(&thread_session);
		}
		return JS_TRUE;
	}
	
	return JS_FALSE;
}

enum teletone_tinyid {
	TELETONE_NAME
};

static JSFunctionSpec teletone_methods[] = {
	{"generate", teletone_generate, 1},
	{"onDTMF", teletone_on_dtmf, 1},
	{"addTone", teletone_add_tone, 10},	 
	{0}
};


static JSPropertySpec teletone_props[] = {
	{"name", TELETONE_NAME, JSPROP_READONLY|JSPROP_PERMANENT}, 
	{0}
};


static JSBool teletone_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
	JSBool res = JS_TRUE;

	return res;
}

JSClass teletone_class = {
	modname, JSCLASS_HAS_PRIVATE, 
	JS_PropertyStub,  JS_PropertyStub,	teletone_getProperty,  JS_PropertyStub, 
	JS_EnumerateStub, JS_ResolveStub,	JS_ConvertStub,	  teletone_destroy, NULL, NULL, NULL,
	teletone_construct
};


switch_status_t teletone_load(JSContext *cx, JSObject *obj)
{
	JS_InitClass(cx,
				 obj,
				 NULL,
				 &teletone_class,
				 teletone_construct,
				 3,
				 teletone_props,
				 teletone_methods,
				 teletone_props,
				 teletone_methods
				 );
	return SWITCH_STATUS_SUCCESS;
}


const sm_module_interface_t teletone_module_interface = {
	/*.name = */ modname,
	/*.spidermonkey_load*/ teletone_load,
	/*.next*/ NULL
};

SWITCH_MOD_DECLARE(switch_status_t) spidermonkey_init(const sm_module_interface_t **module_interface)
{
	*module_interface = &teletone_module_interface;
	return SWITCH_STATUS_SUCCESS;
}

/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:nil
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab:
 */
