//[skeleton0.
package visual.dynamic.described;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;

/**
 * A TweeningSprite is a Sprite that contains "key times" and the ability
 * to generate "in between times"
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public abstract class TweeningSprite extends AbstractSprite
{
    private   double           frac;
    private   int              currentIndex, endState, lastTime;
    private   int              localTime, nextIndex, nextKT;
    protected Vector<Integer>  keyTimes;
    protected Vector<Point2D>  locations;
    protected Vector<Double>   rotations, scalings;
    

    public static final int REMAIN         = 0;
    public static final int REMOVE         = 1;
    public static final int REPEAT         = 2;

    


    /**
     * Default Constructor
     */
    public TweeningSprite()
    {       
       super();
       
       keyTimes  = new Vector<Integer>();
       locations = new Vector<Point2D>();
       rotations = new Vector<Double>();
       scalings  = new Vector<Double>();

       localTime =  0;
       lastTime  = -1;
       initialize();
    }
//]skeleton0.
//[addKeyTime.

    /**
     * Add a key time
     *
     * @param keyTime     The time
     * @param location    The location of the sprite at this time
     * @param rotation    The rotation of the sprite (null to align with path)
     * @param scaling     The scaling of the sprite at this time
     *
     * @return   The index of this key time (or -1 if the key time exists)
     */
    protected int addKeyTime(int keyTime, Point2D location,
                             Double rotation, Double scaling)
    {
       boolean     keepLooking;
       int         existingKT, i, index;


       existingKT  = -1;
       keepLooking = true;
       
       i = 0;
       while ((i < keyTimes.size()) && keepLooking)
       {
          existingKT = ((Integer)keyTimes.get(i)).intValue();
          
          if (existingKT >= keyTime) keepLooking = false;
          else                       i++;
       }

       if ((existingKT == i) && !keepLooking)  // Duplicate
       {
          i = -1;
       }
       else
       {
          keyTimes.insertElementAt(new Integer(keyTime), i);
          locations.insertElementAt(location, i);
          rotations.insertElementAt(rotation, i);
          scalings.insertElementAt(scaling, i);
       }
       
       return i;
    }
//]addKeyTime.    



    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (in milliseconds)
     */
    public void handleTick(int time)
    {
       boolean         aligned;
       int             currentKT, duration, finalKT, firstKT;


       //TODO The re-start stuff is a MESS!!!

       finalKT   = keyTimes.lastElement().intValue();
       firstKT   = keyTimes.firstElement().intValue();

       if (time == 0)
       {
          lastTime     = -1;
          currentIndex = 0;
          nextIndex    = 0;          
       }
       
       

       // Calculate the local time for this sprite
       if (lastTime < 0) localTime = 0;
       else              localTime += (time - lastTime);

       // Handle end states
       if (localTime > finalKT)
       {
          
          if (endState == REMOVE)
          {
             setVisible(false);
             return;
          } 
          else if (endState == REMAIN)
          {
             return;
          }

          localTime = 0;
       }

       // Record the last time
       lastTime  =  time;

       // Re-initialize if necessary
       if (localTime == 0) reinitialize();
       

       // Set the visibility
       if ((localTime < finalKT) && (localTime >= firstKT)) setVisible(true);


       
       // Find the important indexes and times
       //
       if (localTime == nextKT) // Move to the next key time
       {
          currentIndex++;

          if (localTime == finalKT)        // The last key time
          {
             nextIndex = currentIndex;
          }
          else                             // An intermediate key time
          {
             nextIndex = currentIndex + 1;
          }
       }
       else                   // Use the current key time
       {
          nextIndex = currentIndex + 1;
       }


       // Find the current and next key times
       currentKT = keyTimes.get(currentIndex).intValue();
       nextKT    = keyTimes.get(nextIndex).intValue();


       // Determine the interpolation fraction
       if (nextKT == currentKT) 
       {
          frac = 1.0;
       }
       else
       {
          frac = (double)(localTime - currentKT)/(double)(nextKT - currentKT);
       }



       // Tween the location, rotation and scale
       tweenLocation(currentIndex, nextIndex, frac);
       tweenScaling(currentIndex, nextIndex, frac);
       tweenRotation(currentIndex, nextIndex, frac);
    }

    /**
     * Get the interpolation fraction
     *
     * @return  The interpolation fraction (in [0, 1])
     */
    protected double getInterpolationFraction()
    {
       return frac;
    }
    

    /**
     * Get the active key time index
     *
     * @return The index of the "most recent" key time
     */
    protected int getKeyTimeIndex()
    {
       return currentIndex;
    }
    



    /**
     * Get the next key time index
     *
     * @return The index of the "next" key time
     */
    protected int getNextKeyTimeIndex()
    {
       return nextIndex;
    }
    

    /**
     * Initialize state variables
     */
    protected void initialize()
    {
       currentIndex = -1;   // The index into the array of key times 
       nextKT       =  0;   // The next key time
       frac         =  0.0; // The interpolation fraction
    }


    /**
     * Re-initialize state variables
     */
    protected void reinitialize()
    {
       initialize();
       super.reinitialize();
    }
    



    /**
     * Set the "end state" for this Sprite
     *
     * @param endState   Either REMOVE, REMAIN, or REPEAT
     */
    public void setEndState(int endState)
    {
       this.endState = REMOVE;
       if      (endState == REMAIN) this.endState = endState;
       else if (endState == REPEAT) this.endState = endState;
    }

//[tweenLocation.    

    /**
     * Determine the current 'tweened location
     * 
     * @param currentIndex   The index of the current key time
     * @param nextIndex      The index of the next key time
     * @param frac           The interpolation fraction
     */
    protected void tweenLocation(int currentIndex, int nextIndex, 
                                 double frac)
    {
       double          x, y;
       Point2D         currentKTLocation, nextKTLocation;
       
       currentKTLocation = locations.get(currentIndex);
       nextKTLocation    = locations.get(nextIndex);

       x = currentKTLocation.getX() + 
           frac*(nextKTLocation.getX()- currentKTLocation.getX());
       y = currentKTLocation.getY() + 
          frac*(nextKTLocation.getY() - currentKTLocation.getY());

       setLocation(x, y);
    }
//]tweenLocation.
//[tweenRotation.

    /**
     * Determine the current 'tweened rotation
     * 
     * @param currentIndex   The index of the current key time
     * @param nextIndex      The index of the next key time
     * @param frac           The interpolation fraction
     */
    protected void tweenRotation(int currentIndex, int nextIndex, 
                                 double frac)
    {
       double          currentKTRotation, nextKTRotation, r;
       Double          rotation;
       Point2D         currentKTLocation, nextKTLocation;


       rotation = rotations.get(currentIndex);

       if (rotation == null)  // aligned with the line segment
       {
          currentKTLocation = locations.get(currentIndex);
          nextKTLocation    = locations.get(nextIndex);

          r=Math.atan((nextKTLocation.getY()-currentKTLocation.getY())/
                      (nextKTLocation.getX()-currentKTLocation.getX()) );
       }
       else                  // pure rotation tweening
       {
          currentKTRotation = rotation.doubleValue();

          rotation = rotations.get(nextIndex);
          if (rotation == null)
          {
             nextKTRotation = currentKTRotation;
          }
          else
          {
             nextKTRotation    = rotation.doubleValue();
          }
          
          r = currentKTRotation + frac*(nextKTRotation-currentKTRotation);
       }
       
       setRotation(r);
    }
//]tweenRotation.   
//[ScaleTween.

    /**
     * Determine the current 'tweened scaling
     * 
     * @param currentIndex   The index of the current key time
     * @param nextIndex      The index of the next key time
     * @param frac           The interpolation fraction
     */
    protected void tweenScaling(int currentIndex, int nextIndex, double frac)
    {
       double          currentKTScaling, nextKTScaling, s;

       currentKTScaling = scalings.get(currentIndex).doubleValue();
       nextKTScaling    = scalings.get(nextIndex).doubleValue();
       s = currentKTScaling + frac*(nextKTScaling - currentKTScaling);
       setScale(s);
    }
//]tweenScale.    
//[skeleton1.

}
//]skeleton1.
