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

// $Id: TransitionRecorder.java,v 1.16 2003/03/06 00:40:39 moltmans Exp $
// Time-stamp: <2000-03-11 18:59:36 moltmans>
package rec.sys;


///////////////////////////////////////////////////////////////////////////////
//
// FILE:	TransitionRecorder.java
//
// AUTHOR:	Manoj D. Muzumdar
//		archon@ai.mit.edu
//
// DATE:	June, 1998
//
// COPYRIGHT:	Massachusetts Institute of Technology
//		All rights reserved.
//
// PURPOSE:	Drawing recognition system
//
///////////////////////////////////////////////////////////////////////////////


import java.util.*;
import rec.util.*;

public class TransitionRecorder
{
  /**
   * These have alternating Transition objects and MarkPoint objects
   * The top of the stack is _always_ a Transition.  The MarkPoint
   * objects represent pointers to static images of Surface
   * Imagine the TransitionRecorder as film.  Transitions are the difference
   * between sequential frames, and Marks point to Frames themselves.
   */
  private Stack m_UndoStack;
  private Stack m_RedoStack;

  private int m_NotRecording = 0;

  private MarkPoint current;

  public TransitionRecorder()
  {
    m_UndoStack = new Stack();
    m_RedoStack = new Stack();

    current = new MarkPoint();
  }

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


  /** 
   * This undoes stuff until the transition t is reached.  It checks
   * for the existance of t in the queue before any action, and if
   * it doesn't find it, it throws a BadRecorder runtime exception.
   */
  public void rewindTo( Mark t )
  {
    // if we are here, stop.
    //    if ( current.markedWith( t ) )
    //      return;

    if  ( m_UndoStack.search( t ) == -1 )
      {
	printUndo();
	throw new RuntimeException( "Mark " 

                                   + t + " not found in undo stack." );
      }

    stopRecording();

    MarkPoint cur = null;
    for ( cur = undo(); !cur.markedWith( t ); cur = undo() )
      ;

    startRecording();
  }


  /**
   * Return true if it is possible to rewind to t, i.e. if t is in the
   * undo stack.
   */
  public boolean canRewindTo( Mark t )
  {
    // if we are here, stop.
    //    if ( current.markedWith( t ) )
    //      return true;

    if  ( m_UndoStack.search( t ) == -1 )
      return false;

    return true;
  }


  public void rewind( )
  {
    while( canUndo() )
      undo();
  }


  /** 
   * This redoes stuff until the transition t is reached.  It checks
   * for the existance of t in the queue before any action, and if
   * it doesn't find it, it throws a BadRecorder runtime exception.
   */
  public void forwardTo( Mark t )
  {
    // if we are here, stop.
    //    if ( current.markedWith( t ) )
    //      return;

    Assert.check( canForwardTo(t),
		  "Transition " + t + " not found in redo stack." );

    MarkPoint cur = null;
    for ( cur = redo(); !cur.markedWith( t ); cur = redo() )
      ;
  }
  

  public boolean canForwardTo( Mark t )
  {
    // if we are here, stop.
    //    if ( current.markedWith( t ) )
    //      return true;

    return ( m_RedoStack.search( t ) != -1 );
  }


  public void forward( )
  {
    while( canRedo() )
      redo();
  }


  /**
   * Return vector of transitions and MarkPoints back to frame pointer
   * marked by t, _EX_clusive.  Leaves recorder in latest frame pointed to
   * by t (could be current one if current one is marked by t)
   */
  public Vector cutTo( Mark t )
  {
    Console.writeln( "TR: cutting back to mark " + t );

    // if we are here, stop.
    if ( current.markedWith( t ) )
      return new Vector();

    if  ( m_UndoStack.search( t ) == -1 )
      throw new RuntimeException( "Transition " 
                                   + t + " not found in undo stack." );
    Object cur = null;
    Vector trans = new Vector();
    while( ! current.markedWith( t ) )
      {
	trans.addElement( current );
	trans.addElement( m_UndoStack.pop() );

	// grab the current.
	current = (MarkPoint)m_UndoStack.pop();
      }

    // flip the order
    Vector t2 = new Vector();
    int s = trans.size();
    for ( int i = 0; i < s; i++ )
      t2.addElement( trans.elementAt( s - i - 1 ) );

    return t2;
  }



  /**
   * Record a transition.
   */
  public void record( Transition t )
  {
    if ( m_NotRecording == 0 )
      {
	addTransition( t );
	//Console.writeln( "t-add: " + t );
      }
    else
      {
	//Console.writeln( "t-bury: " + t );
      }
  }

  /**
   * @param transitions -- MUST BE of form Transition, MarkPoint, transition,
   *        etc.
   */
  public void record( Vector transitions )
  {
    int size = transitions.size();
    for ( int i = 0; i < size; i += 2 )
      {
	record( (Transition)transitions.elementAt( i ) );

	// then copy over the marks (the next element in Vector	
	current = (MarkPoint)transitions.elementAt( i + 1 );
      }
  }
  


  public void stopRecording()
  {
    m_NotRecording++;
  }


  public void startRecording( )
  {
    Assert.check( m_NotRecording > 0, "recording flag corrupt." );
    m_NotRecording--;
  }


  public boolean isRecording()
  {
    return ( m_NotRecording == 0 );
  }

  /**
   * Make a mark on the tape.  Now rewindTo( mark ) and all that
   * works.  It is essentially and empty handle transition for
   * record keeping purposes.
   */
  public Mark mark( )
  {
    return remark( getMark() );
  }

  public Mark mark( String name )
  {
    return remark( getMark( name ) );
  }

  public Mark getMark( )
  {
    return getMark( "mark" );
  }

  public Mark getMark( String name )
  {
    Mark m = new Mark( name );
    return m;
  }

  public Mark remark( Mark mark )
  {
    //    Console.writeln( "Marking " + mark );
    current.addMark( mark );

    return mark;
  }


  public boolean isMarked( Mark mark )
  {
    return current.markedWith( mark );
  }

  public void unmark( Mark mark )
  {
    Assert.check( isMarked( mark ), "No mark to remove." );
    current.removeMark( mark );
  }

  public void unmark( Object o, Mark mark )
  {
    Assert.check( o instanceof MarkPoint,
		  "Unmark on non-markpoint!" );

    ((MarkPoint)o).removeMark( mark );
  }

  public Object getFrameReference( )
  {
    return current;
  }

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

  public boolean canUndo()
  {
    return( !m_UndoStack.isEmpty() );
  }

  public boolean canRedo()
  {
    return( !m_RedoStack.isEmpty() );
  }

  
  /**
   * Undoes single transition.
   *
   * Essentially useless for outside use.  Should undo to specific transitions
   * or types of transition.
   */
  private MarkPoint undo()
  {
    return shuffle( m_UndoStack, m_RedoStack );
  }

  /**
   * Redoes single transition
   *
   * Essentially useless.  Should redo to specific transitions
   * or types of transition.
   */
  private MarkPoint redo()
  {
    return shuffle( m_RedoStack, m_UndoStack );
  }


  /**
   * This pushes a element from s1 to s2, running the undo method
   * while it does so.
   */
  private MarkPoint shuffle( Stack s1, Stack s2 )
  {
    Console.indent();
    Assert.check( !s1.isEmpty(), "nothing to shuffle" );

    // first push over the current frame Mark
    s2.push( current );

    Transition t = (Transition)s1.pop();

    // do the work of the shuffle
    Console.writeln( "Shuffling " + t + " to " + t.getRedoTransition() );
    Console.indent();

    Transition n = t.undo();
    Assert.check( n != null, "null undo transition for " + t );

    Console.outdent();

    // push the result onto the new stack
    s2.push( n );

    // grab out the current mark.
    current = (MarkPoint)s1.pop();

    Console.outdent();

    // return current Frame pointer
    return current;
  }

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

  
  public void printUndo()
  {
    int s = m_UndoStack.size(); 
    Console.writeln( "UNDO STACK" );
    int counter = 0;
    Console.writeln( "Currently at " + current );

    for ( int i = s - 1; (counter++ < 60) && (i >= 0); i-- )
      {
	Object ob = m_UndoStack.elementAt(i);
	if ( ob instanceof MarkPoint )
	  {
	    if ( ! ( (MarkPoint)ob ).empty() )
	      Console.writeln( ".    " + ob );
	  }
	else
	  {
	    Console.writeln( ".    " + ob
			     );
	    // don't need this part anymore.
			     //			     + "              \t" 
			     //                             + (((Transition)ob).getRedoTransition()) );
	  }
      }
  }

  public Transition peekUndo( )
  {
    return (Transition) m_UndoStack.peek();
  }

  public Transition popUndo( )
  {
    Transition t = (Transition) m_UndoStack.pop();

    // dump the frame marker
    m_UndoStack.pop();
    Assert.note( "Dumping Frame marker in popUndo()" );

    return t;
  }

  public Transition peekRedo( )
  {
    return (Transition) m_RedoStack.peek();
  }

  public Transition popRedo( )
  {
    Transition t = (Transition) m_RedoStack.pop();

    // dump the frame marker
    m_RedoStack.pop();
    Assert.note( "Dumping Frame marker in popRedo()" );

    return t;
  }



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



  private void addTransition( Transition t )
  {
    // First, put redo's in the undo stack.
    //        This is very tricky process.
    //        We are putting the redo's in the undo stack, and then
    //        the _reverse_ of the redo's in the undo stack so the 
    //        final image is the same as _current_, but we have stored
    //        all changes in the redo stack.
    //        so A t1 B t2 _current_ t3 C t4 D ->
    //              A t1 B t2 _current_ t3 C t4 D t4^-1 C t3^-1 _current_

    if( !m_RedoStack.isEmpty() ) {
      
      Console.writeln( "TR: Heading down a new path (moving redo to undo)" );
      Console.indent();

      Stack tmpStack = new Stack();

      Transition tmp = null;

      while( !m_RedoStack.isEmpty() ) {

	// copy over the frame mark
	tmpStack.push( current );
	m_UndoStack.push( current );

	// copy over the transition
	tmp =
	  ((Transition)m_RedoStack.pop()).getRedoTransition();

	Assert.check( tmp != null, "null transition in addTrans" );
	tmpStack.push( tmp );
	Console.writeln( "<- " + tmp );
	m_UndoStack.push( tmp );

	// get new frame mark
	current = (MarkPoint)m_RedoStack.pop();
      }

      // now the 2nd half
      while( !tmpStack.isEmpty() ) {

	// push over current frame pointer
	m_UndoStack.push( current );

	tmp = ((Transition)tmpStack.pop()).getRedoTransition();
	Assert.check( tmp != null, "null transition in addTrans copy" );

	// Only push transitions if they are different from the top trans
	// on the dest stack--this is to deal with marks and making sure
	// they do not split when they should not split.  (This is what
	// might be called a CHEAP FUCKING HACK. - lw
	if ( m_UndoStack.empty() || !tmp.equals( m_UndoStack.peek() ) )
	  m_UndoStack.push( tmp );

	Console.writeln( "<- " + tmp );

	// grab out the new current frame pointer
	current = (MarkPoint)tmpStack.pop();
      }
      Console.outdent();
    } // end if RedoStack empty adjustment

    // ASSERT: m_RedoStack.isEmpty() == true

    // force the creation of the redo transition
    //    (to decrease the chance of weird bugs due to when it is built)
    Transition ignore = t.getRedoTransition();

    // push the current frame onto the undo stack
    m_UndoStack.push( current );

    // Now, push the new transition on the undo stack.
    m_UndoStack.push( t );

    current = new MarkPoint();
  }



}



class MarkPoint {
	
  private HashSet marks;

  public MarkPoint( )
  {
    marks = new HashSet();
  }	

  
  public boolean markedWith( Mark mark )
  {
    return marks.contains( mark );
  }

  public void addMark( Mark mark )
  {
    marks.add( mark );
  }

  public void removeMark( Mark mark )
  {
    marks.remove( mark );
  }

  public String toString( )
  {
    return "mp:" + marks;
  }

  public boolean empty()
  {
    return marks.isEmpty();
  }

  public boolean equals( Object o )
  {
    if ( o instanceof Mark )
      return markedWith( (Mark)o );
    else
      throw new RuntimeException( "Invalid use of equals with MarkPoint"
				  + " comp to " + o 
				  + " class = " + o.getClass() 
				  + " am a " + toString() );
  }
}

