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

////////////////////////////////////////////////////////////////////////////////
//
// FILE:	RecSystem.java
//
// AUTHOR:	Manoj D. Muzumdar
//		archon@ai.mit.edu
//
//              Adapted from code written by Luke Weisman
//              luke@ai.mit.edu
//
// DATE:	Jun 98
//
// COPYRIGHT:	Massachusetts Institute of Technology
//		All rights reserved.
//
// PURPOSE:     User interface component for drawing recognition
//
// HISTORY:     Jun 98  archon  created
//
//              Nov 98  archon
//              Now aware of new rec.geo package.
//
////////////////////////////////////////////////////////////////////////////////

package rec.ui;

import edu.mit.sketch.toolkit.Timer;
import java.awt.*;
import java.awt.Image;
import java.awt.event.*;
import java.io.*;
import java.net.URL;
import java.util.*;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import rec.geo.*;
import rec.simulate.*;
import rec.speech.*;
import rec.sys.*;
import rec.sys.*;
import rec.util.*;
import rec.multi.MultiModal;

public class RecSystem
{
  private String[] m_Arguments;
  private int m_X;
  private int m_Y;

  private RecFrame m_Input;   // The mouse reader part.
  private Surface m_Surface;  // The processor part.

  private MultiModal m_multiModal; // the multimodal controller

  private static boolean m_platformIsWindows9x = 
    System.getProperty("os.name").equals("Windows 95") ||
    System.getProperty("os.name").equals("Windows 98");
    


  public static void main( String[] argv )
  {
    // this is a workaround for a bug in java 1.4 where file locking
    // over NFS on fails for the user's java preferences file, causing
    // annoying messages and a file descriptor leak.
    // (see http://developer.java.sun.com/developer/bugParade/bugs/4673298.html)
    
    // this workaround was posted in the java forums at
    // http://forum.java.sun.com/thread.jsp?forum=27&thread=231280

    // it sets the sync interval to > 600 hours so that java doesn't
    // attempt to lock the file.
    System.setProperty("java.util.prefs.syncInterval","2000000");


    // Runs the recognizer system.
    RecSystem rs = new RecSystem( argv );

    // now start it up
    rs.load();

    // Since we were started at the command line, exit when we close.
    rs.m_Input.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent e ) {
	Process sp = MultiModal.getSpeechProcess();
	if (sp !=null) sp.destroy();	  

	System.exit( 0 );
      }
    } );

    
  }

  /////////////////////////////////////////////////////////////////////////////

  public RecSystem( int x, int y )
  {
    this( new String[0], x, y );
  }

  public RecSystem( String[] args )
  {
    this( args, -1, -1 );
  }

  public RecSystem( String[] args, int x, int y )
  {
    m_Arguments = args;
    checkHelp( m_Arguments );

    m_X = x;
    m_Y = y;
  }

  public RecSystem( String start_file, int x, int y )
  {
    this( x, y );

    m_Arguments = new String[1];
    m_Arguments[0] = start_file;
  }

  /////////////////////////////////////////////////////////////////////////////

  public void load()
  {
    // This is the draw window.
    m_Input = new RecFrame( m_X, m_Y );

    // for closing the application
    m_Input.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent e ) {
	Console.writeln( "window close" );
	m_Surface.shutdown();
	m_Input.dispose();
	//	System.exit( 0 );
      }
    } );

    m_Surface = null;

    Console.writeln( "going to load modules" );

    // Do we have an .ini file to load up?
    if( loadBase( m_Arguments ) ) 
      {
	String base_name = getBase( m_Arguments );
	URL url = null;
	try {
	  url = RecIniFile.grabFile( base_name );
	}
	catch ( IOException e ){
	  Assert.error( "Trouble loading modules: " + base_name );
	}
	loadIniModule( url );
      } 
    else
      {
	// no, so make default surface
	loadIniModule( null );
      }

    // load up the listeners which monitor mouse activity.
    setupListeners();

    // add on any modules and recognizers from command line

    // make a module for possible command line recognizers
    BasicModule argmod = new BasicModule( "argument" );
    boolean argmodhit = false;

    // loading the modules
    for( int i = 0; i < m_Arguments.length; ++i ) {
      Console.indent();
	
      RecIniFile rif = null;
      String mname = m_Arguments[i];

      if ( mname.endsWith( ".ini" ) )
	continue;

      if( !mname.endsWith( ".mod" ) )
	mname += ".mod";
	
      try {

	// build the module...

	URL url = RecIniFile.grabFile( mname );
	rif = new RecIniFile( url, null );
	m_Surface = rif.initializeSurface( m_Input, m_Surface );
      }
      catch( IOException e ) {

	// oops.. must have been a recognizer
	Object o = DynamicConstructor.create( m_Arguments[i] ).create();
	    
	if( o == null ) {
	  Console.writeln( "unknown recognizer/module: " + m_Arguments[i] );
	  return;
	}
	    
	// add the recognizer to our module.
	argmod.add( (Recognizer)o );
	argmodhit = true;
      }

      Console.outdent();
    }

    // if we ever put anything into our module, put it on-line.
    if( argmodhit )
      m_Surface.addModule( argmod, EditModeManager.DEFAULT_MODE );

    // ada 4/1/03: initialize the multimodal connections if turned on
    if (m_Surface.getSystemProperty("multimodal", "").equalsIgnoreCase("on") ) {
      m_multiModal = new MultiModal(this);
      m_Surface.setMultiModal(m_multiModal);
    }
	  
    
    String width = m_Surface.getSystemProperty( "Width", "800" );
    String height = m_Surface.getSystemProperty( "Height", "600" );
    int w;
    int h;
    try {
      w = Integer.parseInt( width );
      h = Integer.parseInt( height );
    }
    catch( NumberFormatException nfe ) {
      Assert.note( "bad formed width (" + width +
		   ") or height (" + height + " )" );
      w = 800;
      h = 600;
    }

    m_Input.setSize( w, h );
    m_Input.show();

    Console.writeln( "Finished building up the surface." );
  } // end load()  

  
  /**
   * Output some help info, if args ask for help.
   */
  private void checkHelp( String[] args )
  {
    if( ( args.length == 1 ) && ( args[0].startsWith( "-h" ) ) ) {

      Console.writeln( "Help:" );
      Console.indent();
      Console.writeln( "java rec.ui.RecSystem [NO_DEFAULT | recog file] [modules|recognizers]" );
      Console.outdent();
      Console.writeln( "[NO_DEFAULT] = do not load recog.ini" );
      Console.writeln( "[recog file] = ends with .ini, instead of recog.ini" );
	
      //      System.exit( 1 );
    }
  }


  /**
   * Load up ini module, attach the corrisponding surface to the
   * screen, etc.
   */
  public void loadIniModule( URL base_url )
  {
    if ( base_url == null )
    {
      m_Surface = new Surface();
    }
    else
    {
      try {
	RecIniFile rif = null;
	rif = new RecIniFile( base_url, EditModeManager.DEFAULT_MODE );
	m_Surface = rif.initializeSurface( m_Input, m_Surface );

	// this is to make sure all the appropriate grammars and
	// recognizers are ready to go
	//	  m_Surface.leaveEditMode( EditModeManager.DEFAULT_MODE );
	m_Surface.addMajorMode( EditModeManager.DEFAULT_MODE );
	m_Surface.enterEditMode( EditModeManager.DEFAULT_MODE );

	URL url = ClassFinder.getResource("lib/play.exe");
	if ( url == null ) {
	  url = ClassFinder.getResource("rec/lib/play.exe");
	}
	if ( url != null ) {
	  File file = (new File(url.getFile())).getAbsoluteFile();
	  String player = file.getPath();
	  String lib = file.getParent();
	  Say.setDirs( lib, player );
	}
      }
      catch( IOException e ) {
	e.printStackTrace();
	Assert.error("Could not loadIniModule");
      }
    }

    // Make the input window the display window.
    m_Surface.setScreen( m_Input.getScreen() );
    m_Surface.setBounds( m_Input.getScreen().getBounds() );
    m_Input.getScreen().setSurface( m_Surface );
  }

  
  private void setupListeners( )
  {
    // this is what grabs strokes from the user
    m_Input.getScreen().addMouseListener( new MouseAdapter() {
      public void mousePressed( MouseEvent e )
	{
	  m_Surface.beginSquiggle( new GPoint( e.getX(), e.getY() ), getTimeStamp(e) );
	} 
      public void mouseReleased( MouseEvent e )
	{
	  m_Surface.endSquiggle( );
	}
    } );

    m_Input.getScreen().addMouseMotionListener( new MouseMotionAdapter() {
      public void mouseDragged( MouseEvent e ) {
	m_Surface.addSquigglePoint( new GPoint( e.getX(), e.getY() ), getTimeStamp(e) );
      }
    } );
  }

  private boolean loadBase( String[] args )
  {
    if( ( args.length >= 1 ) && ( args[0].startsWith( "NO" ) ) )
      {
	Console.writeln( "Not loading an .ini file." );
	return( false );
      }

    return( true );
  }


  private String getBase( String[] args )
  {
    if ( args.length >= 1 )
      {
	if ( args[0].startsWith( "NO" ) )
	  return null;
	else if ( args[0].endsWith( ".ini" ) )
	  return args[0];
	else
	  return "recog.ini";
      }
    else
      return "recog.ini";
  }

  public Surface getSurface()
  {
    return( m_Surface );
  }

  public RecFrame getRecFrame() 
  {
    return m_Input;
  }
  
  /**
   *
   * This method is needed only for supporting the rather 
   * silly windows9X platform, because it is still using the
   * dos clock.
   *
   **/
  private long getTimeStamp( MouseEvent e )    
  {
    if ( m_platformIsWindows9x ) {
      return Timer.tics();
    }
    else {
      return e.getWhen();
    }
  }
}

