/* Copyright (C) 2002 by Peter Eastman

   This program is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT ANY 
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
   PARTICULAR PURPOSE.  See the GNU General Public License for more details. */


/** Encloses an IterAnimationScript, stores source code and other additional
  information
 
  @author Michael Butscher
*/


package artofillusion.script;

import java.io.*;
import artofillusion.*;
import artofillusion.ui.EditingWindow;
import javax.swing.JDialog;



// TODO(MB) Lot of error checking

public class IterAnimationScriptHolder
{
  /** Template for a new script */
  final static String TEMPLATE = "";
  
  /** Later, maybe other types will be allowed which implement
    other interfaces for serialization.
  */
  public static final byte INITSTATETYPE_SERIALIZABLE = 0;
  
  
  protected String sourceCode;
  protected IterAnimationScript theScript;
  
  protected Serializable initialState;
  protected double initialTime;
  protected Object lastState;
  protected double lastTime;  
  
  public IterAnimationScriptHolder()
  {
    sourceCode = TEMPLATE;
  }
  
  public IterAnimationScriptHolder(DataInputStream in) throws IOException
  {
    initFromStream(in);
  }
  
  public String getSourceCode()
  {
    return sourceCode;
  }
  
  public void setSourceCode(String sc)
  {
    sourceCode = sc;
    
    // Clear script object
    theScript = null;
    // Clear caches    
    initialState = null;
    lastState = null;
  }
  
  /** Creates and stores actual script object.
    Normally a direct call isn't necessary.
  */
  public void buildScript()
  {
    if (theScript == null)
    {   // Create if not existing already
      // Clear cache
      initialState = null;
      lastState = null;
      
//      if ("".equals(sourceCode))
//        return;  // No script
      
      try
      {
        theScript = ScriptRunner.parseIterAnimationScript(sourceCode);
      }
      catch (Exception ex)
      {
        theScript = null;
        ScriptRunner.displayError(ex, 1);
      }
    }
  }
  
  /** Creates and stores the initial state of the script.
  */
  public void rebuildInitialState(LayoutWindow window)
  {
    buildScript();
    if (theScript == null)
    {
      initialState = null;
      return;
    }
    
    try
    {
      initialState = theScript.createInitialState(window);
    }
    catch(Exception e)
    {
      ScriptRunner.displayError(e, 1);
      initialState = null;
      lastState = null;
      return;
    }
    
    initialTime = window.getScene().getTime();
    
    // Clear cache
    lastState = null;
  }
  
  /** Applies the changes defined by this script object to the LayoutWindow
    and its scene. {@link #rebuildInitialState()} must be called first.
  */
  public void applyToScene(Scene scene)
  {
    if (theScript == null || initialState == null)
      return;
    
    double destTime = scene.getTime();
    Object destState;
    
    try
    {
      if (initialTime == destTime)
      {
        theScript.realizeState(initialState, initialState, scene);
        return;
      }
      else
      {
        if (lastState != null)
        {
          if (lastTime == destTime)
          {
            theScript.realizeState(initialState, lastState, scene);
            return;          
          }
          else if (lastTime > destTime)
          {   // Move backward in time?
            if (!theScript.isBackwardTimeAllowed(initialState, lastState,
                destTime - lastTime, scene))
            {
              lastState = initialState;
              lastTime = initialTime;
            }
          }
        }
        else
        {
          lastState = initialState;
          lastTime = initialTime;
        }

        // If this point is reached, lastState and lastTime are valid in any case.

        double currtime = lastTime;
        Object currstate = lastState;
        double timestep = 1.0/scene.getFramesPerSecond();

        if (currtime > destTime)
          timestep = -timestep;

        while (Math.abs(currtime - destTime) > Math.abs(timestep))
        {  // Step by step to destination
          currstate = theScript.executeTimeStep(initialState, currstate,
              timestep, scene);
          currtime += timestep;
        }

        if (currtime != destTime)
        {  // Last little step
          currstate = theScript.executeTimeStep(initialState, currstate,
              destTime - currtime, scene);
          currtime = destTime;
        }

        // Save as new last state
        lastState = currstate;
        lastTime = currtime;

        theScript.realizeState(initialState, lastState, scene);
      }
    }
    catch (Exception ex)
    {
      lastState = null;
      ScriptRunner.displayError(ex, 1);
    }

  }
  
  public void edit(LayoutWindow parent)
  {
    //System.out.println("IrterAnimationScriptHolder1 "+sourceCode);
    IterAnimationScriptDialog d = new IterAnimationScriptDialog(parent, sourceCode);
    String source = d.getScriptSource();
    if (source == null)
      return;
    
    sourceCode = source;
    theScript = null;
    lastState = null;
    if (d.getAutoIterInit())
    {
      if (initialState == null)
        initialTime = 0.0;
      
      parent.setTime(initialTime);
      rebuildInitialState(parent);
    }
    else
      initialState = null;
    //System.out.println("IrterAnimationScriptHolder2 "+sourceCode);
  }
  
  public void initFromStream(DataInputStream in) throws IOException, InvalidObjectException
  {
    System.out.println("IterAnimationScriptHolder.initFromStream1");
    short version = in.readShort();

    if (version < 0 || version > 0)
      throw new InvalidObjectException("");
    
    sourceCode = in.readUTF();
    
    theScript = null;    
    buildScript();
    
    byte statetype = in.readByte();
    if (statetype != INITSTATETYPE_SERIALIZABLE)
      throw new InvalidObjectException("");
    else
    {
      System.out.println("IterAnimationScriptHolder.initFromStream2");
      ObjectInputStream ois = new ObjectInputStream(in);
      try
      {
        initialState = (Serializable)ois.readObject();
      }
      catch(ClassNotFoundException e)
      {
        throw new InvalidObjectException("");
      }
      System.out.println("IterAnimationScriptHolder.initFromStream3");
      initialTime = in.readDouble();
    }
    System.out.println("IterAnimationScriptHolder.initFromStream4");
  }
  
  public void writeToStream(DataOutputStream out) throws IOException
  {
    System.out.println("IterAnimationScriptHolder.writeToStream1");
    short version = 0;
    out.writeShort(version);
    
    out.writeUTF(sourceCode);
    System.out.println("IterAnimationScriptHolder.writeToStream2");
    
    byte statetype = INITSTATETYPE_SERIALIZABLE;
    out.writeByte(statetype);
    
    ObjectOutputStream oos = new ObjectOutputStream(out);
    System.out.println("IterAnimationScriptHolder.writeToStream3");
    try
    {
      oos.writeObject(initialState);
    }
    catch(NotSerializableException ne)
    {
      ne.printStackTrace();
      throw new IOException("The initial state of iterAnimation couldn't be serialized");
    }
    
    oos.flush();    
    System.out.println("IterAnimationScriptHolder.writeToStream4");
    out.writeDouble(initialTime);    
  }
}


