/* 
 * 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;

    }
}