// (c) MIT 2003.  All rights reserved.

// -*-Mode: Java-*- 
// $Id: Say.java,v 1.6 2003/03/06 00:40:40 moltmans Exp $
// Say.java -- 
// Author: Michael Oltmans <moltmans@ai.mit.edu> 
// Created: <Mon Aug 23 12:17:38 1999> 
// Time-stamp: <2001-03-05 18:38:34 moltmans> 
package rec.speech;

import java.io.*;
import java.util.Locale;
import java.util.*;
import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;
import javax.speech.*;
import javax.speech.synthesis.*;

public class Say {

  private static Say m_sayer = null;

  // The voice type should be an attribute, but it is ignored
  // anyway...
  private final static String m_roomSpeakingVoice = "English-American: Wade (Adult Male)";
  private static Synthesizer m_synthesizer;
  private static String m_spokenText;
  private Say m_me = this;
  private boolean m_forMe = true;

  private Thread m_life = null;

  public static synchronized Say getSay()
  {
    if ( m_sayer == null )
      m_sayer = new Say();

    return m_sayer;
  }

  /*
   * Create a speech synthesizer.  The Synthesizer is declared static
   * because you only want one per machine.  This checks to see if it
   * exists yet and creates one if necessary.
   */
  private Say () 
  {
    // Start the thread for synchronized speech output
    m_life = new Thread() {
      public void run() {
	looper();
      }
    };
    m_life.start();

    if (m_synthesizer == null) {

      try {
	  //SynthesizerModeDesc synthMode = new SynthesizerModeDesc();
	  //	  synthMode.setLocale(Locale.ENGLISH);
	  //synthesizer = Central.createSynthesizer(synthMode);
	  m_synthesizer = Central.createSynthesizer(null);
	  if (m_synthesizer!=null) 
	    {
	      m_synthesizer.allocate();
	      SynthesizerProperties synthProp =
		(SynthesizerProperties) m_synthesizer.getEngineProperties();

	      // set up voice
	      Voice theVoice = new Voice();
	      theVoice.setStyle("Business");
	      // theVoice.setName("BT TTS Male Voice");
	      // theVoice.setName("English-American: Flo (Adult Female)");
	      synthProp.setVoice(theVoice);
	    }
	  else 
	    {
	      System.out.println("I can't allocate the speech synthesizer!!");
	    }           
	
	  // Don't speak here.  Speech recognition won't work if the
	  // speech synthesizer has already said something before it
	  // starts.  IBM knows about this bug.
	  // say("Hello, the synthesizer is running");   

	} catch (Exception e) {
	  e.printStackTrace();
	  
	}
      }
  }

  public synchronized void looper () 
  {
    while (true) {
      try {
	System.out.println("Starting wait");
	wait(); // Wait for a request to speak
	if (! m_forMe) {
	  m_forMe = true;
	  continue;
	}
	else {
	  m_forMe = false;
	}
      } catch (InterruptedException ex) {
	ex.printStackTrace();
      }
      if (m_synthesizer!=null) {
	try {
	  m_synthesizer.speak(m_spokenText, notifyWhenDone);
	}
	catch (JSMLException e) {
	  e.printStackTrace();
	}
      }
      else {
	System.out.println("I SAY: " + m_spokenText);
      }
    }
  }

  public synchronized void saySync (String text) 
  {
    silentPrint( text );
    try {
      m_spokenText = text;
      notifyAll();
      wait();
      System.out.println("Done speaking");
    }
    catch (InterruptedException ex) {
      ex.printStackTrace();
    }
  }

  private synchronized void doNotify () {
    notifyAll();
  }

  //
  // This is a listener that exits the application when the
  // speak method it is used with finishes speaking.
  //
  private SpeakableListener notifyWhenDone = new SpeakableAdapter() {
    public void speakableEnded(SpeakableEvent e) {
      System.out.println("Doing notify");
      m_me.doNotify();
    }
  };

  public static synchronized void say (String text) 
  {
    silentPrint( text );
    if (m_synthesizer!=null) {
      try {
	m_synthesizer.speak(text, null);
      }
      catch (JSMLException e) {
	e.printStackTrace();
      }
    } else {
      System.out.println(text);
    }
  }
  
  private static void silentPrint( String s )
  {
    System.err.println( "SAYING *" );

    StringTokenizer st = new StringTokenizer( s, " ", true );

    while( st.hasMoreTokens() )
    {
      String tt = "";
      while( st.hasMoreTokens() && (tt.length() < 60 ) )
      {
	String ntxt = st.nextToken();
	if ( !tt.equals( "" ) || !ntxt.equals( " " ) )
	  tt += ntxt;
      }
      System.err.println( "       * " + tt );
    }
  } 

  /* This wav file player is here to minimize destructive interference
     between the speech synthesizer and the sound player, only one of
     which can be active at a time.  This should really have a call to
     the pause and resume methods of the synthesizer, but those
     haven't been implemented yet by IBM.

     This works by calling a DOS wav file player, because the java
     audio API doesn't yet have an associated implementation.  This is
     not guaranteed to work with wav files whose duration is longer
     than the one in the HOW_LONG_SHOULD_I_SLEEP variable.  If there
     are any problems, try increasing the value.
     */

  private static String SOUND_DIRECTORY = null;
  //"j:\\hal\\lib\\sound\\DOS\\";
  private static String SOUND_PROGRAM = null;
  //"j:\\hal\\lib\\sound\\dos\\play.exe";

  public static int HOW_LONG_SHOULD_I_SLEEP = 700;

  public static void setDirs( String soundDir, String soundPlayer ) 
  {
    SOUND_DIRECTORY = soundDir;
    SOUND_PROGRAM = soundPlayer;
  }
  
  
  public static synchronized void playWavSync (String wavFile) {
    playWav(wavFile);
  }

  public static synchronized void playWav (String wavFile) 
  {
    if( SOUND_PROGRAM == null )
      return;
    //    String executable = SOUND_PROGRAM + " " + SOUND_DIRECTORY +
    //          wavFile;
    File file = new File(SOUND_DIRECTORY, wavFile);
    
    String[] executable = {SOUND_PROGRAM, file.getPath()};
    
    // This has been remove from JSAPI
    //    synthesizer.waitQueueEmpty();
    //    System.out.println("Attempting to play sound");
    // synthesizer.pause();
    try {
      //      System.out.println("I am playing " + wavFile );
      Process soundproc = Runtime.getRuntime().exec( executable );
    }
    catch (Exception e) {
      System.out.println( "Error playing sound--process failed" );
      e.printStackTrace();
    }
    try {
      Thread.sleep(HOW_LONG_SHOULD_I_SLEEP);
    }
    catch ( InterruptedException e ) {}
    //    System.out.println("Done waiting for sound to finish");
    // synthesizer.resume();
  }

  public static void main (String argv[]) {
    guiTest();
    return;

//      Say synth = new Say();
//      String result = "";

//      System.out.println( argv.length );
//      if( argv.length == 0 ) {
//        result = "Hello, welcome to the Rec System";
//      }
//      else {
//        result = argv[0] + " ";
//        for( int i=0; i<argv.length; i++) {
//  	result += argv[i] + " ";
//        }
//      }
    
//      synth.saySync( result );
//      // IBM hasn't implemented these yet
//      //synth.waitQueueEmpty();
//      //synth.deallocate();
//      System.exit(0);
  }

  public static void guiTest() 
  {
    JFrame f = new JFrame( "Talker" );
    final Say synth = new Say();
    final JTextField field;
    field = new JTextField( 70 );
    
    ActionListener al = new ActionListener() {
      public void actionPerformed( ActionEvent e ) {
	synth.saySync( field.getText() );
	field.setText( "" );
      }
    };

    field.addActionListener( al );

    JButton button = new JButton( "Send" );
    ActionListener bl = new ActionListener() {
      public void actionPerformed( ActionEvent e ) {
	synth.saySync( field.getText() );
	field.setText( "" );
      }
    };
    button.addActionListener( bl );

    f.getContentPane().setLayout( new GridLayout(0,1) );
    f.getContentPane().add( new JLabel( "Enter the utterance:" ) );
    f.getContentPane().add( field );
    f.getContentPane().add( button );
    f.pack();
    f.show();
  }

  public void finalize () {
    System.out.println("Deallocating...");
    //    synthesizer.deallocate();
  } 
}

