/* Copyright (C) 2001-2004 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. */

package artofillusion.animation;

import artofillusion.*;
import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;
import java.awt.image.*;
import java.text.*;
import java.util.*;

/** This class generates a wireframe preview of an animation. */

public class AnimationPreviewer implements Runnable
{
  LayoutWindow window;
  double originalTime;
  ValueField widthField, heightField, startField, endField, fpsField;
  BLabel timeLabel, frameLabel;
  Scene theScene;
  ObjectInfo sceneCamera;
  BComboBox camChoice;
  CustomWidget canvas;
  Thread previewThread;
  BDialog display;
  Image theImage;
  Graphics imageGraphics;
  byte imageData[][];
  int sourceData[];
  MemoryImageSource imSource;
  NumberFormat format;
  
  static int currentCamera = 0, width = 320, height = 240, fps = 15;
  static double startTime = 0.0, endTime = 1.0;

  public AnimationPreviewer(LayoutWindow parent)
  {
    window = parent;
    theScene = window.getScene();

    // Find all the cameras in the scene.
    
    ObjectInfo obj;
    int i, count;
    
    for (i = 0, count = 0; i < theScene.getNumObjects(); i++)
    {
      obj = theScene.getObject(i);
      if (obj.object instanceof SceneCamera)
        count++;
    }
    if (count == 0)
    {
      new BStandardDialog("", Translate.text("noCameraError"), BStandardDialog.INFORMATION).showMessageDialog(window);
      return;
    }
    if (count <= currentCamera)
      currentCamera = 0;
    ObjectInfo cameras[] = new ObjectInfo [count];
    for (i = 0, count = 0; i < theScene.getNumObjects(); i++)
    {
      obj = theScene.getObject(i);
      if (obj.object instanceof SceneCamera)
        cameras[count++] = obj;
    }

    // Create the components.

    camChoice = new BComboBox();
    for (i = 0; i < cameras.length; i++)
      camChoice.add(cameras[i].name);
    camChoice.setSelectedIndex(currentCamera);
    widthField = new ValueField((double) width, ValueField.POSITIVE+ValueField.INTEGER);
    heightField = new ValueField((double) height, ValueField.POSITIVE+ValueField.INTEGER);
    startField = new ValueField(startTime, ValueField.NONE);
    endField = new ValueField(endTime, ValueField.NONE);
    fpsField = new ValueField(fps, ValueField.POSITIVE+ValueField.INTEGER);
    
    // Display a dialog with the various options.
    
    ComponentsDialog dlg = new ComponentsDialog(window, Translate.text("renderPreview"),
      new Widget [] {camChoice, startField, endField, widthField, heightField, fpsField},
      new String [] {Translate.text("Camera"), Translate.text("StartTime"), 
      Translate.text("EndTime"), Translate.text("Width"), Translate.text("Height"),
      Translate.text("FramesPerSec")});
    if (!dlg.clickedOk())
      return;
    currentCamera = camChoice.getSelectedIndex();
    sceneCamera = cameras[currentCamera];
    width = (int) widthField.getValue();
    height = (int) heightField.getValue();
    startTime = startField.getValue();
    endTime = endField.getValue();
    fps = (int) fpsField.getValue();
    originalTime = theScene.getTime();
    
    // Display a dialog to show the preview.
    
    display = new BDialog(window, Translate.text("Preview"), true);
    format = NumberFormat.getNumberInstance();
    format.setMaximumFractionDigits(3);
    ColumnContainer content = new ColumnContainer();
    display.setContent(BOutline.createEmptyBorder(content, ModellingApp.standardDialogInsets));
    content.setDefaultLayout(new LayoutInfo(LayoutInfo.WEST, LayoutInfo.HORIZONTAL, new Insets(2, 0, 2, 0), null));
    content.add(timeLabel = new BLabel());
    content.add(frameLabel = new BLabel());
    setLabels(0.0, 0);
    content.add(canvas = new CustomWidget());
    canvas.setPreferredSize(new Dimension(AnimationPreviewer.width, AnimationPreviewer.height));
    content.add(Translate.button("close", this, "doClose"), new LayoutInfo());
    display.pack();
    UIUtilities.centerDialog(display, window);
    display.setResizable(false);
    theImage = canvas.getComponent().createImage(width, height);
    imageGraphics = theImage.getGraphics();
    previewThread = new Thread(this);
    previewThread.start();
    display.setVisible(true);
  }
  
  /** Generate and display all of the frames in a loop. */

  public void run()
  {
    int totalFrames = (int) Math.ceil((endTime-startTime)*fps);
    if (totalFrames <= 0)
      totalFrames = 1;
    int dataSize = (int) Math.ceil(width*height/8.0);
    imageData = new byte [totalFrames][dataSize];
    Camera cam = new Camera();
    cam.setSize(width, height);
    long lastUpdate = 0L, ms, delay = 1000/fps;
    
    // In the first loop, we render all of the images.
    
    for (int i = 0; i < totalFrames; i++)
    {
      double time = startTime+i/(double) fps;
      setLabels(time, i);
      theScene.setTime(time);
      SceneCamera sc = (SceneCamera) sceneCamera.object;
      cam.setCameraCoordinates(sceneCamera.coords.duplicate());
      cam.setDistToScreen((height/200.0)/Math.tan(sc.getFieldOfView()*Math.PI/360.0));
      imageGraphics.setColor(Color.white);
      imageGraphics.fillRect(0, 0, width, height);
      imageGraphics.setColor(Color.black);
      for (int j = 0; j < theScene.getNumObjects(); j++)
      {
        ObjectInfo obj = theScene.getObject(j);
        if (!obj.visible)
          continue;
        drawObject(obj, cam, imageGraphics);
      }
      Graphics g = canvas.getComponent().getGraphics();
      g.drawImage(theImage, 0, 0, null);
      g.dispose();
      recordImage(imageData[i]);
      if (Thread.currentThread().isInterrupted())
        return;
      ms = System.currentTimeMillis();
      if (ms < lastUpdate+delay)
      {
        try
        {
          Thread.sleep(lastUpdate+delay-ms);
        }
        catch (InterruptedException ex)
        {
          return;
        }
      }
      lastUpdate = System.currentTimeMillis();
    }
    
    // In later loops, we simply retrieve the data from the array.
    
    sourceData = new int [width*height];
    imSource = new MemoryImageSource(width, height, sourceData, 0, width);
    imSource.setAnimated(true);
    theImage = Toolkit.getDefaultToolkit().createImage(imSource);
    while (!Thread.currentThread().isInterrupted())
    {
      for (int i = 0; i < totalFrames; i++)
      {
        double time = startTime+i/(double) fps;
        setLabels(time, i);
        retrieveImage(imageData[i]);
        Graphics g = canvas.getComponent().getGraphics();
        g.drawImage(theImage, 0, 0, null);
        g.dispose();
        ms = System.currentTimeMillis();
        if (ms < lastUpdate+delay)
        {
          try
          {
            Thread.sleep(lastUpdate+delay-ms);
          }
          catch (InterruptedException ex)
          {
            return;
          }
        }
        lastUpdate = System.currentTimeMillis();
      }
    }
  }
  
  /** Close the window. */
  
  private void doClose()
  {
    previewThread.interrupt();
    try
    {
      previewThread.join();
    }
    catch (InterruptedException ex)
    {
    }
    theScene.setTime(originalTime);
    imageGraphics.dispose();
    display.dispose();
  }

  /** Draw one object into an image. */
  
  private void drawObject(ObjectInfo obj, Camera cam, Graphics g)
  {
    Object3D theObject = obj.object;
    while (theObject instanceof ObjectWrapper)
      theObject = ((ObjectWrapper) theObject).getWrappedObject();
    if (theObject instanceof ObjectCollection)
    {
      Enumeration enum = ((ObjectCollection) theObject).getObjects(obj, true, theScene);
      while (enum.hasMoreElements())
      {
        ObjectInfo info = (ObjectInfo) enum.nextElement();
        CoordinateSystem coords = info.coords.duplicate();
        coords.transformCoordinates(obj.coords.fromLocal());
        cam.setObjectTransform(coords.fromLocal());
        info.draw(g, cam);
      }
      return;
    }
    cam.setObjectTransform(obj.coords.fromLocal());
    obj.draw(g, cam);
  }

  /* Set the labels for the current time and frame. */
  
  private void setLabels(double time, int frame)
  {
    timeLabel.setText(Translate.text("Time")+": "+format.format(time));
    frameLabel.setText(Translate.text("Frame")+": "+(frame+1));
  }
  
  /* Record the bitmap for an image. */
  
  private void recordImage(byte bytes[])
  {
    int data[];
    
    try
    {
      PixelGrabber pg = new PixelGrabber(theImage, 0, 0, -1, -1, true);
      pg.grabPixels();
      data = (int []) pg.getPixels();
    }
    catch (InterruptedException ex)
    {
      return;
    }
    for (int i = 0; i < data.length; i++)
    {
      if (data[i] == 0xFFFFFFFF)
        continue;
      int i1 = i/8, i2 = i&7;
      bytes[i1] |= 1<<i2;
    }
  }

  /* Copy a bitmap into the image. */
  
  public void retrieveImage(byte bytes[])
  {
    for (int i = 0; i < sourceData.length; i++)
    {
      int i1 = i/8, i2 = i&7;
      sourceData[i] = ((bytes[i1] & (1<<i2)) == 0 ? 0xFFFFFFFF : 0xFF000000);
    }
    imSource.newPixels();
  }
}