/* 
 * 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>
 *
 *
 * SpeechTools.jm Speech Detection Interface
 *
 */

/* Constructor for Grammar Class (Class to identify a grammar entity) */
function Grammar(grammar_name, path, obj_path, min_score, confirm_score, halt) {
	this.grammar_name = grammar_name;
	this.path = path;
	this.min_score = min_score;
	this.confirm_score = confirm_score;
	this.halt = halt;
	this.obj_path = obj_path;

	if (!this.min_score) {
		this.min_score = 1;
	}

	if (!this.confirm_score) {
		this.confirm_score = 400;
	}
}

/* Constructor for SpeechDetect Class (Class to Detect Speech) */
function SpeechDetect(session, mod, ip) {
	this.ip = ip;
	this.session = session;
	this.mod = mod;
	this.grammar_name = undefined;
	this.grammar_hash = new Array();
	this.grammar_name = false;
	this.audio_base = "";
	this.audio_ext = ".wav";
	this.tts_eng = false;
	this.tts_voice = false;
	this.AutoUnload = false;
	this.debug = false;

	/* Set the TTS info*/
	this.setTTS = function (tts_eng, tts_voice) {
		this.tts_eng = tts_eng;
		this.tts_voice = tts_voice;		
	}

	/* Set the audio base */
	this.setAudioBase = function (audio_base) {
		this.audio_base = audio_base;
	}

	/* Set the audio extension */
	this.setAudioExt= function (audio_ext) {
		this.audio_ext = audio_ext;
	}
	
	/* Add a grammar to be used*/
	this.addGrammar = function(grammar_object) {
		this.grammar_hash[grammar_object.grammar_name] = grammar_object;
	}

	/* Play an audio file */
	this.streamFile = function(str) {
		var rv;
		if (!str) {
			console_log("error", "No file specified!\n");
			return;
		}
		files = str.split(",");
		for( x = 0; x < files.length; x++) {
			if (!files[x] || files[x] == "noop") {
				continue;
			}
			this.session.streamFile(this.audio_base + files[x] + this.audio_ext);
		}
	}

	/* Speak with TTS */
	this.speak = function(str) {
		return this.session.speak(this.tts_eng, this.tts_voice, str);
	}

	/* Set the current grammar */
	this.setGrammar = function (grammar_name) {
		var grammar_object = this.grammar_hash[grammar_name];
		
		if (!grammar_object) {
			console_log("error", "Missing Grammar!\n");
			return false;
		}

		if (this.grammar_name) {
			if (this.AutoUnload) {
				console_log("debug", "Unloading grammar " + this.grammar_name + "\n");
				this.session.execute("detect_speech", "nogrammar " + this.grammar_name);
			}
			if (grammar_object.path) {
				this.session.execute("detect_speech", "grammar " + grammar_name + " " + grammar_object.path);
			} else {
				this.session.execute("detect_speech", "grammar " + grammar_name);
			}
		} else {
			this.session.execute("detect_speech", this.mod + " " + grammar_name + " " + grammar_object.path + " " + this.ip);
		}

		this.grammar_name = grammar_name;
	}

	/* Pause speech detection */
	this.pause = function() {
		this.session.execute("detect_speech", "pause");
	}

	/* Resume speech detection */
	this.resume = function() {
		this.session.execute("detect_speech", "resume");
	}

	/* Stop speech detection */
	this.stop = function() {
		this.session.execute("detect_speech", "stop");
	}

	/* Callback function for streaming,TTS or bridged calls */
	this.onInput = function(type, inputEvent, _this) {
		if (type == "event") {
			var speech_type = inputEvent.getHeader("Speech-Type");
			var rv = new Array();

			if (!_this.grammar_name) {
				console_log("error", "No Grammar name!\n");
				_this.session.hangup();
				return false;
			}
			var grammar_object = _this.grammar_hash[_this.grammar_name];
			
			if (!grammar_object) {
				console_log("error", "Can't find grammar for " + _this.grammar_name + "\n");
				_this.session.hangup();
				return false;
			}

			if (speech_type == "begin-speaking") {
				if (grammar_object.halt) {
					return false;
				}
			} else {
				var body = inputEvent.getBody();
				var interp = new XML(body); 

				_this.lastDetect = body;
				
				if (_this.debug) {
					console_log("debug", "----XML:\n" + body + "\n");
					console_log("debug", "----Heard [" + interp.input + "]\n");
					console_log("debug", "----Hit score " + interp.@score + "/" + grammar_object.min_score + "/" + grammar_object.confirm_score + "\n");
				}
				
				if (interp.@score >= grammar_object.min_score) {
					if (interp.@score < grammar_object.confirm_score) {
						rv.push("_confirm_");
					}

					eval("xo = interp." + grammar_object.obj_path + ";");
					for (x = 0; x < xo.length(); x++) {
						rv.push(xo[x]);
					}
				} else {
					rv.push("_no_idea_");
				}

				console_log("debug", "dammit: " + rv + "\n");
				delete interp;
				return rv;
			}
		}
	}
} 

/* Constructor for SpeechObtainer Class (Class to collect data from a SpeechDetect Class) */
function SpeechObtainer(asr, req, wait_time) {

	this.items = new Array();
	this.collected_items = new Array();
	this.index = 0;
	this.collected_index = 0;
	this.req = req;
	this.tts_eng = undefined;
	this.tts_voice = false;
	this.asr = asr;
	this.top_sound = false;
	this.add_sound = false;
	this.dup_sound = false;
	this.bad_sound = false;
	this.needConfirm = false;
	this.grammar_name = false;
	this.audio_base = asr.audio_base;
	this.audio_ext = asr.audio_ext;
	this.tts_eng = asr.tts_eng;
	this.tts_voice = asr.tts_voice;
	this.debug = asr.debug;

	if (!req) {
		req = 1;
	}

	if (!wait_time) { 
		wait_time = 5000;
	}

	this.waitTime = wait_time + 0;

	/* Set the TTS info*/
	this.setTTS = function (tts_eng, tts_voice) {
		this.tts_eng = tts_eng;
		this.tts_voice = tts_voice;		
	}

	/* Set the audio base */
	this.setAudioBase = function (audio_base) {
		this.audio_base = audio_base;
	}

	/* Set the audio extension */
	this.setAudioExt= function (audio_ext) {
		this.audio_ext = audio_ext;
	}

	/* Set the grammar to use */
	this.setGrammar = function (grammar_name, path, obj_path, min_score, confirm_score, halt) {
		var grammar_object = new Grammar(grammar_name, path, obj_path, min_score, confirm_score, halt);
		this.asr.addGrammar(grammar_object);
		this.grammar_name = grammar_name;
	}

	/* Set the top audio file or tts for the collection */
	this.setTopSound = function (top_sound) {
		this.top_sound = top_sound;		
	}

	/* Set the audio file or tts for misunderstood input */
	this.setBadSound = function (bad_sound) {
		this.bad_sound = bad_sound;		
	}

	/* Set the audio file or tts for duplicate input */
	this.setDupSound = function (dup_sound) {
		this.dup_sound = dup_sound;		
	}

	/* Set the audio file or tts for accepted input */
	this.setAddSound = function (add_sound) {
		this.add_sound = add_sound;		
	}

	/* Add acceptable items (comma sep list)*/
	this.addItem = function(item) {
		ia = item.split(",");
		var x;
		for (x = 0; x < ia.length; x++) {
			this.items[this.index++] = ia[x];
		}
	}

	/* Add a regex */
	this.addRegEx = function(item) {
		this.items[this.index++] = item;
	}

	/* Reset the object and delete all collect items */
	this.reset = function() {
		this.collected_index = 0;
		delete this.collected_items;
		this.collected_items = new Array();
	}

	/* Stream a file, collecting input */
	this.streamFile = function(str) {
		var rv;
		if (!str) {
			console_log("error", "No file specified!\n");
			return;
		}
		files = str.split(",");
		for( x = 0; x < files.length; x++) {
			if (!files[x] || files[x] == "noop") {
				continue;
			}
			rv = this.asr.session.streamFile(this.audio_base + files[x] + this.audio_ext , "", this.asr.onInput, this.asr);
			if (rv) {
				break;
			}
		}

		return rv;
	}

	/* Speak some text, collecting input */
	this.speak = function(str) {
		return this.asr.session.speak(this.tts_eng, this.tts_voice, str, this.asr.onInput, this.asr);
	}

	/* Process collected input */
	this.react = function(say_str, play_str) {
		var rv;


		if (!rv) {
			rv = this.asr.session.collectInput(this.asr.onInput, this.asr, 500);
		}
		if (!rv) {
			this.asr.resume();
			if (this.tts_eng && this.tts_voice) {
				rv = this.speak(say_str);
			} else {
				rv = this.streamFile(play_str);
			}
		}

		if (!rv) {
			rv = this.asr.session.collectInput(this.asr.onInput, this.asr, 500);
		}
		
		if (rv && !rv[0]) {
			rv = false;
		}

		return rv;
	}

	/* Collect input */
	this.run = function() {
		var rv;
		var hit;
		var dup;

		if (this.collected_index) {
			this.reset();
		}
		
		if (!this.grammar_name) {
			console_log("error", "No Grammar name!\n");
			this.session.hangup();
			return false;
		}

		this.asr.setGrammar(this.grammar_name);

		while(this.asr.session.ready() && this.collected_index < this.req) {
			var x;
			this.needConfirm = false;
			if (!rv) {
				rv = this.react(this.top_sound, this.top_sound);
			}
			if (!rv) {
				this.asr.resume();
				rv = this.asr.session.collectInput(this.asr.onInput, this.asr, this.waitTime);
			}
			hit = false;
			if (rv) {
				var items = rv;
				rv = undefined;
				for (y = 0; y < items.length; y++) {
					if (items[y] == "_no_idea_") {
						if (this.debug) {
							console_log("debug", "----We don't understand this\n");
						}
						break;
					}
					if (items[y] == "_confirm_") {
						this.needConfirm = true;
						if (this.debug) {
							console_log("debug", "----We need to confirm this one\n");
						}
						continue;
					}
					
					for(x = 0 ; x < this.index; x++) {
						if (this.debug) {
							console_log("debug", "----Testing [" + y + "] [" + x + "] " + items[y] + " =~ [" + this.items[x] + "]\n");
						}
						var re = new RegExp(this.items[x]);
						match = re.exec(items[y]);
						if (match) {
							for (i = 0; i < match.length; i++) {
								dup = false;
								for(z = 0; z < this.collected_items.length; z++) {
									if (this.collected_items[z] == match[i]) {
										dup = true;
										break;
									}
								}
								if (dup) {
									if (this.dup_sound) {
										rv = this.react(this.dup_sound + " " + match[i], this.dup_sound + "," + match[i]);
									}
								} else {
									if (this.debug) {
										console_log("debug", "----Adding " + match[i] + "\n");
									}
									this.collected_items[this.collected_index++] = match[i];
									hit = true;
									if (this.add_sound) {
										rv = this.react(this.add_sound + " " + match[i], this.add_sound + "," + match[i]);
									}
								}
							}
						}
					}
				}
			}

			if (!rv) {
				rv = this.asr.session.collectInput(this.asr.onInput, this.asr, 1000);
			}

			if (!rv && !hit && !dup) {
				rv = this.react(this.bad_sound, this.bad_sound);
			}
		}

		return this.collected_items;

	}
}
