//! annyang
//! version : 1.5.0
//! author  : Tal Ater @TalAter
//! license : MIT
//! https://www.TalAter.com/annyang/

(function (undefined) {
  "use strict";

  /**
   * # Quick Tutorial, Intro and Demos
   *
   * The quickest way to get started is to visit the [annyang homepage](https://www.talater.com/annyang/).
   *
   * For a more in-depth look at annyang, read on.
   *
   * # API Reference
   */

  // Save a reference to the global object (window in the browser)
  var root = this;

  // Get the SpeechRecognition object, while handling browser prefixes
  var SpeechRecognition = root.SpeechRecognition ||
                          root.webkitSpeechRecognition ||
                          root.mozSpeechRecognition ||
                          root.msSpeechRecognition ||
                          root.oSpeechRecognition;

  // Check browser support
  // This is done as early as possible, to make it as fast as possible for unsupported browsers
  if (!SpeechRecognition) {
    root.annyang = null;
    return undefined;
  }

  var commandsList = [];
  var recognition;
  var callbacks = { start: [], error: [], end: [], result: [], resultMatch: [], resultNoMatch: [], errorNetwork: [], errorPermissionBlocked: [], errorPermissionDenied: [] };
  var autoRestart;
  var lastStartedAt = 0;
  var debugState = false;
  var debugStyle = 'font-weight: bold; color: #00f;';

  // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.
  var optionalParam = /\s*\((.*?)\)\s*/g;
  var optionalRegex = /(\(\?:[^)]+\))\?/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#]/g;
  var commandToRegExp = function(command) {
    command = command.replace(escapeRegExp, '\\$&')
                  .replace(optionalParam, '(?:$1)?')
                  .replace(namedParam, function(match, optional) {
                    return optional ? match : '([^\\s]+)';
                  })
                  .replace(splatParam, '(.*?)')
                  .replace(optionalRegex, '\\s*$1?\\s*');
    return new RegExp('^' + command + '$', 'i');
  };

  // This method receives an array of callbacks to iterate over, and invokes each of them
  var invokeCallbacks = function(callbacks) {
    callbacks.forEach(function(callback) {
      callback.callback.apply(callback.context);
    });
  };

  var initIfNeeded = function() {
    if (!isInitialized()) {
      root.annyang.init({}, false);
    }
  };

  var isInitialized = function() {
    return recognition !== undefined;
  };

  function normalise( word )
  {
  	var x=" "+word.toLowerCase().replace('.','').replace('?','').replace(',','')+" ";
  	x=x.replace(" busch gardens "," bushwick ").replace(" busch light "," bushwick ").replace(" in bushwick "," ");
  	x=x.replace(" bushkill "," bushwick ");
  	x=x.replace(" the "," ").replace(" is "," ").replace(" are "," ");
  	x=x.replace(" wikipedia entry "," wikipedia ");
  	x=x.replace(" of "," ");
  	x=x.replace(" and "," ");
  	x=x.replace(" an "," ");
  	x=x.replace(" me "," ");
  	x=x.replace(" some "," ");
  	x=x.replace(" please "," ");
  	x=x.replace(" can i see "," show me ");
  	x=x.replace(" list "," show me ");
  	x=x.replace(" check out "," ");
  	x=x.replace(" checkout "," ");
  	x=x.replace(" about "," ");
  	x=x.replace(" tell me "," show me ");
  	x=x.replace(" show me "," ");
  	x=x.replace(" what "," ");
  	x=x.replace(" whats "," ");
  	x=x.replace(" which "," ");
  	x=x.replace(" where "," ");
  	x=x.replace(" places "," ");
  	x=x.replace(" place "," ");
  	x=x.replace(" can "," ");
  	x=x.replace(" get "," ");
  	x=x.replace(" got "," ");
  	x=x.replace(" best "," ");
  	x=x.replace(" good "," ");
  	x=x.replace(" nice "," ");
  	x=x.replace(" yummy "," ");
  	x=x.replace(" cool "," ");
  	x=x.replace(" hip "," ");
  	x=x.replace(" neat "," ");
  	x=x.replace(" convenient "," ");
  	x=x.replace(" i "," ");
  	x=x.replace(" look "," ");
  	x=x.replace(" app "," up ");
  	x=x.replace(" up "," ");
  	x=x.replace(" like "," ");
  	x=x.replace(" thing "," ");
  	x=x.replace(" things "," ");
  	x=x.replace(" shops "," ");
  	x=x.replace(" shop "," ");
  	x=x.replace(" catch a "," ");
  	x=x.replace(" watch a "," ");
  	x=x.replace(" to "," ");
  	x=x.replace(" spot "," ");
  	x=x.replace(" spots "," ");
  	x=x.replace(" any "," ");
  	x=x.replace(" there "," ");
  	x=x.replace(" how should "," ");
  	x=x.replace(" from bushwick "," ");
  	x=x.replace(" a "," ");
  	x=x.replace(" how should "," ");
  	x=x.replace(" how "," ");
  	x=x.replace(" in area "," ");
  	x=x.replace(" area "," ");
  	x=x.replace(" find "," ");
  	x=x.replace(" go "," ");
  	x=x.replace(" it "," ");
  	x=x.replace(" isit "," ");
  	x=x.replace(" jfk airport "," jfk ");
  	x=x.replace(" john f kennedy airport "," jfk ");
  	x=x.replace(" john f kennedy "," jfk ");
  	x=x.replace(" lga airport "," lga ");
  	x=x.replace(" la guardia airport "," lga ");
  	x=x.replace(" in "," ");
  	x=x.replace(" show "," ");
  	x=x.replace(" show "," ");
  	x=x.replace(" around here "," ");
  	x=x.replace(" going on "," ");
  	x=x.replace(" closest "," ");
  	x=x.trim();
  	console.log(x);
  	return stemmer(x);
  }
  
  root.annyang = {

    /**
     * Initialize annyang with a list of commands to recognize.
     *
     * ### Examples:
     *
     *     var commands = {'hello :name': helloFunction};
     *     var commands2 = {'hi': helloFunction};
     *
     *     // initialize annyang, overwriting any previously added commands
     *     annyang.init(commands, true);
     *     // adds an additional command without removing the previous commands
     *     annyang.init(commands2, false);
     *
     * As of v1.1.0 it is no longer required to call init(). Just start() listening whenever you want, and addCommands() whenever, and as often as you like.
     *
     * @param {Object} commands - Commands that annyang should listen to
     * @param {Boolean} [resetCommands=true] - Remove all commands before initializing?
     * @method init
     * @deprecated
     * @see [Commands Object](#commands-object)
     */
    init: function(commands, resetCommands) {

      // resetCommands defaults to true
      if (resetCommands === undefined) {
        resetCommands = true;
      } else {
        resetCommands = !!resetCommands;
      }

      // Abort previous instances of recognition already running
      if (recognition && recognition.abort) {
        recognition.abort();
      }

      // initiate SpeechRecognition
      recognition = new SpeechRecognition();

      // Set the max number of alternative transcripts to try and match with a command
      recognition.maxAlternatives = 5;

      // In HTTPS, turn off continuous mode for faster results.
      // In HTTP,  turn on  continuous mode for much slower results, but no repeating security notices
      recognition.continuous = root.location.protocol === 'http:';

      // Sets the language to the default 'en-US'. This can be changed with annyang.setLanguage()
      recognition.lang = 'en-US';

      recognition.onstart   = function()      { invokeCallbacks(callbacks.start); };

      recognition.onerror   = function(event) {
        invokeCallbacks(callbacks.error);
        switch (event.error) {
        case 'network':
          invokeCallbacks(callbacks.errorNetwork);
          break;
        case 'not-allowed':
        case 'service-not-allowed':
          // if permission to use the mic is denied, turn off auto-restart
          autoRestart = false;
          // determine if permission was denied by user or automatically.
          if (new Date().getTime()-lastStartedAt < 200) {
            invokeCallbacks(callbacks.errorPermissionBlocked);
          } else {
            invokeCallbacks(callbacks.errorPermissionDenied);
          }
          break;
        }
      };

      recognition.onend     = function() {
        invokeCallbacks(callbacks.end);
        // annyang will auto restart if it is closed automatically and not by user action.
        if (autoRestart) {
          // play nicely with the browser, and never restart annyang automatically more than once per second
          var timeSinceLastStart = new Date().getTime()-lastStartedAt;
          if (timeSinceLastStart < 1000) {
            setTimeout(root.annyang.start, 1000-timeSinceLastStart);
          } else {
            root.annyang.start();
          }
        }
      };

      recognition.onresult  = function(event) {
    	
        invokeCallbacks(callbacks.result);
        var results = event.results[event.resultIndex];
        var commandText;
        // go over each of the 5 results and alternative results received (we've set maxAlternatives to 5 above)
        for (var i = 0; i<results.length; i++) {
          // the text recognized
          commandText = results[i].transcript.trim();
          
      	  var commandTextNormalised=normalise(commandText);
          if (debugState) {
            root.console.log('Speech recognized: %c'+commandText, debugStyle);
          }

          // try and match recognized text to one of the commands on the list
          for (var j = 0, l = commandsList.length; j < l; j++) {
            var result = commandsList[j].command.exec(commandText);
            console.log(result);
            if(!result)
            	var result = commandsList[j].command.exec(commandTextNormalised);
            if (result) {
              var parameters = result.slice(1);
              if (debugState) {
                root.console.log('command matched: %c '+commandsList[j].originalPhrase+' => '+commandsList[j].transformedPhrase, debugStyle);
                if (parameters.length) {
                  root.console.log('with parameters', parameters);
                }
              }
              // execute the matched command
              commandsList[j].callback.apply(this, parameters);
              invokeCallbacks(callbacks.resultMatch);
              return true;
            }
          }
        }
        if(results.length>0)
              noMatch(results[0].transcript.trim());
        invokeCallbacks(callbacks.resultNoMatch);
        return false;
      };

      // build commands list
      if (resetCommands) {
        commandsList = [];
      }
      if (commands.length) {
        this.addCommands(commands);
      }
    },

    /**
     * Start listening.
     * It's a good idea to call this after adding some commands first, but not mandatory.
     *
     * Receives an optional options object which supports the following options:
     * - `autoRestart` (Boolean, default: true) Should annyang restart itself if it is closed indirectly, because of silence or window conflicts?
     * - `continuous`  (Boolean, default: undefined) Allow forcing continuous mode on or off. Annyang is pretty smart about this, so only set this if you know what you're doing.
     *
     * ### Examples:
     *     // Start listening, don't restart automatically
     *     annyang.start({ autoRestart: false });
     *     // Start listening, don't restart automatically, stop recognition after first phrase recognized
     *     annyang.start({ autoRestart: false, continuous: false });
     *
     * @param {Object} [options] - Optional options.
     * @method start
     */
    start: function(options) {
      initIfNeeded();
      options = options || {};
      if (options.autoRestart !== undefined) {
        autoRestart = !!options.autoRestart;
      } else {
        autoRestart = true;
      }
      if (options.continuous !== undefined) {
        recognition.continuous = !!options.continuous;
      }

      lastStartedAt = new Date().getTime();
      try{
    	  recognition.start();
      }
	catch(err) {
    	console.log(err);
	}
    },

    /**
     * Stop listening.
     *
     * @method abort
     */
    abort: function() {
      autoRestart = false;
      if (isInitialized) {
      try{
    	  recognition.abort();
      }
	catch(err) {
    	console.log(err);
	}
      }
    },

    /**
     * Turn on output of debug messages to the console. Ugly, but super-handy!
     *
     * @param {Boolean} [newState=true] - Turn on/off debug messages
     * @method debug
     */
    debug: function(newState) {
      if (arguments.length > 0) {
        debugState = !!newState;
      } else {
        debugState = true;
      }
    },

    /**
     * Set the language the user will speak in. If this method is not called, defaults to 'en-US'.
     *
     * @param {String} language - The language (locale)
     * @method setLanguage
     * @see [Languages](#languages)
     */
    setLanguage: function(language) {
      initIfNeeded();
      recognition.lang = language;
    },

    /**
     * Add commands that annyang will respond to. Similar in syntax to init(), but doesn't remove existing commands.
     *
     * ### Examples:
     *
     *     var commands = {'hello :name': helloFunction, 'howdy': helloFunction};
     *     var commands2 = {'hi': helloFunction};
     *
     *     annyang.addCommands(commands);
     *     annyang.addCommands(commands2);
     *     // annyang will now listen to all three commands
     *
     *
     * @param {Object} commands - Commands that annyang should listen to
     * @method addCommands
     * @see [Commands Object](#commands-object)
     */
    addCommands: function(commands) {
      var cb,
          command;

      initIfNeeded();

      for (var phrase in commands) {
        if (commands.hasOwnProperty(phrase)) {
          cb = root[commands[phrase]] || commands[phrase];
          if (typeof cb !== 'function') {
            continue;
          }
          //convert command to regex
      	  var phrase1=phrase;
          command = commandToRegExp(phrase1);
		  console.log(command);
          commandsList.push({ command: command, callback: cb, originalPhrase: phrase, transformedPhrase: phrase1 });
        }
      }
      if (debugState) {
        root.console.log('Commands successfully loaded: %c'+commandsList.length, debugStyle);
      }
    },
    addNormalisedCommands: function(commands) {
      var cb,
          command;

      initIfNeeded();

      for (var phrase in commands) {
        if (commands.hasOwnProperty(phrase)) {
          cb = root[commands[phrase]] || commands[phrase];
          if (typeof cb !== 'function') {
            continue;
          }
          //convert command to regex
      	  var phrase1=normalise(phrase);
          command = commandToRegExp(phrase1);
		  console.log(command);
          commandsList.push({ command: command, callback: cb, originalPhrase: phrase, transformedPhrase: phrase1 });
        }
      }
      if (debugState) {
        root.console.log('Commands successfully loaded: %c'+commandsList.length, debugStyle);
      }
    },

    /**
     * Remove existing commands. Called with a single phrase, array of phrases, or methodically. Pass no params to remove all commands.
     *
     * ### Examples:
     *
     *     var commands = {'hello': helloFunction, 'howdy': helloFunction, 'hi': helloFunction};
     *
     *     // Remove all existing commands
     *     annyang.removeCommands();
     *
     *     // Add some commands
     *     annyang.addCommands(commands);
     *
     *     // Don't respond to hello
     *     annyang.removeCommands('hello');
     *
     *     // Don't respond to howdy or hi
     *     annyang.removeCommands(['howdy', 'hi']);
     *
     * @param {String|Array|Undefined} [commandsToRemove] - Commands to remove
     * @method removeCommands
     */
    removeCommands: function(commandsToRemove) {
      if (commandsToRemove === undefined) {
        commandsList = [];
        return;
      }
      commandsToRemove = Array.isArray(commandsToRemove) ? commandsToRemove : [commandsToRemove];
      commandsList = commandsList.filter(function(command) {
        for (var i = 0; i<commandsToRemove.length; i++) {
          if (commandsToRemove[i] === command.originalPhrase) {
            return false;
          }
        }
        return true;
      });
    },

    /**
     * Add a callback function to be called in case one of the following events happens:
     *
     * start, error, end, result, resultMatch, resultNoMatch, errorNetwork, errorPermissionBlocked, errorPermissionDenied.
     *
     * ### Examples:
     *
     *     annyang.addCallback('error', function () {
     *       $('.myErrorText').text('There was an error!');
     *     });
     *
     *     // pass local context to a global function called notConnected
     *     annyang.addCallback('errorNetwork', notConnected, this);
     *
     * @param {String} type - Name of event that will trigger this callback
     * @param {Function} callback - The function to call when event is triggered
     * @param {Object} [context] - Optional context for the callback function
     * @method addCallback
     */
    addCallback: function(type, callback, context) {
      if (callbacks[type]  === undefined) {
        return;
      }
      var cb = root[callback] || callback;
      if (typeof cb !== 'function') {
        return;
      }
      callbacks[type].push({callback: cb, context: context || this});
    }
  };

}).call(this);

/**
 * # Good to Know
 *
 * ## Commands Object
 *
 * Both the [init()]() and addCommands() methods receive a `commands` object.
 *
 * annyang understands commands with `named variables`, `splats`, and `optional words`.
 *
 * * Use `named variables` for one word arguments in your command.
 * * Use `splats` to capture multi-word text at the end of your command (greedy).
 * * Use `optional words` or phrases to define a part of the command as optional.
 *
 * ### Examples:
 *
 *     <script>
 *     var commands = {
 *       // annyang will capture anything after a splat (*) and pass it to the function.
 *       // e.g. saying "Show me Batman and Robin" will call showFlickr('Batman and Robin');
 *       'show me *term': showFlickr,
 *
 *       // A named variable is a one word variable, that can fit anywhere in your command.
 *       // e.g. saying "calculate October stats" will call calculateStats('October');
 *       'calculate :month stats': calculateStats,
 *
 *       // By defining a part of the following command as optional, annyang will respond
 *       // to both: "say hello to my little friend" as well as "say hello friend"
 *       'say hello (to my little) friend': greeting
 *     };
 *
 *     var showFlickr = function(term) {
 *       var url = 'http://api.flickr.com/services/rest/?tags='+tag;
 *       $.getJSON(url);
 *     }
 *
 *     var calculateStats = function(month) {
 *       $('#stats').text('Statistics for '+month);
 *     }
 *
 *     var greeting = function() {
 *       $('#greeting').text('Hello!');
 *     }
 *     </script>
 *
 * ## Languages
 *
 * While there isn't an official list of supported languages (cultures? locales?), here is a list based on [anecdotal evidence](http://stackoverflow.com/a/14302134/338039).
 *
 * * Afrikaans `af`
 * * Basque `eu`
 * * Bulgarian `bg`
 * * Catalan `ca`
 * * Arabic (Egypt) `ar-EG`
 * * Arabic (Jordan) `ar-JO`
 * * Arabic (Kuwait) `ar-KW`
 * * Arabic (Lebanon) `ar-LB`
 * * Arabic (Qatar) `ar-QA`
 * * Arabic (UAE) `ar-AE`
 * * Arabic (Morocco) `ar-MA`
 * * Arabic (Iraq) `ar-IQ`
 * * Arabic (Algeria) `ar-DZ`
 * * Arabic (Bahrain) `ar-BH`
 * * Arabic (Lybia) `ar-LY`
 * * Arabic (Oman) `ar-OM`
 * * Arabic (Saudi Arabia) `ar-SA`
 * * Arabic (Tunisia) `ar-TN`
 * * Arabic (Yemen) `ar-YE`
 * * Czech `cs`
 * * Dutch `nl-NL`
 * * English (Australia) `en-AU`
 * * English (Canada) `en-CA`
 * * English (India) `en-IN`
 * * English (New Zealand) `en-NZ`
 * * English (South Africa) `en-ZA`
 * * English(UK) `en-GB`
 * * English(US) `en-US`
 * * Finnish `fi`
 * * French `fr-FR`
 * * Galician `gl`
 * * German `de-DE`
 * * Hebrew `he`
 * * Hungarian `hu`
 * * Icelandic `is`
 * * Italian `it-IT`
 * * Indonesian `id`
 * * Japanese `ja`
 * * Korean `ko`
 * * Latin `la`
 * * Mandarin Chinese `zh-CN`
 * * Traditional Taiwan `zh-TW`
 * * Simplified China zh-CN `?`
 * * Simplified Hong Kong `zh-HK`
 * * Yue Chinese (Traditional Hong Kong) `zh-yue`
 * * Malaysian `ms-MY`
 * * Norwegian `no-NO`
 * * Polish `pl`
 * * Pig Latin `xx-piglatin`
 * * Portuguese `pt-PT`
 * * Portuguese (Brasil) `pt-BR`
 * * Romanian `ro-RO`
 * * Russian `ru`
 * * Serbian `sr-SP`
 * * Slovak `sk`
 * * Spanish (Argentina) `es-AR`
 * * Spanish (Bolivia) `es-BO`
 * * Spanish (Chile) `es-CL`
 * * Spanish (Colombia) `es-CO`
 * * Spanish (Costa Rica) `es-CR`
 * * Spanish (Dominican Republic) `es-DO`
 * * Spanish (Ecuador) `es-EC`
 * * Spanish (El Salvador) `es-SV`
 * * Spanish (Guatemala) `es-GT`
 * * Spanish (Honduras) `es-HN`
 * * Spanish (Mexico) `es-MX`
 * * Spanish (Nicaragua) `es-NI`
 * * Spanish (Panama) `es-PA`
 * * Spanish (Paraguay) `es-PY`
 * * Spanish (Peru) `es-PE`
 * * Spanish (Puerto Rico) `es-PR`
 * * Spanish (Spain) `es-ES`
 * * Spanish (US) `es-US`
 * * Spanish (Uruguay) `es-UY`
 * * Spanish (Venezuela) `es-VE`
 * * Swedish `sv-SE`
 * * Turkish `tr`
 * * Zulu `zu`
 *
 * ## Developing
 *
 * Prerequisities: node.js
 *
 * First, install dependencies in your local annyang copy:
 *
 *     npm install
 *
 * Make sure to run the default grunt task after each change to annyang.js. This can also be done automatically by running:
 *
 *     grunt watch
 *
 * You can also run a local server for testing your work with:
 *
 *     grunt dev
 *
 * Point your browser to `https://localhost:8443/demo/` to see the demo page.
 * Since it's using self-signed certificate, you might need to click *"Proceed Anyway"*.
 *
 */