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

////////////////////////////////////////////////////////////////////////////////
//
// FILE:	Widget.java
//
// ID:          $Id: Widget.java,v 1.37 2003/06/03 15:30:50 cadlerun Exp $	
//
// AUTHOR:	Manoj D. Muzumdar
//		archon@ai.mit.edu
//
//              Adapted from code written by Luke Weisman
//              luke@ai.mit.edu
//
// DATE:	06-XX-98
//
// COPYRIGHT:	Massachusetts Institute of Technology
//		All rights reserved.
//
// PURPOSE:	Drawing recognition
//
// HISTORY:     11-15-98 archon
//              Now aware of new rec.geo package
//
//              2-99 archon
//              Added resizing code

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

package rec.sys;

import java.io.*;
import java.awt.*;
import java.util.*;
import rec.util.*;
import rec.geo.*;

// LW: Widget used to be Registerable, but this makes no sense, so I cut it
//    out.  I left in the default methods though--so if you do implement it
//    you can selectively override them.
public abstract class Widget implements Cloneable, Serializable, FamilyMember
{
  private Id m_Id;

  transient private Surface m_Surface;

  // new stuff added to support multiple recognition of objects
  private VectorSet m_Children;
  private VectorSet m_Parents;
  private WidgetSet m_Litter;  // usually null, except when part of single
                               // parent
  // relative strength is fluid, goes from 0 to max_strength, set everytime
  protected int m_RelStrength;
  protected int m_UserStrength;
  static final protected int maxStrength = 10;

  private boolean m_Pulse;  // perhaps we won't need both pulse and strength
  protected TransitionSuitcase m_Suitcase;
  private boolean m_Elected;
  protected String m_EnglishName;

  protected long m_TimeStamp = -1;
  
  ///////////////////////////////////////////////////////////////////////////

  public Widget()
  {
    m_Id = new Id();
    m_Pulse = true; 
    m_Children = new VectorSet();
    m_Parents = new VectorSet();
    m_RelStrength = 0;
    m_UserStrength = 0;
    m_Elected = false;
    m_TimeStamp = -1;
    m_EnglishName = null;
    
    // Set up the basic election suitcase stuff here
    m_Suitcase = null;
  }


  public void setEnglishName( String name )
  {
    m_EnglishName = new String( name );
  }
  
  public long getTime()
  {
    return m_TimeStamp;
  }
  
  public void setTime( long time )
  {
    m_TimeStamp = time;
  }
  
  public final Id getId()
  {
    return( m_Id );
  }

  public final boolean equals( Widget w )
  {
    if ( w == null )
      return false;
    else
      return( w.getId().equals( getId() ) );
  }

  public final boolean equals( Object w )
  {
    if( w instanceof rec.sys.Widget )
      return equals( (Widget)w );

    return false;
  }

  public final boolean equalTypes( Widget w )
  {
    return( w.getClass().getName() == getClass().getName() );
  }

  public String toString()
  {
    return( getClass().getName() + ":" + getId() );
  }

  /**
   * Override if Widget is going to be registered.
   */
  public void removed( Widget w )
  {
  }

  /**
   * Override if Widget is going to be registered.
   */
  public void replaced( Widget ow, Widget nw )
  {
  }

  /**
   * Default behaviour is to act like a series of single replacements.
   */
  public void replaced( Widget ow, Collection nws )
  {
    Iterator e = nws.iterator();
    while( e.hasNext() )
      replaced( ow, (Widget)e.next() );
  }

  /**
   * Override if Widget is going to be registered.
   */
  public void altered( Widget w, Transition t )
  {
  }

  
  public Surface getSurface()
  {
    return m_Surface;
  }


  public void setSurface( Surface s )
  {
    m_Surface = s;
  }

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

  public Object clone()
  {
    Assert.error( "base clone of widget bad" );    
    
    try {
      
      Console.writeln( "Base clone of widget" );
      PipedInputStream pip = new PipedInputStream();
      PipedOutputStream piw = new PipedOutputStream( pip );

      ObjectOutputStream oos = new ObjectOutputStream( piw );
      ObjectInputStream iis = new ObjectInputStream( pip );

      oos.writeObject( this );

      Object o = iis.readObject();
      ((Widget)o).replaceId(); // Get a new Id for the clone.

      iis.close();
      oos.close();

      Console.writeln( "clone: " + o );
     
      return( o );
    }
    catch( Exception e ) {
      e.printStackTrace();
      Assert.error( "clone failed" );
    }

    return( null );
  }

  /**
   * This is for internal class and subclass use only.
   *
   * The only plausible use is for cloning.
   */
  protected final void replaceId()
  {
    m_Id = new Id();
  }


  protected final void recordChange( Transition t )
  {
    if ( m_Surface != null )
      m_Surface.addTransition( t );
    else
      Console.writeln( "Warning: dumping widget transition " + t );
  }

  /**
   * @return Collection of Class objects that are parents of this
   * class.  It includes parent classes and interfaces in the java
   * hierarchy (up to and including Widget) and in the rec system's
   * isa hierarchy.
   *
   * Subclasses should always call this method and then add any
   * classes not in the java hierarchy that should be included in this
   * collection.  Subclasses do not need to explicitly add themselves.
   **/

  // i put the old version of isa back in as a test.


//    public Collection isa()
//    {
//      return new HashSet();
//    }

  public Collection isa()
  {
    Class me = getClass();
    Class widget = ClassFinder.find( "rec.sys.Widget" );
    // get all super classes and interfaces up to and including Widget
    return ClassFinder.getSupers( me, widget );
  }


  
  /**
   * Is this widget a subtype of className?
   **/
  public boolean isItA( String className )
  {
    Class c = null;
    try {
      c = Class.forName( className );
    }
    catch( ClassNotFoundException e ) {
      Assert.error( "bad/unrecognized widget class name" );
    }
    return isItA( c );
  }

  /**
   * Is this widget a subtype of classObj?
   * Checks both Java class hierarchy and the rec isa hierarchy.
   **/
  public boolean isItA( Class classObj )
  {
    Collection types = isa();
    // first test is for java superclasses
    // second is for rec rupertypes (excuse abusive distinction please...)
    if ( classObj.isAssignableFrom( getClass() ) ||
	 types.contains( classObj ) )
    {
      return true;
    }
    else {
      return false;
    }
  }

  public String getEnglishName()
  {
    return m_EnglishName;
  }
    
  ///////////////////////////////////////////////////////////////////////////
  // Family Stuff

  public void addChild( FamilyMember w ) 
  {
    m_Children.add( w );
  }

  public void removeChild( FamilyMember w )
  {
    m_Children.remove( w );
  }

  public void addChildren( Collection c )
  {
    if ( c.size() <= 1 ) {
      m_Children.addAll( c );
    }
    else {
      WidgetSet litter = new WidgetSet( c );
      m_Children.add( litter );
      Iterator it = c.iterator();
      while( it.hasNext() ) {
	Widget w = (Widget)it.next();
	w.m_Litter = litter;
      }
    }
  }

  public WidgetSet hackSetLitter( WidgetSet ws )
  {
    WidgetSet ret = m_Litter;
    m_Litter = ws;
    return ret;
  }
    
  public void setParents( Collection p )
  {
    m_Parents = new VectorSet( p );
  }

  /** only used for deleting widgets */
  public void removeParent( Widget w )
  {
    m_Parents.remove( w );
  }

  /** only used for undoing widget deletion */
  public void addParent( Widget w )
  {
    m_Parents.add( w );
  }

  public Iterator getChildrenIterator()
  {
    return m_Children.iterator();
  }

  public Collection getChildren()
  {
    return m_Children;
  }

  public Collection getAllChildren()
  {
    HashSet h = new HashSet();

    Iterator it = getChildrenIterator();
    while ( it.hasNext() ) {
      FamilyMember w = (FamilyMember)it.next();
      h.addAll( w.getAllChildren() );
      if ( w instanceof Widget ) {
	h.add( w );
      }
    }
    
    return h;
  }

  public Iterator getParentsIterator()
  {
    return getParents().iterator();
  }

  public Collection getParents()
  {
    return m_Parents;
  }

  public HashSet getAncestors()
  {
    HashSet ancestors = new HashSet();
    
    Iterator it = getParentsIterator();

    while ( it.hasNext() ) {
      Widget p = (Widget)it.next();
      ancestors.add( p );
      //if ( !p.inLitter() ) {
      ancestors.addAll( p.getAncestors() );
      //}
    }
    
    return ancestors;
  }

  // ada: get the start time of the widget
  // it may have many components - return the time of the earliest one
  // here I assume that the rootAncestors are Squiggles, but if none are,
  // (utterances??) then just return 0
  public long getStartTime()
  {
    long time = -1;
    Iterator roots = getRootAncestors().iterator();

    while (roots.hasNext()) {
      Object n = roots.next();
      if (n instanceof Squiggle) {
	Squiggle s = (Squiggle) n;
	long nTime = s.getStartTime();
	if ((time == -1) || (nTime < time)) {
	  time = nTime;
	}
      }
    }

    if (time == -1) return 0; // no squiggles
    return time;
  }

  // ada: get the end time of the widget
  // it may have many components - return the time of the latest one
  // here I assume that the rootAncestors are Squiggles, but if none are,
  // (utterances??) then just return 0
  public long getEndTime()
  {
    long time = -1;
    Iterator roots = getRootAncestors().iterator();

    while (roots.hasNext()) {
      Object n = roots.next();
      if (n instanceof Squiggle) {
	Squiggle s = (Squiggle) n;
	long nTime = s.getEndTime();
	if ((time == -1) || (nTime > time)) {
	  time = nTime;
	}
      }
    }

    if (time == -1) return 0; // no squiggles
    return time;
  }

  public Collection getRootAncestors()
  {
    HashSet ancestors = new HashSet();
    
    if ( isRoot() ) {
      ancestors.add( this );
    }

    Iterator it = getParentsIterator();

    while ( it.hasNext() ) {
      Widget p = (Widget)it.next();
      if ( p.isRoot() ) {
	ancestors.add( p );
      }
      ancestors.addAll( p.getRootAncestors() );
    }

    return ancestors;
  }

  public Collection getAttached()
  {
    return getAttached( new HashSet() );
  }
  
  public Collection getAttached( Collection old_roots )
  {
    WidgetSet affectedRoots = new WidgetSet( connectedRoots() );
    
    affectedRoots.addAll( old_roots );
    
    Iterator allconf_it = affectedRoots.iterator();
    HashSet all_conflicts = new WidgetSet();
    while ( allconf_it.hasNext() ) {
      Widget another_conflict = (Widget)allconf_it.next();
      all_conflicts.addAll( another_conflict.getAllChildrenAndMe() );
    }

    return all_conflicts;
  }
  
  // returns nodes which are not really roots but are part of a set 
  // so sometimes behave as roots.
  public Collection getGlassRootAncestors()
  {
    HashSet glassAncestors = new HashSet();

    Iterator it = getParentsIterator();
    
    while ( it.hasNext() ) {
      Widget p = (Widget)it.next();
      if ( p.inLitter() ) {
	glassAncestors.add( p );
      }
      glassAncestors.addAll( p.getGlassRootAncestors() );
    }

    return glassAncestors;
  }
  
  // for debugging
  protected void printAncestors( Widget w )
  {
    Iterator it = w.getParentsIterator();
    
    Console.indent();

    while( it.hasNext() ) {
      Widget p = (Widget)it.next();
      Console.writeln( "" + p );
      printAncestors( p );
    }
    Console.outdent();
  }

  //for debugging
  private void printChildren( FamilyMember w )
  {
    Iterator it = w.getChildrenIterator();
    
    Console.indent();

    while( it.hasNext() ) {
      FamilyMember c = (FamilyMember)it.next();
      Console.writeln( "" + c );
      printChildren( c );
    }
    Console.outdent();
  }

  // for debugging
  public void printFamily()
  {
    Console.writeln();
    Console.writeln( "ANCESTORS of " + this );
    printAncestors( this );
    Console.writeln( "CHILDREN of " + this );
    printChildren( this );
  }

  // for debugging
  public void printConflicts()
  {
    Console.writeln( "CONFLICTS of " + this );
    Console.indent();
    Collection c = allConflicts();
    Iterator it = c.iterator();
    while ( it.hasNext() ) {
      Console.writeln( it.next().toString() );
    }
    Console.outdent();
  }

  /* all roots that we can see through connection from this widget */
  public Collection connectedRoots()
  {
    HashSet roots = new HashSet();
    HashSet currentRoots = new HashSet( getRootAncestors() );

    while ( currentRoots.size() > 0 ) {
      HashSet additions = new HashSet();
      Iterator it = currentRoots.iterator();
      while ( it.hasNext() ) {
	Widget w = (Widget)it.next();
	Iterator child_it = w.getAllChildren().iterator();
	while ( child_it.hasNext() ) {
	  FamilyMember f = (FamilyMember)child_it.next();
	  if ( f instanceof Widget ) {
	    additions.addAll( ((Widget)f).getRootAncestors() );
	  }
	}
	roots.add( w );
      }
      currentRoots.addAll( additions );
      currentRoots.removeAll( roots );
    }	
    
    return roots;
  }

  public Collection allConflicts()
  {
    // gets all conflicts, including us
    Collection affected;

    affected = sConflicts( null );

    return affected;
  }
  
  public Collection conflicts()
  {
    
    Collection affected;
    affected = sConflicts( null );
    
    // remove ourself because we are not in conflict with ourself
    if ( m_Litter != null ) {
      affected.removeAll( m_Litter.getAllChildren() );
    }
    else {
      affected.removeAll( getAllChildrenAndMe() );
    }

//      Console.writeln( "affected widgets" );
//      Iterator it = affected.iterator();
//      while ( it.hasNext() ) {
//        Console.writeln( "" + (Widget)it.next() );
//      }

    return affected;
    
  }

  private Collection sConflicts( FamilyMember f ) 
  {
    HashSet broken = new HashSet();

    Iterator it = getParentsIterator();
    while ( it.hasNext() ) {
      Widget w = (Widget)it.next();
      if ( inLitter() ) {
	broken.addAll( w.sConflicts( m_Litter ) );
      }
      else {
	broken.addAll( w.sConflicts( this ) );
      }
    }

    it = getChildrenIterator();
    while ( it.hasNext() ) {
      FamilyMember w = (FamilyMember)it.next();
      if ( !w.equals( f ) ) {
	broken.addAll( w.getAllChildrenAndMe() );
      }
    }

    broken.add( this );
    
    return broken;
  }

  public Collection getAllChildrenAndMe()
  {
    Collection c = this.getAllChildren();
    c.add( this );
    return c;
  }

  public Collection getLitter()
  {
    if ( !inLitter() ) {
      return null;
    }
    
    HashSet sibs = new HashSet( m_Litter );
    sibs.remove( this );

    //    Console.writeln( sibs.toString() );
    
    return sibs;
  }

  /** 
   * kills all the conflicts of the widget and then returns them
   * in a large dark box
   */
  public Collection certain()
  {
    Collection conf = conflicts();
    Iterator it = conf.iterator();
    
    while ( it.hasNext() ) {
      FamilyMember f = (FamilyMember)it.next();
      f.die();
    }

    return conf;
  }

  public Collection uproot()
  {
    Collection conf = allConflicts();
    Iterator it = conf.iterator();
    
    while ( it.hasNext() ) {
      FamilyMember f = (FamilyMember)it.next();
      f.die();
      Console.writeln( f + " is a conflict" );
    }

    return conf;
  }


  public boolean relatedToOneOf( Collection wids )
  {
    Iterator it = wids.iterator();
    while ( it.hasNext() ) {
      if ( relatedTo( (Widget)it.next() ) ) {
	return true;
      }
    }

    return false;
      
  }
  
  public boolean relatedTo( Widget w )
  {
    if ( w.equals( this ) )
      return true;
    
    if ( childOf( w ) )
      return true;

    if ( parentOf( w ) )
      return true;

    return false;
  }


  public boolean childOf( Widget w )
  {
    Iterator it = getParentsIterator();

    while( it.hasNext() )
      {
	Widget f = (Widget)it.next();

	if ( f.equals( w ) )
	  return true;

	if ( f.childOf( w ) )
	  return true;
      }

    return false;
  }

  public boolean parentOf( Widget w )
  {
    Iterator it = getChildrenIterator();

    while( it.hasNext() )
      {
	FamilyMember f = (FamilyMember)it.next();

	if ( f.equals( w ) )
	  return true;

	if ( f.parentOf( w ) )
	  return true;
      }

    return false;
  }


  // don't remove the links, just set the die flag so that others can 
  // see where we came from and choose a replacement for us.
  public void die()
  {
    m_Pulse = false;
  }

  public void revive()
  {
    m_Pulse = true;
  }

  public boolean alive()
  {
    return m_Pulse;
  }

  public boolean isRoot() 
  {
    return m_Parents.isEmpty();
  }
  
  public boolean inLitter()
  {
    return ( m_Litter != null );
  }

  public boolean hasChildren()
  {
    return !m_Children.isEmpty();
  }

  public int numChildren()
  {
    return m_Children.size();
  }

  ///////////////////////////////////////////////////////////////////////////
  // some stuff on strength.  There is a range from 0-10, just so that 
  // we have some fixed concept of which values indicate strong, without 
  // having to know about the values of the other widgets.
  //----------------------------------------
  // Strength will be used by the resolution method of the surface to 
  // determine which widgets to display, and which to throw out.
  //----------------------------------------
  // no longer limited by a range.  i think that we should perhaps
  // do something to keep strength under control, but for now there is
  // no limit on anything until i think of something to do about it.
  // perhaps an idea of strengthening in discrete qts instead of any amt.
  // ---------------------------------------
  // these little comments need to be changed again...
  

  // don't use these anymore

//    // not public so people forced to use Reasoner's strengthen and weaken
//    //             -lw
//    void strengthen( double amt )
//    {
//      Console.writeln( "strengthening " + this + " by " + amt );
//      m_Strength += amt;
//    }

//    // not public so people forced to use Reasoner's strengthen and weaken
//    //             -lw
//    void weaken( double amt )
//    {
//      Console.writeln( "weakening " + this + " by " + amt );
//      m_Strength -= amt;
//    }

  // next functions not public since they should only be used in Surface and
  // the sys package
  void setRelStrength( int amt )
  {
    Assert.check( (amt >= 0) && (amt <= maxStrength) );
    m_RelStrength = amt;
    //Console.writeln( "setting " + this + "'s relative strength to " + amt );
  }

  static int getMaxStrength()
  {
    return maxStrength;
  }

  /** -1 for all the way negative, 0 for 0, and 1 for all the way up */
  void setUserStrength( int amt )
  { 
    if ( amt < 0 ) {
      m_UserStrength = (-1 * maxStrength) - 1;
      //     Console.writeln("setting user strength of " + this + " to " +
      //	      ((-1 * maxStrength) - 1 ));
    }
    else if ( amt > 0 ) {
      m_UserStrength = maxStrength + 1;
      //Console.writeln("setting user strength of " + this + " to " +
      //	      (maxStrength + 1));
    }
    else {
      m_UserStrength = 0;
      //Console.writeln("setting user strength of " + this + " to 0");
    }
  }
  
  public int getStrength()
  {
    return m_RelStrength + m_UserStrength;
  }

  ///////////////////////////////////////////////////////////////////////////
  // Elect and Impeach: a widget has been elected to show itself on the 
  // surface, but must keep track of the changes which it makes in the
  // process so that it can undo things if it is impeached.
  ///////////////////////////////////////////////////////////////////////////
  // for now all the widget does is add and remove itself to and
  // from the surface.  This method can be overridden
  public void elect( Surface s )
  {
    Console.writeln( "electing widget " + this );
    
    if ( m_Suitcase == null ) {
      makeFirstSuitcase( s );
    }

    // unpack the suitcase of transitions
    // if not already elected
    if ( m_Suitcase.isOpen() ) {
      Assert.note( "Widget " + this + " already elected" );
      return;
    }
    else {
      m_Suitcase.unpack();
      Iterator it = m_Suitcase.getActions().iterator();
      Console.indent();
      while ( it.hasNext() ) {
	Console.writeln( "" + it.next() );
      }
      Console.outdent();
    }
  }

  public void impeach()
  {
    Console.writeln( "impeaching widget " + this );
    
    // pack up (undo) all the transitions from the suitcase, as long as 
    // we have been elected
    if ( !m_Suitcase.isOpen() ) {
      return;
    }
    else {
      m_Suitcase.repack();
    }
  }

  public boolean isElected()
  {
    if ( m_Suitcase == null ) {
      return false;
    }
    return m_Suitcase.isOpen();
  }

  /* default trasition suitcase for widget election.  Put the widget on the 
   * surface 
   */
  /* THIS MUST BE PRIVATE TO AVOID BAD ERRORS */
  private final void makeFirstSuitcase( Surface s ) 
  { 
    m_Suitcase = new TransitionSuitcase();

    makeDefaultSuitcase( s );
  } 
  
  protected abstract void makeDefaultSuitcase( Surface s );

  public void addToSuitcase( DoTransition t )
  {
    if ( m_Suitcase == null ) {
      makeFirstSuitcase( t.getSurface() );
    }
    m_Suitcase.addTransition( t );
  }

  public void addToBeginningOfSuitcase( DoTransition t )
  {
    if ( m_Suitcase == null ) {
      makeFirstSuitcase( t.getSurface() );
    }
    m_Suitcase.addTransitionToBeginning( t );
  }

}
