/* Copyright (C) 1999-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.object;

import artofillusion.*;
import artofillusion.animation.*;
import artofillusion.math.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.io.*;

/** PointLight represents a light source which emits light equally in all directions. */

public class PointLight extends Light
{
  double radius;

  static BoundingBox bounds;
  static WireframeMesh mesh;

  static
  {
    Vec3 vert[];
    double r1 = 0.05, r2 = 0.25, i, j, k;
    int ind1, ind2, from[], to[];

    bounds = new BoundingBox(-0.25, 0.25, -0.25, 0.25, -0.25, 0.25);
    vert = new Vec3 [28];
    from = new int [14];
    to = new int [14];
    vert[0] = new Vec3(r1, 0.0, 0.0);
    vert[1] = new Vec3(r2, 0.0, 0.0);
    from[0] = 0;
    to[0] = 1;
    vert[2] = new Vec3(-r1, 0.0, 0.0);
    vert[3] = new Vec3(-r2, 0.0, 0.0);
    from[1] = 2;
    to[1] = 3;
    vert[4] = new Vec3(0.0, r1, 0.0);
    vert[5] = new Vec3(0.0, r2, 0.0);
    from[2] = 4;
    to[2] = 5;
    vert[6] = new Vec3(0.0, -r1, 0.0);
    vert[7] = new Vec3(0.0, -r2, 0.0);
    from[3] = 6;
    to[3] = 7;
    vert[8] = new Vec3(0.0, 0.0, r1);
    vert[9] = new Vec3(0.0, 0.0, r2);
    from[4] = 8;
    to[4] = 9;
    vert[10] = new Vec3(0.0, 0.0, -r1);
    vert[11] = new Vec3(0.0, 0.0, -r2);
    from[5] = 10;
    to[5] = 11;

    r1 *= 0.57735; // 1/sqrt(3).
    r2 *= 0.57735;
    ind1 = 12;
    ind2 = 6;
    for (i = -1.0; i < 2.0; i += 2.0)
      for (j = -1.0; j < 2.0; j += 2.0)
	for (k = -1.0; k < 2.0; k += 2.0)
	  {
	    vert[ind1++] = new Vec3(r1*i, r1*j, r1*k);
	    vert[ind1++] = new Vec3(r2*i, r2*j, r2*k);
	    from[ind2] = ind1-2;
	    to[ind2++] = ind1-1;
	  }
    mesh = new WireframeMesh(vert, from, to);
  }

  public PointLight(RGBColor theColor, float theIntensity, double theRadius)
  {
    this(theColor, theIntensity, theRadius, false, 0.25f);
  }
  
  public PointLight(RGBColor theColor, float theIntensity, double theRadius, boolean isAmbient, float decay)
  {
    setParameters(theColor.duplicate(), theIntensity, isAmbient, decay);
    setRadius(theRadius);
    bounds = new BoundingBox(-0.25, 0.25, -0.25, 0.25, -0.25, 0.25);
  }

  public double getRadius()
  {
    return radius;
  }

  public void setRadius(double r)
  {
    radius = r;
  }

  public Object3D duplicate()
  {
    return new PointLight(color, intensity, radius, ambient, decayRate);
  }

  public void copyObject(Object3D obj)
  {
    PointLight lt = (PointLight) obj;

    setParameters(lt.color.duplicate(), lt.intensity, lt.ambient, lt.decayRate);
    setRadius(lt.radius);
  }

  public BoundingBox getBounds()
  {
    return bounds;
  }

  /* A PointLight is always drawn the same size, which has no connection to the properties
     of the light. */

  public void setSize(double xsize, double ysize, double zsize)
  {
  }

  public boolean canSetTexture()
  {
    return false;
  }

  public WireframeMesh getWireframeMesh()
  {
    return mesh;
  }

  public boolean isEditable()
  {
    return true;
  }

  public void edit(EditingWindow parent, ObjectInfo info, Runnable cb)
  {
    final Widget patch = color.getSample(50, 30);
    final ValueField intensityField = new ValueField(intensity, ValueField.NONE);
    final ValueField radiusField = new ValueField(radius, ValueField.NONNEGATIVE);
    final ValueField decayField = new ValueField(decayRate, ValueField.NONNEGATIVE);
    final BCheckBox ambientBox = new BCheckBox(Translate.text("Ambient"), ambient);
    RGBColor oldColor = color.duplicate();
    final BFrame parentFrame = parent.getFrame();

    patch.addEventLink(MouseClickedEvent.class, new Object() {
      void processEvent()
      {
        new ColorChooser(parentFrame, Translate.text("lightColor"), color);
        patch.setBackground(color.getColor());
      }
    });
    BButton applyButton = Translate.button("apply", new Object() {
      void processEvent()
      {
	setParameters(color, (float) intensityField.getValue(), ambientBox.getState(), 
	    (float) decayField.getValue());
	setRadius(radiusField.getValue());
        informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
      }
    }, "processEvent");
//    buttons.add(applyButton);
    ComponentsDialog dlg = new ComponentsDialog(parentFrame, Translate.text("editPointLightTitle"), 
	new Widget [] {patch, intensityField, radiusField, decayField, ambientBox, applyButton}, 
	new String [] {Translate.text("Color"), Translate.text("Intensity"), Translate.text("Radius"), Translate.text("decayRate"), null, null});
    if (!dlg.clickedOk())
    {
      color.copy(oldColor);
      return;
    }
    setParameters(color, (float) intensityField.getValue(), ambientBox.getState(),
        (float) decayField.getValue());
    setRadius(radiusField.getValue());
    cb.run();
  }

  /* The following two methods are used for reading and writing files.  The first is a
     constructor which reads the necessary data from an input stream.  The other writes
     the object's representation to an output stream. */

  public PointLight(DataInputStream in, Scene theScene) throws IOException, InvalidObjectException
  {
    super(in, theScene);

    short version = in.readShort();
    if (version != 0)
      throw new InvalidObjectException("");
    setParameters(new RGBColor(in), in.readFloat(), in.readBoolean(), in.readFloat());
    setRadius(in.readDouble());
  }

  public void writeToFile(DataOutputStream out, Scene theScene) throws IOException
  {
    super.writeToFile(out, theScene);

    out.writeShort(0);
    color.writeToFile(out);
    out.writeFloat(intensity);
    out.writeBoolean(ambient);
    out.writeFloat(decayRate);
    out.writeDouble(radius);
  }

  /* Return a Keyframe which describes the current pose of this object. */

  public Keyframe getPoseKeyframe()
  {
    return new PointLightKeyframe(color, intensity, decayRate, radius);
  }

  /* Modify this object based on a pose keyframe. */

  public void applyPoseKeyframe(Keyframe k)
  {
    PointLightKeyframe key = (PointLightKeyframe) k;

    setParameters(key.color.duplicate(), key.intensity, ambient, key.decayRate);
    setRadius(key.radius);
  }

  /** This will be called whenever a new pose track is created for this object.  It allows
      the object to configure the track by setting its graphable values, subtracks, etc. */

  public void configurePoseTrack(PoseTrack track)
  {
    track.setGraphableValues(new String [] {"Intensity", "Decay Rate", "Radius"},
        new double [] {intensity, decayRate, radius}, 
        new double [][] {{-Double.MAX_VALUE, Double.MAX_VALUE}, {0.0, Double.MAX_VALUE}, {0.0, Double.MAX_VALUE}});
  }

  /* Allow the user to edit a keyframe returned by getPoseKeyframe(). */

  public void editKeyframe(EditingWindow parent, Keyframe k, ObjectInfo info)
  {
    final PointLightKeyframe key = (PointLightKeyframe) k;
    final Widget patch = key.color.getSample(50, 30);
    ValueField intensityField = new ValueField(key.intensity, ValueField.NONE);
    ValueField radiusField = new ValueField(key.radius, ValueField.NONNEGATIVE);
    ValueField decayField = new ValueField(key.decayRate, ValueField.NONNEGATIVE);
    RGBColor oldColor = key.color.duplicate();
    final BFrame parentFrame = parent.getFrame();

    patch.addEventLink(MouseClickedEvent.class, new Object() {
      void processEvent()
      {
        new ColorChooser(parentFrame, Translate.text("lightColor"), key.color);
        patch.setBackground(key.color.getColor());
      }
    });
    ComponentsDialog dlg = new ComponentsDialog(parentFrame, Translate.text("editPointLightTitle"), 
	new Widget [] {patch, intensityField, radiusField, decayField}, 
	new String [] {Translate.text("Color"), Translate.text("Intensity"), Translate.text("Radius"), Translate.text("decayRate")});
    if (!dlg.clickedOk())
    {
      key.color.copy(oldColor);
      return;
    }
    key.intensity = (float) intensityField.getValue();
    key.decayRate = (float) decayField.getValue();
    key.radius = radiusField.getValue();
  }

  /* Inner class representing a pose for a cylinder. */

  public static class PointLightKeyframe extends Light.LightKeyframe //implements Keyframe
  {
//    public RGBColor color;
//    public float intensity, decayRate;
    public double radius;

    public PointLightKeyframe(RGBColor color, float intensity, float decayRate, double radius)
    {
//      this.color = color.duplicate();
//      this.intensity = intensity;
//      this.decayRate = decayRate;
//      this.radius = radius;
      super(color, intensity, decayRate);
      this.radius = radius;
    }

    /* Create a duplicate of this keyframe. */

    public Keyframe duplicate()
    {
      return new PointLightKeyframe(color, intensity, decayRate, radius);
    }

//    /* Create a duplicate of this keyframe for a (possibly different) object. */
//
//    public Keyframe duplicate(Object owner)
//    {
//      return duplicate();
//    }

    /* Get the list of graphable values for this keyframe. */

    public double [] getGraphValues()
    {
      return Utilities.arrayConcat(super.getGraphValues(), new double [] {radius});
//      return new double [] {intensity, decayRate, radius};
    }

    /* Set the list of graphable values for this keyframe. */

    public void setGraphValues(double values[])
    {
//      intensity = (float) values[0];
//      decayRate = (float) values[1];
      super.setGraphValues(values);
      final int shift = super.getGraphValueCount();  // Currently ==2
      radius = values[shift + 0];
    }

    public int getGraphValueCount()
    {
      return super.getGraphValueCount() + 1;
    }

//    /* These methods return a new Keyframe which is a weighted average of this one and one,
//       two, or three others. */
//
//    public Keyframe blend(Keyframe o2, double weight1, double weight2)
//    {
//      PointLightKeyframe k2 = (PointLightKeyframe) o2;
//      RGBColor c = new RGBColor(weight1*color.getRed()+weight2*k2.color.getRed(),
//        weight1*color.getGreen()+weight2*k2.color.getGreen(),
//        weight1*color.getBlue()+weight2*k2.color.getBlue());
//      return new PointLightKeyframe(c, (float) (weight1*intensity+weight2*k2.intensity),
//        (float) (weight1*decayRate+weight2*k2.decayRate),
//        weight1*radius+weight2*k2.radius);
//    }
//
//    public Keyframe blend(Keyframe o2, Keyframe o3, double weight1, double weight2, double weight3)
//    {
//      PointLightKeyframe k2 = (PointLightKeyframe) o2, k3 = (PointLightKeyframe) o3;
//      RGBColor c = new RGBColor(weight1*color.getRed()+weight2*k2.color.getRed()+weight3*k3.color.getRed(),
//        weight1*color.getGreen()+weight2*k2.color.getGreen()+weight3*k3.color.getGreen(),
//        weight1*color.getBlue()+weight2*k2.color.getBlue()+weight3*k3.color.getBlue());
//      return new PointLightKeyframe(c, (float) (weight1*intensity+weight2*k2.intensity+weight3*k3.intensity),
//        (float) (weight1*decayRate+weight2*k2.decayRate+weight3*k3.decayRate),
//        weight1*radius+weight2*k2.radius+weight3*k3.radius);
//    }
//
//    public Keyframe blend(Keyframe o2, Keyframe o3, Keyframe o4, double weight1, double weight2, double weight3, double weight4)
//    {
//      PointLightKeyframe k2 = (PointLightKeyframe) o2, k3 = (PointLightKeyframe) o3, k4 = (PointLightKeyframe) o4;
//      RGBColor c = new RGBColor(weight1*color.getRed()+weight2*k2.color.getRed()+weight3*k3.color.getRed()+weight4*k4.color.getRed(),
//        weight1*color.getGreen()+weight2*k2.color.getGreen()+weight3*k3.color.getGreen()+weight4*k4.color.getGreen(),
//        weight1*color.getBlue()+weight2*k2.color.getBlue()+weight3*k3.color.getBlue()+weight4*k4.color.getBlue());
//      return new PointLightKeyframe(c, (float) (weight1*intensity+weight2*k2.intensity+weight3*k3.intensity+weight4*k4.intensity),
//        (float) (weight1*decayRate+weight2*k2.decayRate+weight3*k3.decayRate+weight4*k4.decayRate),
//        weight1*radius+weight2*k2.radius+weight3*k3.radius+weight4*k4.radius);
//    }
    
    /** Blend this Keyframe with up to three others and modify it inplace to
      contain the result.
      o2, o3 and o4 are the Keyframes where o3 or o4 may be null
     (and are ignored then). If o3 is null, o4 is ignored!
    */
    protected void blendInplace(Keyframe o2, Keyframe o3, Keyframe o4, double weight1, double weight2, double weight3, double weight4)
    {
      super.blendInplace(o2, o3, o4, weight1, weight2, weight3, weight4);
      PointLightKeyframe k2 = (PointLightKeyframe) o2, k3 = (PointLightKeyframe) o3, k4 = (PointLightKeyframe) o4;
      
      radius = weight1*radius + weight2*k2.radius;
      
      if (k3 != null)
      {
        radius += weight3*k3.radius;

        if (k4 != null)
        {
          radius += weight4*k4.radius;
        }
      }
    }


    /* Determine whether this keyframe is identical to another one. */

    public boolean equals(Keyframe k)
    {
      if (!(k instanceof PointLightKeyframe))
        return false;
      PointLightKeyframe key = (PointLightKeyframe) k;
//      return (key.color.equals(color) && key.intensity == intensity && key.decayRate == decayRate && key.radius == radius);
      return (super.equals(k) && key.radius == radius);
    }

    /* Write out a representation of this keyframe to a stream. */

    public void writeToStream(DataOutputStream out) throws IOException
    {
      color.writeToFile(out);
      out.writeFloat(intensity);
      out.writeFloat(decayRate);
      out.writeDouble(radius);
    }

    /* Reconstructs the keyframe from its serialized representation. */

    public PointLightKeyframe(DataInputStream in, Object parent) throws IOException
    {
      this(new RGBColor(in), in.readFloat(), in.readFloat(), in.readDouble());
    }
  }
}