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

// -*-Mode: Java-*- 
// $Id: SpringAdjuster.java,v 1.2 2003/03/06 00:40:43 moltmans Exp $
// SpringAdjuster.java -- 
// Author: Michael Oltmans <moltmans@ai.mit.edu> 
// Created: <Wed Apr  5 13:05:14 2000> 
// Time-stamp: <2000-04-18 16:31:40 moltmans> 
package rec.annotate;

import java.awt.Rectangle;
import java.util.*;
import rec.util.*;
import rec.geo.*;
import rec.sys.*;
import rec.core.widgets.*;
import rec.simulate.widgets.*;

/**
 * This class helps springs set their parameters such that they can
 * achieve their desired effects.
 **/
public class SpringAdjuster implements Adjuster
{
  protected WSimSpring m_spring;
  protected MovementAnnotation m_movement;
  protected MovementAnnotation m_goalEffect;

  /**
   * @param movement an annotation that describes the movement of a body
   *               connected to a spring
   **/
  public SpringAdjuster( MovementAnnotation movement ) 
  {
    m_movement = movement;
    m_spring = findSpringAttachedTo( movement.getMovee().getWidget() );
  }

  public boolean hasSpring() 
  {
    return m_spring != null;
  }
  
  /**
   * Set the rest length so that it moves the mover in the direction
   * indicated by the arrow far enough that it hits movee.
   **/
  protected void setRestLength( WSimBody mover, WSimBody movee,
				WSquiggleArrow arrow )
  {
    // verify attachment of mover to spring
    if( !hasSpring() || !mover.getConstraints().contains( m_spring ) ) {
      return;
    }
    
    double restLength;
    double compression = getCompression( mover, arrow );
	
    Rectangle b1 = movee.getBounds();
    Rectangle b2 = mover.getBounds();

    // if they intersect just do what getCompression does
    if( !b1.intersects( b2 ) ) {
      double dist = Geometry.distance( movee.getLocation().getCenter(),
				       mover.getLocation().getCenter() );
      if( compression > 0 )
	restLength = m_spring.getLength() + dist;
      else
	restLength = m_spring.getLength() - dist;
    }
    else {
      restLength = m_spring.getLength() * compression;
    }
    
    m_spring.setRestLength( restLength );
  }

  /**
   * Set the rest length so that it moves the body in the direction
   * indicated by the arrow.
   **/   
  protected void setRestLength( WSimBody movee, WSquiggleArrow arrow ) 
  {
    // verify attachment of movee to spring
    if( !hasSpring() || !movee.getConstraints().contains( m_spring ) ) {
      return;
    }
    
    double compression = getCompression( movee, arrow );
    
    m_spring.setRestLength( m_spring.getLength() * compression );
  }
  
  /**
   * Check if the arrow points in the same direction as angle.
   * (within PI/4 degrees in either direction.
   *
   * @return less than 1.0 if in tension.
   *         greater than 1.0 if in compression.
   *         equal to 1.0 if at rest.
   **/
  protected double getCompression( WSimBody body, WSquiggleArrow arrow ) 
  {
    Assert.check( hasSpring(), "No Spring for this adjuster." );
    if( arrow == null )
      return 1.0;

    double angle = 0.0;
    if( body.equals( m_spring.getHeadBody() ) ) {
      angle = Geometry.angle( m_spring.getTail(),
			      m_spring.getHead() );
    }
    else if( body.equals( m_spring.getTailBody() ) ) {
      angle = Geometry.angle( m_spring.getHead(),
			      m_spring.getTail() );
    }
    else {
      Assert.error( "Calling setCompression with bad body" );
    }

    double diff = Math.abs( angle - arrow.getTailAngle() );

    if( Radian.isBetween( -0.25 * Math.PI, 0.25 * Math.PI, diff ) ) {
      return 1.2;
    }
    else if( Radian.isBetween( 0.75 * Math.PI, 1.25 * Math.PI, diff ) ) {
      return 0.8;
    }

    return 1.0;
  }

  public void setGoal( Annotation a )
  {
    if( a == null || !(a instanceof MovementAnnotation) )
      return;
    m_goalEffect = (MovementAnnotation)a;
  }

  public void adjust() 
  {
    if( !hasSpring() )
      return;
    if( m_goalEffect == null ) {
      setRestLength( (WSimBody)m_movement.getMovee().getWidget(),
		     m_movement.getArrow() );
    }
    else {
      setRestLength( (WSimBody)m_movement.getMovee().getWidget(),
		     (WSimBody)m_movement.getMovee().getWidget(),
		     m_movement.getArrow() );
    }
  }

  public Annotation getCause() 
  {
    return m_movement;
  }

  public Annotation getEffect()
  {
    return m_goalEffect;
  }

  public static WSimSpring findSpringAttachedTo( Widget w )
  {
    if( (w != null) && (w instanceof WSimBody) ) {
      Iterator it = ((WSimBody)w).getConstraints().iterator();

      while( it.hasNext() ) {
	WSimConstraint constraint = (WSimConstraint)it.next();
	if( constraint instanceof WSimSpring ) {
	  return(WSimSpring)constraint;
	}
      }
    }
    return null;
  }

}
