//<pre>

package actor;

import java.util.Properties;
import java.util.StringTokenizer;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;

/**
 * $Id: KeyFrameAnimation.java,v 1.22 2004/03/08 08:12:47 awang Exp $
 * These are Immutable: All getters should return copies of Point3D,
 * not originals and no public/protected setter methods or variables..
 */
public class KeyFrameAnimation
{
  private String name;
  private KeyFrame[] frames;
  private double rate, travel;
  private int repeats, frameCount;

  // Should be called only by animationFactory
  //
  // but subclasses would have trouble instantiating 
  // themselves. I'm subclassing this for my keyframe 
  // editor. -a
  //
  protected KeyFrameAnimation
    (String name, KeyFrame[] frames, double rate, double travel)
  {
    this( name, frames, rate, travel, Integer.MAX_VALUE);
  }
  
  // Should be called only by animationFactory
  KeyFrameAnimation(Properties properties)
  {
    this.setProperties(properties);
  }
  
  // Should be called only by animationFactory
  protected KeyFrameAnimation(File file) throws IOException, 
                FileNotFoundException{
    setProperties(file);
  }
  
  // Should be called only by animationFactory
  KeyFrameAnimation
   (String name, KeyFrame[] frames, double rate, double travel, int repeats)
  {      
    this.name = name;
    this.frames = frames;
    this.rate = rate; 
    this.travel = travel;
    this.repeats = repeats;
    this.frameCount = frames.length;
  }

  public Properties getProperties()  
  {
    Properties p = new Properties();
    p.setProperty("name", this.getName());
    p.setProperty("rate",  this.getRate()+"");
    p.setProperty("travel", this.getTravel()+"");
    p.setProperty("frames", this.framesToString());
    p.setProperty("frameCount", this.getFrameCount()+"");
    return p;
  }
  
  /**
   * Returns properties in string format.
   */  
  public String toString()
  {
    return getProperties().toString();    
  }
  
  /**
   * file contains output of toString()
   */
  
  private void setProperties(File file) throws FileNotFoundException, IOException
  {
   
    BufferedReader fileReader = new BufferedReader(new FileReader(file));
                
    String nextLine = fileReader.readLine();
    String input = "";
                
    while(nextLine != null){
                  
      input += "\n" + nextLine;
                    
      nextLine = fileReader.readLine();
                  
    }
                  
    setProperties(input);
    
  }
  
  /** a - where is this guy called from?? -dh
   * What do you mean? - a
   * 
   * Given output of toString, convert strings that come 
   * from a Properties back into a Properties object. Will 
   * (currently) fail if there's a ", " inside one of the 
   * values of a property.
   */  
  private void setProperties(String properties)
  {    
    Properties prop = new Properties();    
    String withoutBraces = properties.substring(1, properties.length() - 1);
    
    // between i_delimiter and i_nextDelimeter is the
    // next pair that's being parsed    
    int i_delimiter = -1;    
    int i_nextDelimiter = withoutBraces.indexOf(", ", i_delimiter + 1);    
    do
    {
      // property value pair
      String nextPair = withoutBraces.substring(i_delimiter + 2, i_nextDelimiter);

      int i_equals = nextPair.indexOf("=");
      String property = nextPair.substring(0,i_equals);
      String value = nextPair.substring(i_equals + 1, nextPair.length());
      
      prop.setProperty(property,value);
      
      i_delimiter = i_nextDelimiter;    
      i_nextDelimiter = withoutBraces.indexOf(", ", i_delimiter + 1);
      
      if (i_nextDelimiter == -1)
      {       
        // the last pair doesn't have another delimiter at its end
        i_nextDelimiter = withoutBraces.length();        
      }      
    }
    while(i_delimiter != withoutBraces.length());    
      setProperties(prop);    
  }
  
  private void setProperties(Properties p)
  {
    this.name = p.getProperty("name");   
    this.rate = Double.parseDouble(p.getProperty("rate"));
    this.travel = Double.parseDouble(p.getProperty("travel"));
    this.frameCount = Integer.parseInt(p.getProperty("frameCount"));
    this.frames = new KeyFrame[frameCount];
    
    // fully initializing frames.
    for(int i = 0; i < frames.length; i++){
     
      frames[i] = new KeyFrame();
      
      Point3D[] positions = new Point3D[6];
      
      frames[i].setVertexPositions(positions);
      
      for(int j = 0; j < 6; j++){
       
        positions[j] = new Point3D(0,0,0);
        
      }
      
      
    }
    
    this.repeats = Integer.MAX_VALUE;
    
    // see if we have a repeat property
    String rpts = p.getProperty("repeats");
    if (rpts != null)
      this.repeats = Integer.parseInt(rpts);
    
    // convert the frames from property String
    this.setFramesFromString(p.getProperty("frames"));
  }
    
  // a- this guy seems to throw a NullPointer on currentFrame - dh
  void setFramesFromString(String framesAsString) 
  {   
    // beginning of
    int beginNextFrame = framesAsString.indexOf("[");
    int frameNum = 0;
    
    while (true)
    {    
      KeyFrame currentFrame = this.frames[frameNum];
        
      // what comes after "[" and before the comma after that.        
      currentFrame.setCycleTime(Double.parseDouble
        (framesAsString.substring(beginNextFrame + 1,
        framesAsString.indexOf(",",beginNextFrame))));
        
      Point3D[] vertexPos = currentFrame.getVertexPositions();
        
      // next position after the begin frame marker
      int beginNextPos = framesAsString.indexOf("[",beginNextFrame + 1);        
      for(int i = 0; i < vertexPos.length; i++)
      {        
        Point3D currentPos = vertexPos[i];            
        if(beginNextPos == -1){             
          throw new RuntimeException("Properties in bad format");                
        }
            
        String posAsString = framesAsString.substring
          (beginNextPos + 1, framesAsString.indexOf("]",beginNextPos + 1));
            
        // a little bit inefficient, but it's not like this is 
        // going to be called every frame.
        StringTokenizer tok = new StringTokenizer(posAsString,",");            
        double x = Double.parseDouble(tok.nextToken());
        double y = Double.parseDouble(tok.nextToken());
        double z = Double.parseDouble(tok.nextToken());
             
        currentPos.set(x,y,z);            
        beginNextPos = framesAsString.indexOf("[",beginNextPos + 1);            
      }        
      frameNum++;
      beginNextFrame = framesAsString.indexOf("][",beginNextFrame + 1) + 1;    
      if(beginNextFrame <= 0) break;
    }      
  }
    
  String framesToString()
  {
    String fs = "";
    for (int i = 0; i < frames.length; i++)
    {
      KeyFrame kf = frames[i];
      Point3D[] vps = kf.getVertexPositions();
      fs += "["+ kf.getCycleTime() + ","; 
      for (int j = 0; j < vps.length; j++) {
        fs += vps[j];
        if (j < vps.length-1)
          fs += ",";
      }
      fs += "]";
    }
    return fs;
  }

  public Point3D[] getVertexOffsets(double elapsedTime, double beat)
  {        
    KeyFrame lerpFrame = calculateInterpolatedAnimationFrame(elapsedTime, beat);  
    return lerpFrame.getVertexPositions();        
  }
  
  public KeyFrame calculateInterpolatedAnimationFrame(
    double elapsed,
    double beat)
  { 
    int keyIdx = (int)beat;
    KeyFrame kf0 = getFrame(keyIdx);
    KeyFrame kf1 = getFrame((keyIdx + 1) % getFrameCount());
    Point3D[] lerpPoint = KeyFrame.keyFrameLerp(beat, kf0, kf1);        
    return new KeyFrame(lerpPoint);
  }

  public int getFrameCount()
  {
    if (frames == null || frames.length==0)
      return frameCount;
    return frames.length;
  }

  private void setFrameCount(int frames)
  {
    this.frameCount = frames;
  }

  public KeyFrame[] getFrames()
  {
    return frames;
  }

  public KeyFrame getFrame(int idx)
  {
    return frames[idx];
  }

  public double getRate()
  {
    return rate;
  }

  public double getTravel()
  {
    return travel;
  }

  public String getName()
  {
    return name;
  }

}// end