/*
 * mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2013-2014, Peter Olsson <peter@olssononline.se>
 *
 * 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.
 *
 * Ported from the Original Code in FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthm@freeswitch.org>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Peter Olsson <peter@olssononline.se>
 * Anthony Minessale II <anthm@freeswitch.org>
 * William King <william.king@quentustech.com>
 *
 * fsglobal.cpp -- JavaScript Global functions
 *
 */

#include "mod_v8.h"
#include "fsglobal.hpp"
#include "fssession.hpp"
#include <switch_curl.h>

using namespace std;
using namespace v8;

class CURLCallbackData
{
public:
	Isolate *isolate;
	Persistent<Array> hashObject;
	int fileHandle;
	switch_size_t bufferSize;
	switch_size_t bufferDataLength;
	char *buffer;

	CURLCallbackData() {
		isolate = NULL;
		fileHandle = 0;
		bufferSize = 0;
		bufferDataLength = 0;
		buffer = NULL;
	}

	~CURLCallbackData() {
		hashObject.Reset();
	}
};

size_t FSGlobal::HashCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
	register size_t realsize = size * nmemb;
	char *line, lineb[2048], *nextline = NULL, *val = NULL, *p = NULL;
	CURLCallbackData *config_data = (CURLCallbackData *)data;

	if (!config_data->hashObject.IsEmpty()) {
		switch_copy_string(lineb, (char *) ptr, sizeof(lineb));
		line = lineb;

		HandleScope scope(config_data->isolate);

		/* Get the array object to store data in */
		Local<Array> args = Local<Array>::New(config_data->isolate, config_data->hashObject);

		while (line) {
			if ((nextline = strchr(line, '\n'))) {
				*nextline = '\0';
				nextline++;
			}

			if ((val = strchr(line, '='))) {
				*val = '\0';
				val++;
				if (val[0] == '>') {
					*val = '\0';
					val++;
				}

				for (p = line; p && *p == ' '; p++);
				line = p;
				for (p = line + strlen(line) - 1; *p == ' '; p--)
					*p = '\0';
				for (p = val; p && *p == ' '; p++);
				val = p;
				for (p = val + strlen(val) - 1; *p == ' '; p--)
					*p = '\0';

				// Add data to hash
				args->Set(String::NewFromUtf8(config_data->isolate, line), String::NewFromUtf8(config_data->isolate, js_safe_str(val)));
			}

			line = nextline;
		}
	}

	return realsize;
}

size_t FSGlobal::FileCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
	register unsigned int realsize = (unsigned int) (size * nmemb);
	CURLCallbackData *config_data = (CURLCallbackData *)data;

	if ((write(config_data->fileHandle, ptr, realsize) != (int) realsize)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to write all bytes!\n");
	}

	return realsize;
}

size_t FSGlobal::FetchUrlCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
	register unsigned int realsize = (unsigned int) (size * nmemb);
	CURLCallbackData *config_data = (CURLCallbackData *)data;

	/* Too much data. Do not increase buffer, but abort fetch instead. */
	if (config_data->bufferDataLength + realsize >= config_data->bufferSize) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Data do not fit in the allocated buffer\n");
		return 0;
	}

	memcpy(config_data->buffer + config_data->bufferDataLength, ptr, realsize);
	config_data->bufferDataLength += realsize;
	config_data->buffer[config_data->bufferDataLength] = 0;

	return realsize;
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(FetchURLHash)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	switch_CURL *curl_handle = NULL;
	CURLCallbackData config_data;
	int saveDepth = 0;

	if (info.Length() > 0) {
		String::Utf8Value url(info[0]);

		if (info.Length() > 1 && info[1]->IsString()) {
			/* Cast to string */
			Local<String> str = Local<String>::Cast(info[1]);

			/* Try to get existing variable */
			Local<Value> obj = info.GetIsolate()->GetCurrentContext()->Global()->Get(str);

			if (!obj.IsEmpty() && obj->IsArray()) {
				/* The existing var is an array, use it */
				config_data.hashObject.Reset(info.GetIsolate(), Local<Array>::Cast(obj));
			} else if (obj.IsEmpty() || obj->IsUndefined()) {
				/* No existing var (or an existing that is undefined), create a new one */
				Local<Array> arguments = Array::New(info.GetIsolate());
				info.GetIsolate()->GetCurrentContext()->Global()->Set(str, arguments);
				config_data.hashObject.Reset(info.GetIsolate(), arguments);
			} else {
				/* The var exists, but is wrong type - exit with error */
				info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Second argument is the name of an existing var of the wrong type"));
				return;
			}
		} else if (info.Length() > 1 && info[1]->IsArray()) {
			/* The var is an array, use it */
			config_data.hashObject.Reset(info.GetIsolate(), Local<Array>::Cast(info[1]));
		} else if (info.Length() > 1) {
			/* The var exists, but is wrong type - exit with error */
			info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Second argument is of the wrong type"));
			return;
		} else {
			/* Second argument doesn't exist, this is also ok. The hash will be returned as the result */
			Local<Array> arguments = Array::New(info.GetIsolate());
			config_data.hashObject.Reset(info.GetIsolate(), arguments);
		}

		curl_handle = switch_curl_easy_init();

		if (!strncasecmp(js_safe_str(*url), "https", 5)) {
			switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
			switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
		}

		config_data.isolate = info.GetIsolate();

		switch_curl_easy_setopt(curl_handle, CURLOPT_URL, js_safe_str(*url));
		switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
		switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
		switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
		switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, FSGlobal::HashCallback);
		switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &config_data);
		switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "freeswitch-v8/1.0");

		switch_curl_easy_perform(curl_handle);

		switch_curl_easy_cleanup(curl_handle);

		/* Return the hash */
		info.GetReturnValue().Set(config_data.hashObject);
	} else {
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Invalid arguments"));
	}
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(FetchURLFile)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	switch_CURL *curl_handle = NULL;
	CURLCallbackData config_data;
	int saveDepth = 0;

	if (info.Length() > 1) {
		const char *url = NULL, *filename = NULL;
		String::Utf8Value str1(info[0]);
		String::Utf8Value str2(info[1]);
		url = js_safe_str(*str1);
		filename = js_safe_str(*str2);

		curl_handle = switch_curl_easy_init();
		if (!strncasecmp(url, "https", 5)) {
			switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
			switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
		}
		config_data.isolate = info.GetIsolate();

		if ((config_data.fileHandle = open(filename, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) {
			switch_curl_easy_setopt(curl_handle, CURLOPT_URL, url);
			switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
			switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
			switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
			switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, FSGlobal::FileCallback);
			switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &config_data);
			switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "freeswitch-v8/1.0");

			switch_curl_easy_perform(curl_handle);

			switch_curl_easy_cleanup(curl_handle);
			close(config_data.fileHandle);
			info.GetReturnValue().Set(true);
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to open file [%s]\n", filename);
			info.GetReturnValue().Set(false);
		}
	} else {
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Invalid arguments"));
	}
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(FetchURL)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	switch_CURL *curl_handle = NULL;
	CURLCallbackData config_data;
	int32_t buffer_size = 65535;
	switch_CURLcode code = (switch_CURLcode)0;
	int saveDepth = 0;

	if (info.Length() >= 1) {
		const char *url;
		String::Utf8Value str(info[0]);
		url = js_safe_str(*str);
		if (info.Length() > 1) {
			buffer_size = info[1]->Int32Value();
		}

		curl_handle = switch_curl_easy_init();
		if (!strncasecmp(url, "https", 5)) {
			switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
			switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
		}

		config_data.isolate = info.GetIsolate();
		config_data.bufferSize = buffer_size;
		config_data.buffer = (char *)malloc(config_data.bufferSize);
		config_data.bufferDataLength = 0;

		if (config_data.buffer == NULL) {
			info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Failed to allocate data buffer."));
			switch_curl_easy_cleanup(curl_handle);
			return;
		}

		switch_curl_easy_setopt(curl_handle, CURLOPT_URL, url);
		switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
		switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
		switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
		switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, FSGlobal::FetchUrlCallback);
		switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &config_data);
		switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "freeswitch-v8/1.0");

		code = switch_curl_easy_perform(curl_handle);

		switch_curl_easy_cleanup(curl_handle);

		if (code == CURLE_OK) {
			config_data.buffer[config_data.bufferDataLength] = 0;
			info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), js_safe_str(config_data.buffer)));
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Curl returned error %u\n", (unsigned) code);
			info.GetReturnValue().Set(false);
		}

		free(config_data.buffer);
	} else {
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Invalid arguments"));
	}
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(Exit)
{
	JS_CHECK_SCRIPT_STATE();

	if (info.Length() > 0) {
		HandleScope handle_scope(info.GetIsolate());
		String::Utf8Value str(info[0]);

		if (*str) {
			JSMain::ExitScript(info.GetIsolate(), *str);
			return;
		}
	}

	JSMain::ExitScript(info.GetIsolate(), NULL);
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(Log)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	char *level_str, *msg;
	switch_log_level_t level = SWITCH_LOG_DEBUG;
	char *file = NULL;
	int line = 0;

	if (info.Length() > 0) {
		/* Get filename and line number from stack */
		file = JSMain::GetStackInfo(info.GetIsolate(), &line);
	}

	if (info.Length() > 1) {
		String::Utf8Value str(info[0]);

		if ((level_str = *str)) {
			level = switch_log_str2level(level_str);
			if (level == SWITCH_LOG_INVALID) {
				level = SWITCH_LOG_DEBUG;
			}
		}

		String::Utf8Value str2(info[1]);
		if ((msg = *str2) && *msg != '\0') {
			const char lastchar = msg[strlen(msg)-1];
			switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, "console_log", line, NULL, level, "%s%s", msg, lastchar != '\n' ? "\n" : "");
			switch_safe_free(file);
			return;
		}
	} else if (info.Length() > 0) {
		String::Utf8Value str(info[0]);
		if ((msg = *str) && *msg != '\0') {
			const char lastchar = msg[strlen(msg)-1];
			switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, "console_log", line, NULL, level, "%s%s", msg, lastchar != '\n' ? "\n" : "");
			switch_safe_free(file);
			return;
		}
	}

	switch_safe_free(file);
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(GlobalSet)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	const char *var_name = NULL, *val = NULL, *val2 = NULL;
	bool tf = true;

	if (info.Length() > 1) {
		String::Utf8Value str1(info[0]);
		String::Utf8Value str2(info[1]);
		var_name = js_safe_str(*str1);
		val = js_safe_str(*str2);
		if (info.Length() == 2) {
			switch_core_set_variable(var_name, val);
			info.GetReturnValue().Set(true);
			return;
		} else {
			String::Utf8Value str3(info[2]);
			val2 = js_safe_str(*str3);
			if (switch_core_set_var_conditional(var_name, val, val2) != SWITCH_TRUE) {
				tf = false;
			}
			info.GetReturnValue().Set(tf);
			return;
		}
	}

	info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "var name not supplied!"));
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(GlobalGet)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	const char *var_name = NULL;
	char *val = NULL;

	if (info.Length() > 0) {
		String::Utf8Value str(info[0]);
		var_name = js_safe_str(*str);
		val = switch_core_get_variable_dup(var_name);
		info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), js_safe_str(val)));
		switch_safe_free(val);
		return;
	}

	info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "var name not supplied!"));
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(Include)
{
	JS_CHECK_SCRIPT_STATE();
	if (info.Length() < 1) {
		/* Bad arguments, return exception */
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Invalid arguments"));
	} else {
		/* Try to get the current script path */
		string scriptPath = "";
		Handle<Value> val = info.GetIsolate()->GetCurrentContext()->Global()->Get(String::NewFromUtf8(info.GetIsolate(), "scriptPath"));
		if (!val.IsEmpty() && val->IsString()) {
			String::Utf8Value tmp(val);
			if (*tmp) {
				scriptPath = *tmp;
			}
		}

		/* Loop all arguments until we find a valid file */
		for (int i = 0; i < info.Length(); i++) {
			HandleScope handle_scope(info.GetIsolate());
			String::Utf8Value str(info[i]);
			char *path = NULL;
			const char *script_name = NULL;

			if (*str) {
				if (switch_is_file_path(*str)) {
					script_name = *str;
				} else {
					/* First try to use the same path as the executing script */
					if (scriptPath.length() > 0) {
						path = switch_mprintf("%s%s%s", scriptPath.c_str(), SWITCH_PATH_SEPARATOR, *str);
						if (path && JSMain::FileExists(path)) {
							script_name = path;
						} else {
							switch_safe_free(path);
						}
					}

					/* At last, use FS script directory */
					if (!script_name && (path = switch_mprintf("%s%s%s", SWITCH_GLOBAL_dirs.script_dir, SWITCH_PATH_SEPARATOR, *str))) {
						script_name = path;
					}
				}
			}

			if (script_name && JSMain::FileExists(script_name)) {
				string js_file = JSMain::LoadFileToString(script_name);

				if (js_file.length() > 0) {
					Handle<String> source = String::NewFromUtf8(info.GetIsolate(), js_file.c_str());
					Handle<Script> script = Script::Compile(source, info[i]);
					info.GetReturnValue().Set(script->Run());
					switch_safe_free(path);
					return;
				}
			}
			switch_safe_free(path);
		}
	
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Failed to include javascript file"));
	}
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(Sleep)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	int32_t msec = 0;

	if (info.Length() > 0) {
		msec = info[0]->Int32Value();
	}

	if (msec) {
		switch_yield(msec * 1000);
	} else {
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "No time specified"));
	}
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(Use)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());

	if (info.Length() > 0) {
		String::Utf8Value str(info[0]);
		char *mod_name = *str;

		if (mod_name) {
			const v8_mod_interface_t *mp;

			if ((mp = (v8_mod_interface_t *)switch_core_hash_find(module_manager.load_hash, mod_name))) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Loading %s\n", mod_name);
				mp->v8_mod_load(info);
			} else {
				char *err = switch_mprintf("Error loading %s", mod_name);
				info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), err));
				free(err);
			}
		}
	} else {
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Invalid arguments"));
	}
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(ApiExecute)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());

	if (info.Length() > 0) {
		String::Utf8Value str(info[0]);
		const char *cmd = js_safe_str(*str);
		string arg;
		switch_core_session_t *session = NULL;
		switch_stream_handle_t stream = { 0 };

		if (!strcasecmp(cmd, "jsapi")) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid API Call!\n");
			info.GetReturnValue().Set(false);
			return;
		}

		if (info.Length() > 1) {
			String::Utf8Value str2(info[1]);
			arg = js_safe_str(*str2);
		}

		if (info.Length() > 2) {
			if (!info[2].IsEmpty() && info[2]->IsObject()) {
				Handle<Object> session_obj = Handle<Object>::Cast(info[2]);
				FSSession *obj = JSBase::GetInstance<FSSession>(session_obj);
				if (obj) {
					session = obj->GetSession();
				}
			}
		}

		SWITCH_STANDARD_STREAM(stream);
		switch_api_execute(cmd, arg.c_str(), session, &stream);

		info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), switch_str_nil((char *) stream.data)));
		switch_safe_free(stream.data);
	} else {
		info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), ""));
	}
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(Email)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	string to, from, headers, body, file, convert_cmd, convert_ext;
	
	if (info.Length() > 0) {
		String::Utf8Value str(info[0]);
		to = js_safe_str(*str);
	}

	if (info.Length() > 1) {
		String::Utf8Value str(info[1]);
		from = js_safe_str(*str);
	}

	if (info.Length() > 2) {
		String::Utf8Value str(info[2]);
		headers = js_safe_str(*str);
	}

	if (info.Length() > 3) {
		String::Utf8Value str(info[3]);
		body = js_safe_str(*str);
	}

	if (info.Length() > 4) {
		String::Utf8Value str(info[4]);
		file = js_safe_str(*str);
	}

	if (info.Length() > 5) {
		String::Utf8Value str(info[5]);
		convert_cmd = js_safe_str(*str);
	}

	if (info.Length() > 6) {
		String::Utf8Value str(info[6]);
		convert_ext = js_safe_str(*str);
	}

	if (to.c_str() && from.c_str() && headers.c_str() && body.c_str() && switch_simple_email(to.c_str(), from.c_str(), headers.c_str(), body.c_str(), file.c_str(), convert_cmd.c_str(), convert_ext.c_str()) == SWITCH_TRUE) {
		info.GetReturnValue().Set(true);
		return;
    }

	info.GetReturnValue().Set(false);
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(Bridge)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	FSSession *jss_a = NULL, *jss_b = NULL;
	Handle<Object> obj_a;
	Handle<Object> obj_b;
	void *bp = NULL;
	switch_input_callback_function_t dtmf_func = NULL;
	FSInputCallbackState cb_state;

	info.GetReturnValue().Set(false);

	if (info.Length() > 1) {
		if (info[0]->IsObject()) {
			obj_a = Handle<Object>::Cast(info[0]);
			
			if (!(jss_a = JSBase::GetInstance<FSSession>(obj_a))) {
				info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Cannot find session A"));
				return;
			}
		}
		if (info[1]->IsObject()) {
			obj_b = Handle<Object>::Cast(info[1]);
			if (!(jss_b = JSBase::GetInstance<FSSession>(obj_b))) {
				info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Cannot find session B"));
				return;
			}
		}
	}

	if (!(jss_a && jss_a->GetSession())) {
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "session A is not ready!"));
		return;
	}

	if (!(jss_b && jss_b->GetSession())) {
		info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "session B is not ready!"));
		return;
	}

	if (info.Length() > 2) {
		Handle<Function> func = JSBase::GetFunctionFromArg(info.GetIsolate(), info[2]);

		if (!func.IsEmpty()) {
			cb_state.function.Reset(info.GetIsolate(), func);

			if (info.Length() > 3 && !info[3].IsEmpty()) {
				cb_state.arg.Reset(info.GetIsolate(), info[3]);
			}

			cb_state.jss_a = jss_a;
			cb_state.jss_b = jss_b;
			cb_state.session_obj_a.Reset(info.GetIsolate(), obj_a);
			cb_state.session_obj_b.Reset(info.GetIsolate(), obj_b);
			cb_state.session_state = jss_a;
			dtmf_func = FSSession::CollectInputCallback;
			bp = &cb_state;
		}
	}

	switch_ivr_multi_threaded_bridge(jss_a->GetSession(), jss_b->GetSession(), dtmf_func, bp, bp);

	info.GetReturnValue().Set(true);
}

/* Replace this with more robust version later */
JS_GLOBAL_FUNCTION_IMPL_STATIC(System)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	char *cmd;
	int result;
	info.GetReturnValue().Set(false);

	if (info.Length() > 0) {
		String::Utf8Value str(info[0]);
		cmd = *str;
		if (cmd) {
			result = switch_system(cmd, SWITCH_TRUE);
			info.GetReturnValue().Set(Integer::New(info.GetIsolate(), result));
			return;
		}
	}

	info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Invalid arguments"));
}

JS_GLOBAL_FUNCTION_IMPL_STATIC(FileDelete)
{
	JS_CHECK_SCRIPT_STATE();
	HandleScope handle_scope(info.GetIsolate());
	const char *path;

	info.GetReturnValue().Set(false);

	if (info.Length() > 0) {
		String::Utf8Value str(info[0]);
		path = *str;
		if (path) {
			if ((switch_file_remove(path, NULL)) == SWITCH_STATUS_SUCCESS) {
				info.GetReturnValue().Set(true);
			}
			return;
		}
	}

	info.GetIsolate()->ThrowException(String::NewFromUtf8(info.GetIsolate(), "Invalid arguments"));
}

static const js_function_t fs_proc[] = {
	{"console_log", FSGlobal::Log},				// Deprecated
	{"consoleLog", FSGlobal::Log},
	{"log", FSGlobal::Log},						// Short version of 'consoleLog()'
	{"getGlobalVariable", FSGlobal::GlobalGet},
	{"setGlobalVariable", FSGlobal::GlobalSet},
	{"exit", FSGlobal::Exit},
	{"include", FSGlobal::Include},
	{"require", FSGlobal::Include},				// Extra version of 'include()'
	{"bridge", FSGlobal::Bridge},
	{"email", FSGlobal::Email},
	{"apiExecute", FSGlobal::ApiExecute},
	{"use", FSGlobal::Use},
	{"msleep", FSGlobal::Sleep},
	{"fileDelete", FSGlobal::FileDelete},
	{"system", FSGlobal::System},
	{"fetchURL", FSGlobal::FetchURL},			// Deprecated
	{"fetchURLHash", FSGlobal::FetchURLHash},	// Deprecated
	{"fetchURLFile", FSGlobal::FetchURLFile},	// Deprecated
	{"fetchUrl", FSGlobal::FetchURL},
	{"fetchUrlHash", FSGlobal::FetchURLHash},
	{"fetchUrlFile", FSGlobal::FetchURLFile},
	{0}
};

const js_function_t *FSGlobal::GetFunctionDefinitions()
{
	return fs_proc;
}

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