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

///////////////////////////////////////////////////////////////////////////////
//
// FILE:	Surface.java
//
// ID:          $Id: Surface.java,v 1.86 2004/02/25 17:32:09 cadlerun Exp $
//
// AUTHOR:	Manoj D. Muzumdar
//		archon@ai.mit.edu
//
//              Luke Weisman
//              luke@ai.mit.edu
//
// DATE:	Jun 98
//
// COPYRIGHT:	Massachusetts Institute of Technology
//		All rights reserved.
//
// PURPOSE:	Drawing recognition
//
// HISTORY:     Jun 98  archon  created
//
//              Sep 98  archon
//              Interfaced ModuleManager with Surface.
//
//              Nov 98  archon
//              Completely separated the surface and widget registries.
//
//              Jan 99  archon
//              Added stroke cleaning code in endSquiggle().
//
///////////////////////////////////////////////////////////////////////////////

package rec.sys;

import java.io.*;
import java.util.*;
import rec.util.*;
import rec.geo.*;
import rec.core.*;
import rec.core.recognizers.*;
import rec.annotate.rules.*;
import rec.ui.UtterancePanel;
import rec.speech.*;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Frame;
import java.awt.Color;
import java.awt.Image;
import rec.core.widgets.WSelection;
import rec.core.widgets.WGroup;
import rec.simulate.widgets.WSimGearTrain;
import rec.simulate.widgets.WSimConstraint;
import rec.simulate.widgets.WSimBody;
import rec.multi.MultiModal;

public class Surface extends VisibleWidget implements TagsListener
{
  /**
   * This is used to weight the last stroke in terms of calculating stroke
   * 'average' speed.
   */
  private static double TIME_WEIGHT = 0.15;

  // three steps: Recognize --> Reason --> Resolve
  private ModuleManager m_ModuleManager; 

  // there should only be one resolver, unless we decide to build another
  // layer (to resolve the results of resolution...)
  private Resolver m_Resolver;

  // the directed graph which keeps track of the ranks of the interpretations
  // I don't think this has to be (or should be) a member
  //private OrderGraph m_RankGraph;
  
  /**
   * This maps from editing modes to collections of modules active in
   * that mode
   **/
  private EditModeManager m_EditModeManager;
  
  private HashSet m_BannedOutput;
  private WidgetPool m_RecognizePool;
  private PoolBuilder m_TempPoolBuilder;
  private PoolBuilder m_SpacePoolBuilder;
  private PoolBuilder m_ElectedPoolBuilder;

  // this is no longer used. -cja 9/27/01
  protected WidgetRegistry m_WidgetRegistry;
  
  // these two current -cja 9/27/01
  protected WidgetPool m_WidgetPool;
  private SurfaceRegistry m_SurfacePool;

  protected TransitionRecorder m_TransitionRecorder;
  private Screen m_Screen;
  private Rectangle m_Bounds;
  private rec.util.Queue m_WidgetQueue;
  private OrderGraph m_RankGraph;

  private WidgetPool m_LastWidgetPool;
  private SurfaceRegistry m_LastSurfacePool;
  
  ////////////////////////////////////////////////////////////////////////////

  private Vector m_FastRecognizers;
  private boolean m_Nabbed;
  private FastRecognizer m_Nabber;
  private Mark m_NabMarker;

  // dividing steps in recognition process
  private Mark m_Mark;
  // dividing entire strokes
  private Mark m_BigMark;

  private boolean m_Cleaning = false;
  private rec.util.Queue m_CleanQueue;
  ////////////////////////////////////////////////////////////////////////////

  private Squiggle m_CurrentSquiggle;
  private WSelection m_RetrySelection;
  
  ///////////////////////////////////////////////////////////////////////////
  
  private HashSet m_CurrentAddedWidgets;
  private HashSet m_CurrentDeletedWidgets;
  //private SpatialWidget m_CurrentSelection;
  
  /**
   * This is all hack stuff for timing the average 'speed' of
   * a stroke for recognizing 'fast' scribbles versus slow and
   * delibrate things.
   */
  private double stroke_speed; // used for timing the length of strokes.
  private double average_speed = -1;

  ///////////////////////////////////////////////////////////////////////////
  private Properties m_SystemProperties = new Properties();

  ///////////////////////////////////////////////////////////////////////////
  private Notebook m_annotations;
  private UtterancePanel m_utterancePanel;
  private boolean m_isWidgetNameShown;
  private boolean m_isWidgetSquiggleShown;
  ///////////////////////////////////////////////////////////////////////////
  private MultiModal m_multiModal; // added by ada 12/6/02 for multimodal interaction
  ///////////////////////////////////////////////////////////////////////////
  public Surface()
  {
    // for ordering events such as add/remove Widget
    m_Cleaning = false;
    m_CleanQueue = new rec.util.Queue();

    m_TransitionRecorder = new TransitionRecorder();
    m_TransitionRecorder.stopRecording();

    m_ModuleManager = new ModuleManager();
    m_Resolver = new BasicResolver();

    m_BannedOutput = new HashSet();
    m_SpacePoolBuilder = new rec.core.ShallowPoolBuilder();
    m_TempPoolBuilder = new rec.core.TemporalPoolBuilder( 4000 );
    m_ElectedPoolBuilder = new rec.core.ElectedPoolBuilder();
    
    // this is set later, but initialize in case we use it before it's set.
    m_RecognizePool = new WidgetPool();
    
    m_SurfacePool = new SurfaceRegistry();
    m_WidgetRegistry = new UndoableWidgetRegistry( this,
						   m_TransitionRecorder );
    m_WidgetPool = new WidgetPool();
    
    m_BigMark = m_TransitionRecorder.getMark( "big" );
    m_Mark = m_TransitionRecorder.getMark( "little" );

    m_WidgetQueue = new rec.util.Queue();

    m_CurrentAddedWidgets = new HashSet();
    m_CurrentDeletedWidgets = new HashSet();

    m_Screen = null;
    m_Bounds = null;

    m_RankGraph = new OrderGraph();

    m_EditModeManager = new EditModeManager();

    m_isWidgetNameShown = false;
    m_isWidgetSquiggleShown = false;

    m_TransitionRecorder.startRecording();

    m_multiModal = null;
  }

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

  /**
   * Set the pointer to MultiModal.
   */
  public void setMultiModal(MultiModal mm) {
    m_multiModal = mm;
  }

  /**
   * Return the MultiModal object.
   */
  public MultiModal getMultiModal() {
    return m_multiModal;
  }
  
  /////////////////////////////////////////////////////////////////////////////

  public double size()
  {
    // the size of a surface is zero for now
    return( 0.0 );
  }

  public void resize( double scaleFactor )
  {
  }

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

  /**
   * We don't really want to draw the squiggles
   * since there aren't any...
   **/
  public void drawSquiggles( Graphics g ) 
  {
    draw( g );
  }

  public void draw( Graphics g )
  {
    //Console.writeln( "Surface DRAW" );
    m_SurfacePool.display( g );
  }

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

  public boolean includes( GPoint p, double offset )
  {
    if( m_Bounds.contains( p.getPoint() ) )
      // bit of hack here, is it possible to just do the offset calcs
      // directly?
      //
      // how this this a bit of a hack? -archon

      return( true );

    GPoint[] arrPoints = new GPoint[4];

    arrPoints[0] = new GPoint( m_Bounds.x, m_Bounds.y );
    arrPoints[1] = new GPoint( m_Bounds.x + m_Bounds.width, m_Bounds.y );
    arrPoints[2] = new GPoint( m_Bounds.x + m_Bounds.width,
			       m_Bounds.y + m_Bounds.height );
    arrPoints[3] = new GPoint( m_Bounds.x, m_Bounds.y + m_Bounds.height );

    return( (new GPolygon( arrPoints )).includes( p, offset ) );
  }


  public boolean contains( GPoint p )
  {
    return m_Bounds.contains( p.getPoint() );
  }

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

  // shift the center; keep the size of the bounds.
  public void move( GPoint p ) 
  {
    m_Bounds.x = (int)Math.rint( p.x - (double)m_Bounds.width / 2.0 );
    m_Bounds.y = (int)Math.rint( p.y - (double)m_Bounds.height / 2.0 );
  }

  public void rotate( GPoint p, double angle )
  {
    // for now do nothing when a surface is rotated
  }

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

  public Location getLocation()
  {
    return( new Location( getBounds() ) );
  }

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

  public void startRecording()
  {
    m_TransitionRecorder.startRecording();
  }

  public void stopRecording()
  {
    m_TransitionRecorder.stopRecording();
  }

  //////////////////////////////////////////////////////////////////////
  // M O D E  H A N D L I N G  M E T H O D S:
  

  // this stuff was split into recognizer and reasoner functionality, 
  // and in the future may be taken out of the surface class altogether
  // (and replaced with a call to just get the module managers)

  /**
   * Install the modules associated with the referenced editing mode.
   **/
  public void enterEditMode( String mode ) 
  {
    m_EditModeManager.enterEditMode( mode );
  }

  /**
   * Install the modules associated with the referenced editing mode.
   * Enter the edit mode with the given arg.
   **/
  public void enterEditMode( String mode, Object arg )
  {
    m_EditModeManager.enterEditMode( mode, arg );
  }

  /** Remove the modules associated with the referenced editing mode. **/
  public void leaveEditMode( String mode )
  {
    m_EditModeManager.leaveEditMode( mode );
  }

  public void addMajorMode( String mode ) 
  {
    m_EditModeManager.addMajorMode( mode );
  }

  /**
   * @param mode
   *   <ul><li>EditModeManager.DEFAULT_MODE for inclusion in the base
   *   editing mode.  <li>mode name for module to be active only when
   *   mode is active.
   **/
  public void addModule( Module m, String mode ) 
  {
    System.out.println("Loading module: " + m.getName() );
    HashSet modes = null;
    if ( mode != null ) {
      modes = new HashSet();
      modes.add( mode );
    }
    addModule( m, modes );
  }

  /**
   * Install a new module on the surface.  To have a module always
   * used in a particular editing mode use the edit mode argument.
   *
   * @param m    Module to add to the surface
   * @param mode Set of modes that must be active for this module to
   * be active:
   **/
  public void addModule( Module m, HashSet mode )
  {
    System.out.println("Loading module: " + m.getName() );
    Assert.check( mode != null, "Null mode for module: " + m );
    if ( ! mode.isEmpty() ) {
      m_EditModeManager.addModuleToMode( m, mode );
      // make the new module active if we are in mode
      if ( m_EditModeManager.isActive( mode ) )
	m.enable();
      else
	m.disable();
    }
  
    m_ModuleManager.add( m );
  }

  public boolean hasModule( String name ) 
  {
    return (getModule( name ) != null);
  }

  public Module getModule( String name )
  {
    Iterator mods = m_ModuleManager.getModules();

    while ( mods.hasNext() ) {
      Module mod = (Module)mods.next();
      if ( mod.getName().equals( name ) ) {
	//	DebugWindow.addLine( "MO", "Has module: " + name );
	return mod;
      }
    }
    //    DebugWindow.addLine( "MO", "Doesn't have module " + name );
    return null;
  }
  
  /**
   * Remove a module from the specified mode.
   **/
  public Module removeModule( Module m, String mode )
  {
    if ( mode != null ) {
      m_EditModeManager.removeModuleFromMode( m, mode );
    }
    else {
      Assert.error( "Null mode for module: " + m );
    }

    return (Module)m_ModuleManager.remove( m );
  }


  public Module removeModule( Id mid )
  {
    return (Module)m_ModuleManager.remove( mid );
  }

  public int numModules()
  {
    return m_ModuleManager.size();
  }

  // E N D   O F   M O D E   H A N D L I N G   M E T H O D S:
  //////////////////////////////////////////////////////////////////////



  // there used to be a transition here, but now selections are required to
  // keep from of their own undo/redo info

  public boolean isRegistered(  Registerable r, Widget w )
  {
    return m_WidgetRegistry.isRegistered( r, w );    
  }

  /**
   * These all have transitions now--the registry is too messed up
   * to mess with.
   */
  public void register( Registerable r, Widget w )
  {
    m_WidgetRegistry.register( r, w );
  }

  /**
   * These all have transitions now--the registry is too messed up
   * to mess with.
   */
  public void unregister( Registerable r, Widget w )
  {
    m_WidgetRegistry.unregister( r, w );
  }



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

  /**
   * Start a big event.  This is used by the system undo/redo.
   * This is called at beginning of each stroke.
   */
  public void beginEvent( )
  {
    m_TransitionRecorder.remark( m_Mark );

    // okay so this is redudant--it is easier to think about.
    //   (given MORE IMPORTANT mark at endEvent)
    m_TransitionRecorder.remark( m_BigMark );
  }

  public void endEvent( )
  {
    //    m_TransitionRecorder.remark( m_Mark );

    m_TransitionRecorder.remark( m_BigMark );
  }

  /**
   * Record an event so the undo/redo stuff can grab a handle on it.
   */
  public void addTransition( Transition t )
  {
    m_TransitionRecorder.record( t );
  }

  public boolean canUndo()
  {
    return( m_TransitionRecorder.canUndo() );
  }

  public boolean canRedo()
  {
    return( m_TransitionRecorder.canRedo() );
  }

  // undoes the last recognition
  public void undo()
  {
    undoTo( m_BigMark );
  }

  /**
   * Redo last 'event' as defined by calls to the beginEvent and endEvent
   */
  public void redo()
  {
    redoTo( m_BigMark );
  }

  public void littleUndo()
  {
    undoTo( m_Mark );
  }

  public void littleRedo()
  {
    redoTo( m_Mark );
  }

  public void undoTo( Mark mark)
  {
    Console.writeln();
    Console.writeln( "*** Begin Undo ***" );

    if( !m_TransitionRecorder.canUndo() ) {
      Console.writeln( "nothing to undo" );
      return;
    }

    m_TransitionRecorder.stopRecording();

    if ( m_TransitionRecorder.canRewindTo( mark ) )
      m_TransitionRecorder.rewindTo( mark );
    else
     m_TransitionRecorder.rewind();

    m_TransitionRecorder.startRecording();

    m_Screen.repaint();
  }

  /**
   * Redo last 'event' as defined by calls to the beginEvent and endEvent
   */
  public void redoTo( Mark mark )
  {
    Console.writeln();
    Console.writeln( "*** Begin Redo ***" );

    if( !m_TransitionRecorder.canRedo() ) {
      Console.writeln( "nothing to redo" );
      return;
    }

    m_TransitionRecorder.stopRecording();

    if ( m_TransitionRecorder.canForwardTo( mark ) )
      m_TransitionRecorder.forwardTo( mark );
    else
     m_TransitionRecorder.forward();

    m_TransitionRecorder.startRecording();
    
    m_Screen.repaint();
  }

  /** reinterpret the last object just considered
   */
  // make another interpretation of the squiggle as strong as the winning
  // interpretation, and make the winning one very weak
  // SYNCH WITH UTTERANCES!!!

  // MO: What if retrying an utterance?

  // if the last object considered was a selection loop, which selected just
  // one object, ask the user if she wants to reinterpret the object selected
  // or the stroke used to make the selection
  public void retry()
  {
    // get the parent frame and set up the dialog
    Frame[] allFrames = Frame.getFrames();
    Frame p_frame = allFrames[0];

    if ( m_CurrentSquiggle == null ) {
      MessageBox fail = new MessageBox( p_frame, "Try Again: Error",
					"Nothing to reinterpret" );
      fail.show();
      return;
    }
    // make an undo mark, i guess
    beginEvent();

    Collection reps = m_Resolver.getLastRelatedElects( m_CurrentSquiggle );

    Assert.check( reps.size() > 0 );

    // prepare to make the retry call.  It should be the same whether we
    // are doing it for the latest thing added or for the selected widget
    // if it is a selection, this stuff should only need to be done once

    boolean first_sel = false;
    WGroup sel_grp = null;
    if ( reps.size() == 1 ) {
      Widget w = (Widget)reps.iterator().next();
      if ( w instanceof WSelection ) {
	WSelection sel = (WSelection)w;
	if ( !sel.fromLoop() ) {
	  m_RetrySelection = sel;
	  sel_grp = (WGroup)sel.getSelected();
	  
	  // the widget to kill becomes the widget which is selected, not
	  // the selection itself
	  reps = sel_grp.getWidgets();
	  Widget test = (Widget)reps.iterator().next();
	  test.printFamily();
	  Collection roots =
	    ((Widget)reps.iterator().next()).getRootAncestors();
	  // we just need one root, and there must be at least one

	  m_CurrentSquiggle = (Squiggle)roots.iterator().next();
	  first_sel = true;
	  
	  // reset the rankgraph
	  // this is a HACK.  It seems ok, except for the modules will
	  // be messed up.  we do need to reset the rankgraph though,
	  // in light of everything that has happened.
	  reason( m_CurrentSquiggle, null );
	}	
	
      }
	
    }

    RetryDialog choices = new RetryDialog( p_frame, "Try Again",
					   this, reps, m_CurrentSquiggle,
					   m_RetrySelection, first_sel );
    choices.show();
    
    
    m_Screen.repaint();
    
//      endEvent();

    
  }

  public void resetSelection( WGroup g, WGroup sel_grp )
  {

    if ( m_RetrySelection != null ) {
      if ( g.size() > 0 ) {
	m_RetrySelection.rehookSelection( g, this );
	DoTransition dt1 = new DieTransition( this, sel_grp );
	dt1.doDo();
	addTransition( dt1 );
      }
      else {
	m_RetrySelection.rehookSelection( g, this );
      }
    }

  }
  
  public void resetWidgetPools()
  {
    m_WidgetPool = m_LastWidgetPool;
    m_SurfacePool = m_LastSurfacePool;
    m_CurrentSquiggle = null;
  }

  public void retryCancel( boolean first_sel )
  {
    if ( first_sel ) {
      Assert.check( m_RetrySelection != null );
      Collection roots = m_RetrySelection.getRootAncestors();
      m_CurrentSquiggle = (Squiggle)roots.iterator().next();
    }
  }
  
  public Collection retryBase( Squiggle base, Collection reps )
  {
    Collection ret = null;
    
    // most of the time this set is size 1.
    if ( reps.size() == 1 ) {
      Widget w = (Widget)reps.iterator().next();
      
//        if ( w.equals( base ) ) {
//  	Console.writeln( "" );
//  	Console.writeln( "***RETRY***" );
//  	Console.writeln( "No further interpretation" );
//  	return new Vector();
//        }

      // make a transition to weaken this widget to the point where
      // it won't be chosen anymore (i.e. less than the strength of
      // the current squiggle).
      // I am killing undo!!!
      w.setUserStrength( -1 );
      DoTransition dt2 = new RemoveTransition( this, w );

      m_RankGraph.removeNodeSimple( w );
      
      ret = resolve( base, false );

      dt2.doDo();
      addTransition( dt2 );
    }
    else {
      // choose the part (or parts) to kill
      // the only rule for now is this: if you are going to kill
      // one member of a litter, you must kill them all.
      Iterator it = reps.iterator();
      Vector do_list = new Vector();
      
      while ( it.hasNext() ) {
	Widget w = (Widget)it.next();

	if ( w.inLitter() ) {
	  Collection litter = w.getLitter();
	  litter.add( w );

	  Console.writeln( "the reps are " + reps );
	  
	  if ( reps.containsAll( litter ) ) {
	    // remove the whole litter and break out of loop
	    Iterator lit_it = litter.iterator();

	    while ( lit_it.hasNext() ) {
	      Widget w_lit = (Widget)lit_it.next();
	      w_lit.setUserStrength( -1 );
	      m_RankGraph.removeNodeSimple( w_lit );
	      // hack, doesn't do the right thing with litters.
	      //DoTransition dt2 = new RemoveTransition( this, w_lit );
	      //do_list.add( dt2 );
	    }

	    break;
	  }
	}
	else {
	  if ( !( w instanceof Squiggle ) ) {
	    w.setUserStrength( -1 );
	    DoTransition dt2 = new RemoveTransition( this, w );
	    do_list.add( dt2 );
	    m_RankGraph.removeNodeSimple( w );
	    break;
	  }
	}
      }

      ret = resolve( base, true );

      Iterator do_it = do_list.iterator();
      while ( do_it.hasNext() ) {
	DoTransition do_now = (DoTransition)do_it.next();
	do_now.doDo();
	addTransition( do_now );
      }
    }

    return ret;
  }
  
  
  /////////////////////////////////////////////////////////////////////////////

  public boolean hasScreen()
  {
    return( (m_Screen == null) ? false : true );
  }

  public void setScreen( Screen s )
  {
    m_Screen = s;
  }

  public Screen getScreen()
  {
    Assert.check( hasScreen(), "surface has no screen" );

    return( m_Screen );
  }

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

  public boolean hasBounds()
  {
    return( (m_Bounds == null) ? false : true );
  }

  public void setBounds( Rectangle r )
  {
    m_Bounds = r;
  }

  public java.awt.Rectangle getBounds()
  {
    Assert.check( hasBounds(), "surface is unbounded" );

    return( m_Bounds );
  }

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

  private void pruneLastStroke()
  {
    Collection lastElected = m_Resolver.getLastElects();

    Collection marked = new HashSet();
    Iterator it = lastElected.iterator();
    while ( it.hasNext() ) {
      Widget w = (Widget)it.next();
      marked.addAll( w.getAncestors() );
      marked.addAll( w.getAllChildrenAndMe() );
    }

    Collection sev_trans = new HashSet();
    Collection die_trans = new HashSet();
    Iterator marked_it = marked.iterator();
    while ( marked_it.hasNext() ) {
      FamilyMember f = (FamilyMember)marked_it.next();
      Iterator children = f.getAllChildren().iterator();
      while ( children.hasNext() ) {
	FamilyMember f2 = (FamilyMember)children.next();
	if ( (f2 instanceof Widget) && !marked.contains( f2 ) ) {
	  DoTransition sev = new SeverTransition( this,
						  Collections.singleton( f2 ) );
	  DoTransition die = new DieTransition( this, (Widget)f2 );
	  sev_trans.add(sev);
	  die_trans.add(die);
	  addTransition( sev );
	  addTransition( die );
	}
      }
    }

    Iterator trans_it = sev_trans.iterator();
    while ( trans_it.hasNext() ) {
      ((DoTransition)trans_it.next()).doDo();
    }
    trans_it = die_trans.iterator();
    while ( trans_it.hasNext() ) {
      ((DoTransition)trans_it.next()).doDo();
    }
    
  }
  
  public void beginSquiggle( GPoint p, long timestamp )
  {
    Console.resetIndent();
    Console.writeln();
    Console.writeln( "***** BEGIN STROKE *****" );
    beginEvent();

    // save the current widget pool and surface pool in case of try again
    m_LastWidgetPool = new WidgetPool();
    m_LastWidgetPool.add(  m_WidgetPool.getWidgets() );
    m_LastSurfacePool = new SurfaceRegistry();
    m_LastSurfacePool.add( m_SurfacePool.getWidgets() );
    
    boolean prune = false;
    
    // prune from the last squiggle drawn
    if ( prune && ( m_CurrentSquiggle != null ) ) {
      pruneLastStroke();
    }

    stroke_speed = System.currentTimeMillis();

    m_CurrentSquiggle = null;
    m_RetrySelection = null;
    
    // set up all the fast (realtime) recognizers.
    m_FastRecognizers = new Vector();

    Iterator e1 = m_ModuleManager.getModules();
    while( e1.hasNext() ) {

	Module m = (Module)e1.next();

	if( !m.isEnabled() )
	  continue;

	Iterator e2 = m.getFastRecognizers();
	while( e2.hasNext() ) 
	  {
	    FastRecognizer fr = (FastRecognizer)e2.next();
	    fr.reset();
	    m_FastRecognizers.addElement( fr );
	  }
    }
    m_Nabbed = false;
    m_Nabber = null;
	  
    // add the first point, and we are off!
    addSquigglePoint( p, timestamp );
  }

  /**
   * This is called with each point in a stroke
   */
  public void addSquigglePoint( GPoint p, long timestamp )
  {
    if( m_CurrentSquiggle == null ){
      m_CurrentSquiggle = new Squiggle( p, timestamp );
      int strokeWidth = 4;
      try {
	strokeWidth = Integer.parseInt( getSystemProperty("stroke-width", "4") );
      } catch( NumberFormatException e ) {}
      
      m_CurrentSquiggle.setWidth( strokeWidth );
    }
    else {
      m_Screen.showSquiggle( m_CurrentSquiggle.getLastPoint(), p );
      m_CurrentSquiggle.addPoint( p, timestamp );
    }

    // if not nabbed, and have frs around, look for one who wants stroke.
    if ( !m_Nabbed && m_FastRecognizers != null ) {

      int sz = m_FastRecognizers.size();
      for( int i = 0; (!m_Nabbed) && (i < sz); ++i ) 
	{
	  FastRecognizer f = (FastRecognizer)m_FastRecognizers.elementAt( i );
	  
	  int resp = f.want( this, m_CurrentSquiggle, false );
	  
	  if( resp == f.WANT ) {
	    m_Nabbed = true;
	    m_NabMarker = m_TransitionRecorder.mark();
	    m_Nabber = f;
	    Console.writeln( "fast rec " + f + " accept stroke" );
	  }
	  else if( resp == f.DONT_WANT ) {
	      Console.writeln( "fast rec " + f + " rejected stroke" );
	      
	    m_FastRecognizers.removeElementAt( i );
	    sz--;
	      i--;
	  } // else do nothing on UNSURE
	}
      
      if( sz == 0 )
	m_FastRecognizers = null;
    }
    
    if( m_Nabbed ){
	doFastRecognize( false );
    }

  }
  
  /**
   * When the mouse is released, either cleanup the fast recognizers
   * or recognize the resultant stroke.
   */
  public synchronized void endSquiggle()
  {
    
    if( m_Nabbed )
      doFastRecognize( true );

    // this is not an 'else' clause since doFastRecognize can un-nab
    // a stroke, and then we might need to deal with it, so we re-check
    if ( !m_Nabbed ) 
    {
      // clean up the current stroke
      m_CurrentSquiggle.clean();
      
      calculateStrokeSpeeds();

      m_CurrentSquiggle.setTime( System.currentTimeMillis() );
      
      addRootWidget( m_CurrentSquiggle );

      doRecognize( m_CurrentSquiggle );
    }



    // we need this for the retry method.  let's see if the simple thing
    // works.
    //m_CurrentSquiggle = null;

    //    getLastNWidgets( 10 );
  }


  /**
   * Hack speed calculation to later be used for "scribble"
   * detection.  (hah.)
   */
  private void calculateStrokeSpeeds()
  {
    // calculate speed of stroke in length units / milliseconds
    stroke_speed = System.currentTimeMillis() - stroke_speed;
    stroke_speed /= m_CurrentSquiggle.getLength();

    if ( average_speed == -1 )
      average_speed = stroke_speed;
    else
      average_speed = average_speed
	* (1 - TIME_WEIGHT) + TIME_WEIGHT * stroke_speed;
    
    m_CurrentSquiggle.setSpeed( stroke_speed );
  }


// This won't work because if user chooses something via try again or
// something moves the recognition will be different... also need the
// same set of recognizers and reasoners.... yeiks.
//    public void saveSquiggles(String filename) 
//    {
//      ObjectOutputStream output = null;
//      try {
//        output = new ObjectOutputStream(new FileOutputStream(filename));
//      }
//      catch (IOException e) {
//        Assert.error("Trouble saving squiggles: " + e);
//        return;
//      }
  
//      Iterator it = getWidgets().iterator();
//      while( it.hasNext() ) {
//        Widget widget = (Widget)it.next();
//        if (widget instanceof Squiggle) {
//  	((Squiggle)widget).saveSquiggle(output);
//        }
//      }
//    }

//    public void restoreSquiggles(String filename)
//    {
    
//    }
  

  private Collection doRecognize( Widget w )
  {
    // build the recognize pool
    m_RecognizePool = buildRecognizePool( w );

    Vector newWidgets = new Vector();

    // recognize from widgets in the recognize pool
    boolean evt = false;
    while( !m_WidgetQueue.isEmpty() ) {
      Widget widget = (Widget)m_WidgetQueue.remove();
      newWidgets.add(widget);
      recognizeWidget( widget );
    }
    
    // resaon about widgets created and others
    reason( w, newWidgets );
    
    // debugging output: family of current squiggle
    w.printFamily();

    Collection returnMe = resolve( w, false, newWidgets );

    // ada: adding a new function to deal with multimodal input
    if ( (this.getSystemProperty("multimodal", "").equalsIgnoreCase("on")) &&
	 (m_multiModal != null) )
      m_multiModal.updateSurfaceWidgets(this);
    
    m_Screen.repaint();

    //    Console.writeln( "stroke complete" );
    endEvent();
    
    return returnMe;

  }

  /**
   * Construct a pool of the widgets that are either near by or that
   * were drawn recently.
   **/
  private WidgetPool buildRecognizePool( Widget spark )
  {
    WidgetPool ret;
    WidgetPool temp;

    ret = m_SpacePoolBuilder.build( this, m_WidgetPool, spark );

    temp = m_TempPoolBuilder.build( this, m_WidgetPool, spark );
    ret.add( temp.getWidgets() );
    temp = m_ElectedPoolBuilder.build( this, m_WidgetPool, spark );
    ret.add( temp.getWidgets() );

    return ret;
  }


  // In this function we will add all the reasoning 
  private void reason( Widget w, Vector newWidgets )
  {
    // put all the widgets into the graph
    // first get all the just-created widgets
    Collection connected = w.getAttached();

    m_RankGraph = new OrderGraph();
    
    Iterator connected_it = connected.iterator();

    if(newWidgets != null){
	Iterator new_widget_it = newWidgets.iterator();

	if (new_widget_it.hasNext()){
	    Widget widg = (Widget) new_widget_it.next();
	    Collection conneceted = widg.getAttached();
	    while (new_widget_it.hasNext()){
		widg = (Widget) new_widget_it.next();
		connected.addAll(widg.getAttached());
	    }
	    connected_it = connected.iterator();
	}
	

    }
    while ( connected_it.hasNext() ) {
      Object o = connected_it.next();
      if ( o instanceof Widget ) {
	Widget next_w = (Widget)o;

	// add the widget to the graph
	m_RankGraph.addNode( next_w );

	Iterator it = m_ModuleManager.getModules();
	while ( it.hasNext() ) {
	  Module r = (Module)it.next();
	  if ( r.isEnabled() )
	    r.reasonOnWidget( this, next_w, m_RankGraph );
	}
      }
    }

    // prune the graph (so that we don't have too many connections)
    m_RankGraph.prune();

  }
    
  private Collection resolve(Widget w, boolean retry, Vector newWidgets)
  {
      return m_Resolver.resolve( this, w, retry, m_RankGraph, newWidgets);
  }
    
  private Collection resolve( Widget w, boolean retry )
  {
      return m_Resolver.resolve( this, w, retry, m_RankGraph );
  }

  public void emptyQueue( )
  {
    while( !m_WidgetQueue.isEmpty() ) 
      {
	/*      if ( evt )
		beginEvent();
		else
		evt = true;
        */
	recognizeWidget( (Widget)m_WidgetQueue.remove() );
      }
    m_Screen.repaint();
  }

  private void reRecognizeMovedWidgets(){


      WSelection movedSelection = ((FastMoveTargetRecognizer) m_Nabber)
	  .getSelection();
      Collection movedStuff = movedSelection.getMovables();
      
      movedStuff.add(movedSelection);

      leaveEditMode("Selection");
      
      reRecognize(movedStuff);
 
  }

  public void killWidgets(Collection widgets){
      
      Iterator it = widgets.iterator();
      
      while (it.hasNext()){

	  Widget w = (Widget) it.next();

	  DoTransition sev = new SeverTransition (this,
						  Collections.singleton(w));
	  DoTransition die = new DieTransition (this, w);
	  addTransition(sev);
	  addTransition(die);
	  sev.doDo();
	  die.doDo();
      }

  }

  private boolean isLowestCombiner(Widget w){

      if(w instanceof WGroup || w instanceof WSimGearTrain){
	  return false;
      }

      if(w instanceof Squiggle && w.getChildren().size() > 0){
	  return false;
      }

      if (w.getParents().size() == 1){
	  if(!(w.getParents().toArray()[0] instanceof Squiggle)){
	      return false;
	  }
      }
      
      Collection children = new HashSet();
      
      Collection nextChildren = w.getChildren();
      int numChildren = w.numChildren();

      Collection surfaceWidgets = getSurfaceWidgets();

      if(surfaceWidgets.contains(w)){
	  return true;
      }
      
      while(numChildren > 0){
	  
	  children = nextChildren;
	  Iterator childrenIterator = children.iterator();
	  nextChildren = new HashSet();
	  
	  while(childrenIterator.hasNext()){
	      Widget child = (Widget) childrenIterator.next();
	      if (child.getParents().size() == 1){
		  nextChildren.addAll(child.getChildren());
		  if(surfaceWidgets.contains(child)){
		      return true;
		  }
	      }
	      
	      numChildren = nextChildren.size();
	  }
      }

      return false;
  }

  public Collection reRecognize(Collection widgets)
  {
      Iterator it = widgets.iterator();
      Collection squiggles = new HashSet();
      Squiggle squiggle = null;

      while (it.hasNext()){

	  Object o = it.next();
	  
	  Widget w = (Widget) o;

 	  if(w instanceof Squiggle){
	      squiggle = (Squiggle)w;
 	  }

// 	  if(w instanceof Squiggle){
// 	      squiggles.add(w); 
// 	  }


  	  if (isLowestCombiner(w)){
	      squiggles.add(w);
	      
	      Collection surfaceWidgets = getSurfaceWidgets();

	      if(surfaceWidgets.contains(w)){
		  //		  System.out.println(w + "IS ON THE SURFACE");
 		  w.impeach();
 		  // This is a hack.  For some reason the colors on these widgets
 		  // are not being reset after leaving selection mode.
 		  if (w instanceof WSimConstraint){
 		      ((SpatialWidget)w).setColor(Color.magenta);
 		  }
 		  else if(w instanceof WSimBody){
 		      ((SpatialWidget)w).setColor(Color.blue);
 		  }
 		  else{
 		      ((SpatialWidget)w).setColor(Color.gray);
		  }
	      }
	  }
      }

      it = widgets.iterator();

      while (it.hasNext()){

	  Object o = it.next();
	  
	  Widget w = (Widget) o;
	
	  DoTransition sev = new SeverTransition (this,
						  Collections.singleton(w));
	  DoTransition die = new DieTransition (this, w);
	  addTransition(sev);
	  addTransition(die);
	  sev.doDo();
	  die.doDo();
      }

      Object[] squig_array = squiggles.toArray();
      
      java.util.Comparator comp = new WidgetTimeComparator();
      Arrays.sort( squig_array, comp );
      
      Collection newRecognitions = new HashSet();

      for(int i = 0; i < squig_array.length; i++){
	  Widget current_squig = (Widget) squig_array[i];
	  
	  current_squig.setTime(System.currentTimeMillis());

	  //scheduleWidget(current_squig);
	  current_squig.revive();
	  addRootWidget( current_squig );
	  newRecognitions.add(current_squig);
	  newRecognitions.addAll(doRecognize(current_squig));
	  m_CurrentSquiggle = squiggle;
	
      }

      return newRecognitions;
  }

  private void doFastRecognize( boolean ending )
  {
    if( !m_Nabber.recognize( this, m_CurrentSquiggle, ending ) ) {

      // the recognizer does not want to keep the stroke.
      // undo all its damage
      //      Console.writeln( "* fastRecognize rejected partial stroke" );

      m_TransitionRecorder.rewindTo( m_NabMarker );
      m_NabMarker = null;

      m_Nabber = null;
      m_Nabbed = false;
    }
    if(m_Nabber instanceof ButtonClickRecognizer){
	doRecognize(m_CurrentSquiggle);
    }

    if (m_Nabber instanceof FastMoveTargetRecognizer && ending){
	reRecognizeMovedWidgets();
    }
    
    m_Screen.repaint();
  }

  /**
   * This is the top level entry point for utterances into the rec
   * system.
   **/
  public synchronized void addUtterance( WUtterance utterance)
  {
    // utterances have a duration but widgets only have a time
    // stamp...  we use the end time as the time stamp...
    utterance.setTime( utterance.getEndTime() );
    displayUtterance(utterance);
    addRootWidget( utterance );
    doRecognize( utterance );
  }

  public boolean isUtterancePanelEnabled()
  {
    return m_utterancePanel != null;
  }

  public void setUtterancePanel(UtterancePanel p) 
  {
    m_utterancePanel = p;
  }
  
  private void displayUtterance(WUtterance utterance)
  {
    m_utterancePanel.addUtterance(utterance);    
  }

  public void resultAccepted( Tag tag )
  {
    addUtterance( new WSpeechUtterance( tag ) );
  }
  
  
  public int numWidgets()
  {
    return( m_SurfacePool.getSize() );
  }


  /**
   * For drawing order--this is probably not the way to do this.
   */
  public void shuffleToBottom( Widget w )
  {
    m_SurfacePool.shuffleToBottom( w );
  }

  /**
   * For drawing order--this is probably not the way to do this.
   */
  public void shuffleToTop( Widget w )
  {
    m_SurfacePool.shuffleToTop( w );
  }


  public void addWidgetToSurface( VisibleWidget w )
  {
    // lw: this is so alterwidget rerecognition doesn't get assert
    //     failure.  I am worried this is not right.
    if ( ! m_SurfacePool.contains( w ) )
      {
	Console.writeln( "adding " + w + " to surface " );
	w.setSurface( this );
	m_SurfacePool.add( w );
	addTransition( createAddSurfaceTransition( w ) );
      }
    else Console.writeln( "adding "+w+" to surface, but already present" );
  }

  public void addWidgetsToSurface( Collection c )
  {
    Iterator it = c.iterator();
    while ( it.hasNext() ) {
      Object w = it.next();
      if ( w instanceof VisibleWidget ) {
	VisibleWidget vw = (VisibleWidget)it.next();
	addWidgetToSurface( vw );
      }
    }
  }


  public void removeWidgetFromSurface( Widget w )
  {
    // lw: this is so alterwidget rerecognition doesn't get assert
    //     failure.  I am worried this is not right.
    Console.writeln( "removing " + w + " from surface " );
    if ( m_SurfacePool.contains( w ) )
      {
	m_SurfacePool.remove( w );
	addTransition( createAddSurfaceTransition( w ).getRedoTransition() );
      }
  }

  /**
   * these are the transitions for adding and removing widgets
   * mr = main registry, sr = surface registry, wq = widget queue.
   * 1 means add, -1 means remove, 0 means do nothing
   */
  private Transition createAddSurfaceTransition( Widget w )
  {
    final Widget mw = w;

    return new FlipTransition( this ) {
      public void doUndo()
	{
	  Console.writeln( "Removing " + mw + " from the surface***" );
	  m_SurfacePool.remove( mw );
	}
      public void doRedo() 
	{
	  m_SurfacePool.add( mw );
	}
      public String name()
	{
	  return "surface-add[" + mw.getId() + "]";
	}
    };
  }

  private DoTransition createAddWidgetTransition( Widget w )
  {
    final Widget mw = w;

    return new DoTransition( this ) {
      public void doDo()
	{
	  m_WidgetPool.add( mw );
	}
      public void doUndo() 
	{
	  m_WidgetPool.remove( mw );
	}
      public String getAction()
	{
	  return "widget-add[" + mw.getId() + "]";
	}
    };
  }

  


  // this public one is for adding buttons and stuff you want to start
  // on the surface
  public void addStartWidget( Widget w )
  {
    m_WidgetPool.add( w );
    //(cut for now)m_WidgetRegistry.add( w );
    //reason( w );  --- there is nothing to reason with.

    m_RankGraph = new OrderGraph();
    m_RankGraph.addNode( w );
    
    resolve( w, false );
  }

  // this is for adding squiggles and utterances, which don't come
  // from any parents
  private void addRootWidget( Widget w )
  {
    DoTransition t = createAddWidgetTransition( w );
    t.doDo();
    addTransition( t );
    //(cut for now)m_WidgetRegistry.add( w );
    scheduleWidget( w );
  }

  ///
  // archon:
  //
  // default is rerecognize
  //
  // probably they will all be rerecognized, so this may be unneccessary 
  // for now, but you never know, so I'll keep it here. -cja
  ///
  public void addWidget( Widget w )
  {
    addWidget( w, true );
  }

  /**
   * @param reRecognize = true means put it in the queue.
   */
  public void addWidget( Widget w, boolean reRecognize )
  {
    // the action
    final Widget ww = w;
    final boolean rr = reRecognize;
    Evt e = new Evt() {
      public void clean( )
	{
	  sAddWidget( ww, rr );
	}
      public String toString()
	{
	  return "add:" + ww;
	}
    };

    clean( e );
  }
  
  /**
   * @depreciated 
   */
  public void addWidgets( Collection ws )
  {
    addWidget( ws );
  }

  public void addWidget( Collection ws )
  {
    Iterator e = ws.iterator();

    while( e.hasNext() )
      addWidget( (Widget)e.next() );
  }


  private void sAddWidget( Widget w, boolean reRecognize )
  {
    //Console.writeln( "adding widget " + w );
    w.setSurface( this );
    // put this widget into the added widgets buffer
    m_CurrentAddedWidgets.add( w );

    if( reRecognize ) 
      scheduleWidget( w );
  }

  /**
   * Add widgets not during the recognition process, like for putting
   * previously dead widget back into the pool.  their connections are
   * already there, so we don't need to go through the parent setting and
   * all that
   */
  public void onlyAddWidget( Collection ws )
  {
    Iterator it = ws.iterator();

    while ( it.hasNext() ) {
      onlyAddWidget( (Widget)it.next() );
    }
  }

  public void onlyAddWidget( Widget w )
  {
    m_WidgetPool.add( w );
    //(cut for now)m_WidgetRegistry.add( w );
  }

  public void replaceWidget( Widget ow, Widget nw )
  {
    replaceWidget( Collections.singleton( ow ), Collections.singleton( nw ), true );
  }
  
  /**
   * The main replace widget method.
   */
  public void replaceWidget( Widget ow, Widget nw, boolean rerecognize )
  {
    replaceWidget( Collections.singleton( ow ), Collections.singleton( nw ), rerecognize );
  }

  /**
   * Replace a SET of old widgets with a single new one.
   *
   * BUG NOTE: the registry is not quite right with this.  If A
   * is reg'ed with X and B is reg'ed with Y, and A and B go to X
   * then X and Y is reg'ed with C.  Undoing makes X and Y reged with
   * A and B both.
   */
  public void replaceWidget( HashSet ows, Widget nw, boolean rerecognize )
  {
    replaceWidget( ows, Collections.singleton( nw ), rerecognize );
  }

  /**
   * Replace a single widget with a SET of new widgets
   * (such as a wall or line breaking
   * up into little itty bitty bits.
   */
  public void replaceWidget( Widget ow, HashSet nws, boolean rerecognize )
  {
    replaceWidget( Collections.singleton( ow ), nws, rerecognize );
  }

  // Many --> Many replace
  public void replaceWidget( HashSet ows, HashSet nws )
  {
    replaceWidget( ows, nws, true );
  }

  private void replaceWidget( Set ows, Set nws, boolean rerecognize )
  {
//      //(cut for now)m_WidgetRegistry.replace( ows, nws );
    // the action
    
    // find the most recent thing that the new widget is being
    // built from
    Iterator it = ows.iterator();
    long longest_time = -1;
    while ( it.hasNext() ) {
      Widget w = (Widget)it.next();
      if ( longest_time < w.getTime() ) {
	longest_time = w.getTime();
      }
    }

    // now set the new widgets timestamps
    it = nws.iterator();
    while ( it.hasNext() ) {
      Widget w = (Widget)it.next();
      w.setTime( longest_time );
    }
    
    Evt e = new ReplaceEvt( ows, nws, rerecognize );

    clean( e );
  }

  private class ReplaceEvt extends Evt {
    Set ows;
    Set nws;
    boolean rerecognize;

    public ReplaceEvt( Set ows, Set nws, boolean rerec )
    {
      this.ows = ows;
      this.nws = nws;
      rerecognize = rerec;
    }
    
    public void clean( )
    {
      sReplaceWidget( ows, nws, rerecognize );
    }

    public String toString()
    {
      return "rep->set:" + ows;
    }
  }

  private void sReplaceWidget( Set ows, Set nws, boolean rerecognize )
  {
//      Console.writeln( "******************" );
//      Console.writeln( "replace " + ows + " with " + nws );

    // do the replace, get a transition so we can undo it if necessary
    // and store the transition.
    Transition t = m_WidgetPool.replaceAndGetTransition( this, ows, nws );
    addTransition( t );

    // and put all the stuff on the widget queue, and mark
    // so no duplicates get recognized (with bannedouput).
    if ( rerecognize ) {
      Iterator e = nws.iterator();
      while ( e.hasNext() ) {
	Widget w = (Widget)e.next(); 
	scheduleWidget( w );
	m_BannedOutput.add( w.getClass().getName() );
      }
    }
    //m_RecognizePool.add( nws );
//      Console.writeln( "*******END********" );
  } // end Many --> Many replace


  ///
  // default is to not rerecognize
  //
  // actually altering a widget with recognition is currently meaningless
  //     because an immediate notification cannot made until the final
  //     widget is known
  ///
  public void alterWidget( Widget w, AlterTransition t )
  {
    alterWidget( w, t, false );
  }

  public void alterWidget( Widget w, AlterTransition t, boolean rerecognize )
  {
    // the action
    final Widget ww = w;
    final AlterTransition tt = t;
    final boolean re = rerecognize;
    Evt e = new Evt() {
      public void clean( )
	{
	  sAlterWidget( ww, tt, re );
	}
      public String toString()
	{
	  return "alter:" + ww + ", " + tt;
	}
    };

    clean( e );
  }

  private void sAlterWidget( Widget w, AlterTransition t, boolean rerecognize )
  {
    Console.writeln( "alter(b) " + w );

    //(cut for now)m_WidgetRegistry.alter( w, t );

    if( rerecognize )
      scheduleWidget( w );
  }




  ////
  // archon:
  //
  // this is always immediate
  ////
  public void removeWidget( Widget w )
  {
    // the action
    final Widget ww = w;
    Evt e = new Evt() {
      public void clean( )
	{
	  sRemoveWidget( ww );
	}
      public String toString()
	{
	  return "remove:" + ww;
	}
    };

    clean( e );
  }


  // this function has changed somewhat.  Instead of removing it, build an
  // internal remove list, which gets called at the end of the function
  // so that we can group things and call replace instead.
  private void sRemoveWidget( Widget w )
  {
    //Console.writeln( "remove " + w );

    // leave the widget in the queue if it's there

    m_CurrentDeletedWidgets.add( w );
  }


  public void removeWidgets( Vector ws )
  {
    Iterator e = ws.iterator();

    while( e.hasNext() )
      removeWidget( (Widget)e.next() );
  }
  
  // recognizers need to be careful not to use add and delete inconsistantly
  // otherwise this function will not work.  If recognizers only use
  // replace, both sets should be null.  Otherwise we have unpredictable 
  // behavior
  public void endRecognition()
  {
    if ( m_CurrentDeletedWidgets.isEmpty() && m_CurrentAddedWidgets.isEmpty() )
      {
	// this is OK, just do nothing
	return;
      }
    else if ( m_CurrentDeletedWidgets.isEmpty() ) {
      Assert.note( "***** In last recognition, widgets were added but not deleted" );
      Console.writeln( "the following widgets were added" );
      Iterator it = m_CurrentAddedWidgets.iterator();
      while( it.hasNext() ) {
	Widget w = (Widget)it.next();
	Console.writeln( "widget " + w + " added *****" );
      }
      m_CurrentAddedWidgets = new HashSet();
    }
    else if ( m_CurrentAddedWidgets.isEmpty() ) {
      Assert.note( "In last recognition, widgets were deleted but not added" );
      m_CurrentDeletedWidgets = new HashSet();
    }
    else {
      replaceWidget( m_CurrentDeletedWidgets, m_CurrentAddedWidgets );
      m_CurrentDeletedWidgets = new HashSet();
      m_CurrentAddedWidgets = new HashSet();
    }
  }

  public DoTransition expunge()
  {
    DoTransition dt = new DoTransition( this ) {

      private Collection c;
      private Collection sc;
      private Collection rc;

      public void doDo()
      {
	c = m_WidgetPool.expunge();
	sc = m_SurfacePool.expunge();
	rc = m_RecognizePool.expunge();

	//Console.indent();
	//Iterator it = c.iterator();
	//Console.writeln( "The follwoing widgets were dead and removed from the widget pool" );
	//while ( it.hasNext() ) {
	//  Console.writeln( (Widget)it.next() + "" );
	//}
	//Console.outdent();
      }

      public void doUndo()
      {
	m_WidgetPool.add( c );
	m_SurfacePool.add( sc );
	m_RecognizePool.add( rc );
      }

      public String getAction()
      {
	return "remove dead widgets from the pools";
      }
    };
    
    dt.doDo();
    
    //(cut for now)while ( it.hasNext() ) {
      //(cut for now)m_WidgetRegistry.remove((Widget)it.next());
    //(cut for now)}

    return dt;
  }
  

  /**
   * @process an event, or queue it.
   */
  private void clean( Evt e )
  {
    //Console.writeln( "Cleaning event: " + e );
    Console.indent();
    e.clean();
    Console.outdent();

    /*
    m_CleanQueue.append( e );

    if (! m_Cleaning )
      {
	m_Cleaning = true;
	
	while( ! m_CleanQueue.isEmpty() )
	  {
	    Evt te = (Evt)m_CleanQueue.remove();
	    Console.writeln( "Cleaning event: " + te );
	    Console.indent();
	    te.clean();
	    Console.outdent();
	  }
	
	m_Cleaning = false;
      }
      */
    // else do nothing
  }

  /**
   * This allows the recognizers to pop the widget back up to the top of the 
   * queue to be rerecognized.  This should be called just before returning
   * TRUE from a recognizer. 
   */
  public void rerecognizeWidget( Widget w )
  {
    scheduleWidget( w );
  }




  public boolean containsWidget( Widget w )
  {
    return( m_SurfacePool.contains( w ) );
  }

  public boolean containsWidget( Id id )
  {
    return( m_SurfacePool.contains( id ) );
  }

  public Widget getWidget( Id id )
  {
    return( m_WidgetPool.get( id ) );
  }

  /** Get all the visible widgets in the recognize pool. **/
  public Collection getVisibleWidgets()
  {
    Collection c = new HashSet();
    Iterator it = getWidgets().iterator();
    while ( it.hasNext() ) {
      Object o = it.next();
      if ( o instanceof VisibleWidget ) {
	c.add( o );
      }
    }
    return c;
  }

  /**
   * Get all the widgets in the surface pool.  i.e. all the widgets
   * that make up the currently believed state of the world.
   **/
  public Collection getSurfaceWidgets()
  {
    return m_SurfacePool.getWidgets();
  }
  

  /** Get the widgets that are in the recognize pool. **/
  public Collection getWidgets()
  {
    return( m_RecognizePool.getWidgets() );
  }

  /** Get the widgets that are in the recognize pool. **/
  public Iterator getWidgetsIterator()
  {
    return( m_RecognizePool.iterator() );
  }

  public WidgetPool getSurfacePool()
  {
    return m_SurfacePool;
  }

  public Iterator getSurfacePoolIterator()
  {
    return m_SurfacePool.iterator();
  }

  /** Get all the widgets that have ever been recognized */
  public Collection getAllWidgets()
  {
    return m_WidgetPool.getWidgets();
  }

  /**
   * Get the previous n widgets.  i.e. Widgets that were based on
   * different squiggles/utterances and not contained in a later
   * widget.
   **/
  public LinkedList getLastNWidgets( int n )
  {
    Iterator it = m_WidgetPool.iterator();
    LinkedList lastN = new LinkedList();

    while( it.hasNext() ) {
      Widget w = (Widget)it.next();
      if ( w.isElected() ) {
	lastN.addFirst( w );
	if ( lastN.size() > n ) {
	  lastN.removeLast();
	}
      }
    }

    return lastN;
  }

  /**
   * @return Elected widgets that are based on different
   *         squiggles/utterances.  Most recent are first.
   **/
  public LinkedList getTimeOrderedElectedWidgets() 
  {
    return getLastNWidgets( m_WidgetPool.getSize() );
  }
  

//    /**
//     * Get all the widgets drawn in the last millisecs milliseconds
//     **/
//    public Iterator getRecentWidgets( long millisecs ) 
//    {
    
//    }
  
  

  /**
   * When we want to add a new Widget, we put it through this.
   *
   * The resulting Widget cannot be a Squiggle.
   *
   * @return  The resulting Widget after recognition.
   */

  private void recognizeWidget( Widget w )
  {
    //Console.writeln( "recognize " + w );
    //Console.indent();

    m_BannedOutput.clear();
    m_RecognizePool.add( w );

    // mark beginning of widget recognition.
    m_TransitionRecorder.remark( m_Mark );
    Object frame = m_TransitionRecorder.getFrameReference();
    
    Iterator e1 = m_ModuleManager.getModules();

    boolean bEndRecognition = false;

    // will we ever want recognizers to be able to halt recognition of
    // a widget?  for now, no
    while( e1.hasNext() ) {

      Module m = (Module)e1.next();

      //Console.writeln( "Module " + m );
      //Console.indent();

      // if the module is active (or should be) recognize.
      if( m.isEnabled() ) {
	bEndRecognition = m.recognizeWidget( this, w );
      }

      //Console.outdent();
    }

    // mark end of recognition
    m_TransitionRecorder.remark( m_Mark );
    
    if ( ! bEndRecognition )
      {
	// if no relevant recognition activity, then we don't want 
	// to mark the transition so the little undo and little redo
	// doesn't stop for data structure maintenence.
	m_TransitionRecorder.unmark( frame, m_Mark );
      }
    

    Console.outdent();
  }

  public boolean banned( String className )
  {
    return m_BannedOutput.contains( className );
  }

  public void addWidgetOrdering( String wname )
  {
    ;
  }



  public TransitionRecorder recorder() 
  {
    return m_TransitionRecorder;
  }
  

  public Mark mark()
  {
    return m_BigMark;
  }


  /**
   * This puts a widget on the queue for recognition.
   */
  private void scheduleWidget( Widget w )
  {
    if ( m_WidgetQueue.contains( w ) ) {
      Console.writeln( "Widget already scheduled for recognition: " + w);
    }
    else{
      m_WidgetQueue.append( w );    
    }
  }

  private void unscheduleWidget( Widget w )
  {
    if ( m_WidgetQueue.contains( w ) )
      {
	Console.writeln( "Unscheduling " + w );
	m_WidgetQueue.remove( w );
      }
  }
  
  
  public double getAverageStrokeSpeed( )
  {
    return average_speed;
  }

  public Image createImage( int x, int y )
  {
    return m_Screen.createImage( x, y );
  }

  abstract class Evt {
    public abstract void clean( );
  }
 
  /**
   * Debugging print functions, so we can always know what's in the
   * widget pool and surface pool
   */
  public void printWidgetPools()
  {
    m_WidgetPool.print();
    m_SurfacePool.print();
  }

  public void graphTimeInfo()
  {
    Iterator it = m_SurfacePool.iterator();
    PrintWriter output = null;
    
    try {
      output = new PrintWriter( new FileWriter( "TimeInfo" ), true );
    }
    catch( IOException e ) {
      System.out.println( e );
      Assert.error("Error in Surface.graphTimeInfo");
    }
    
    long lastTime = 0;
    long thisTime;
    long timeDiff;
    boolean first = true;
    java.util.Comparator comp = new WidgetTimeComparator();
    double[] diffList = new double[m_WidgetPool.getSize() + 500];
    int counter = 0;
    
    while ( it.hasNext() ) {
      Object[] roots = ((Widget)it.next()).getRootAncestors().toArray();
      Arrays.sort( roots, comp );
      
      Iterator root_it = Arrays.asList( roots ).iterator();
      while ( root_it.hasNext() ) {
	Widget w = (Widget)root_it.next();
	thisTime = w.getTime();
	timeDiff = thisTime - lastTime;

	if ( first ) {
	  output.println( "0" );
	  diffList[counter] = 0;
	  counter++;
	  first = false;
	}
	else {
	  output.println( timeDiff );
	  diffList[counter] = timeDiff;
	  counter++;
	}

	lastTime = thisTime;
      }

      diffList[counter] = 0;
      counter++;
      output.println();
    }

    
    //    TimeGraph graph = new TimeGraph( diffList, counter ); 
  }

  public void printCurrentWidgetFamily()
  {
    if ( m_CurrentSquiggle != null ) {
      m_CurrentSquiggle.printFamily();
    }
    else {
      Console.writeln( "Current Squggle is null" );
    }
  }

  public Notebook getNotebook( )
  {
    if (m_annotations == null) {
      m_annotations = new Notebook( this );
    }

    return m_annotations;
  }

  public void shutdown()
  {
    if (m_annotations != null) {
      m_annotations.shutdown();
    }
  }

  /**
   * This is to keep track of system specific properties like paths to
   * external programs etc...
   **/
  public void setSystemProperty( String key, String value )
  {
    m_SystemProperties.setProperty( key, value );
  }

  /**
   * This gets the value for property "key" and if it is not found
   * returns the empty string.
   */
  public String getSystemProperty( String key ) 
  {
    return getSystemProperty( key, null );
  }

  /**
   * This gets the value for property "key" and if it is not found
   * returns defaultValue.
   **/
  public String getSystemProperty( String key, String defaultValue ) 
  {
    return m_SystemProperties.getProperty( key, defaultValue );
  }

  ////////////////////////////////////////////////////////////////////
  // Flags for how to draw widgets

  public boolean isWidgetNameShown() 
  {
    return m_isWidgetNameShown;
  }

  public void setIsWidgetNameShown( boolean isShown ) 
  {
    m_isWidgetNameShown = isShown;
  }

  public boolean isWidgetSquiggleShown() 
  {
    return m_isWidgetSquiggleShown;
  }

  public void setIsWidgetSquiggleShown( boolean isShown ) 
  {
    m_isWidgetSquiggleShown = isShown;
  }
  

}

