package modeler;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.HashMap;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import net.java.games.jogl.GL;
import net.java.games.jogl.GLU;
import net.java.games.jogl.GLUquadric;

/**
 * Light object.  Also includes functions to map light objects to OpenGL lights
 * @author ags
 */
public class Light extends Treeable { 

  private static final long serialVersionUID = 3688792483416912692L;

  public static final float AMBIENT_CONSTANT = 0.05f;
  public static final float DIFFUSE_CONSTANT = 0.4f;
  
  /** Constant light parameters */
  public final float[] ambient = new float[] { AMBIENT_CONSTANT, AMBIENT_CONSTANT, AMBIENT_CONSTANT, 1 };
  public final float[] diffuse = new float[] { DIFFUSE_CONSTANT, DIFFUSE_CONSTANT, DIFFUSE_CONSTANT, 1 };
  public final float[] specular = new float[] { 1, 1, 1, 1 };
  public final float[] position = new float[] { 0, 5, 0, 1 };
  
  /** Map of all 8 GL lights */
  protected final static boolean[] taken = { false, false, false, false, false, false, false, false };
  protected final static int[] ids = { GL.GL_LIGHT0, GL.GL_LIGHT1, GL.GL_LIGHT2, GL.GL_LIGHT3, GL.GL_LIGHT4, GL.GL_LIGHT5, GL.GL_LIGHT6, GL.GL_LIGHT7 };

  /** This lights ID and index in the GL map */
  public int id;
  protected int index;

  /** Lights name and icon */
  protected transient Icon icon = new ImageIcon("../icons/light.gif");
  
  /** quadric for drawing the light */
  protected transient GLUquadric quad;

  /**
   * Required for IO
   */
  protected Light() {}
  
  /**
   * Constructor
   * @param newIndex
   */
  private Light(int newIndex) {

    index = newIndex;
    if (index != -1) {
      id = ids[index];
    }
    setName("Light " + index);
  }
  
  /**
   * @see modeler.Treeable#getIcon()
   */
  public Icon getIcon() {

    if(icon == null)
      icon = new ImageIcon("../icons/light.gif");
    return icon;
  }

  /**
   * Create a new light which is on or off depending on enabled.
   * @param enabled
   * @return
   */
  public static synchronized Light makeNewLight(boolean enabled) {

    if (!enabled) {
      return new Light(-1);
    }

    for (int ctr = 0; ctr < taken.length; ctr++) {
      if (taken[ctr] == false) {
        taken[ctr] = true;
        return new Light(ctr);
      }
    }
    return null;
  }

  /**
   * Enable the lights in the scene
   */
  public static synchronized void turnOnLights(GL gl) {

    for (int ctr = 0; ctr < taken.length; ctr++) {
      if (taken[ctr]) {
        gl.glEnable(ids[ctr]);
      }
      else {
        gl.glDisable(ids[ctr]);
      }
    }
  }

  /**
   * Unmap a light from the OpenGL map
   * @param l
   */
  public static synchronized void returnLight(Light l) {

    taken[l.index] = false;
  }

  /**
   * Draws a light
   */
  public void draw(GL gl, GLU glu, Camera cam) {

    if(quad == null) {
      quad = glu.gluNewQuadric();
    }  

    float scale = 0.05f * cam.getHeight();
    gl.glPushAttrib(GL.GL_ALL_ATTRIB_BITS);
    gl.glDisable(GL.GL_LIGHTING);
    gl.glPushMatrix();
    gl.glTranslatef(position[0], position[1], position[2]);
    gl.glScalef(scale, scale, scale);
    gl.glColor3f(0.8f,0.7f,0.1f);
    glu.gluSphere(quad, 1, 4, 2);
    gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
    gl.glLineWidth(1f);
    gl.glScalef(1.03f, 1.03f, 1.03f);
    gl.glColor3f(0,0,0);
    glu.gluSphere(quad, 1, 4, 2);
    gl.glLineWidth(1);
    gl.glPopMatrix();
    gl.glPopAttrib();

    
  }
  
  /**
   * @see modeler.Treeable#getAttributePanel(modeler.MainFrame)
   */
  public ItemAttributePanel getAttributePanel(MainFrame mainFrame) {

    return new AttributePanel(mainFrame, this);
  }
  
  /**
   * @throws IOException 
   * @see modeler.Treeable#readData(java.io.ObjectInputStream)
   */
  protected void readData(ObjectInputStream in) throws IOException {

    id = in.readInt();
    index = in.readInt();
    if(index >= 0)
      taken[index] = true;
    position[0] = in.readFloat();
    position[1] = in.readFloat();
    position[2] = in.readFloat();
    diffuse[0] = in.readFloat();
    diffuse[1] = in.readFloat();
    diffuse[2] = in.readFloat();
    specular[0] = in.readFloat();
    specular[1] = in.readFloat();
    specular[2] = in.readFloat();
    ambient[0] = in.readFloat();
    ambient[1] = in.readFloat();
    ambient[2] = in.readFloat();
    
  }

  /**
   * @throws IOException 
   * @see modeler.Treeable#saveData(java.io.ObjectOutputStream)
   */
  protected void saveData(ObjectOutputStream out) throws IOException {

    out.writeInt(id);
    out.writeInt(index);
    out.writeFloat(position[0]);
    out.writeFloat(position[1]);
    out.writeFloat(position[2]);
    out.writeFloat(diffuse[0]);
    out.writeFloat(diffuse[1]);
    out.writeFloat(diffuse[2]);
    out.writeFloat(specular[0]);
    out.writeFloat(specular[1]);
    out.writeFloat(specular[2]);
    out.writeFloat(ambient[0]);
    out.writeFloat(ambient[1]);
    out.writeFloat(ambient[2]);
    
  }

  /**
   * @see modeler.Treeable#exportMainData(java.util.HashMap, java.io.PrintWriter)
   */
  protected void exportMainData(HashMap materialsMap, PrintWriter pw) {

    pw.println("  <light>");
    pw.println("    <location>"+position[0]+" "+position[1]+" "+position[2]+"</location>");
    pw.println("    <power>"+specular[0]+" "+specular[1]+" "+specular[2]+"</power>");
    pw.println("  </light>");
    
  }
  
  /**
   * @see javax.swing.tree.TreeNode#getAllowsChildren()
   */
  public boolean getAllowsChildren() {

    return false;

  }

  /**
   * @see javax.swing.tree.TreeNode#isLeaf()
   */
  public boolean isLeaf() {

    return true;

  }
  
  /**
   * The attribute panel for this node
   * @author arbree
   * Oct 21, 2005
   * Transformation.java
   * Copyright 2005 Program of Computer Graphics, Cornell University
   */
  private class AttributePanel extends ItemAttributePanel implements DocumentListener {

    private static final long serialVersionUID = 3256437006337718072L;
    
    //Text fields for each of the components
    protected JFormattedTextField Px;
    protected JFormattedTextField Py;
    protected JFormattedTextField Pz;
    protected JFormattedTextField Sr;
    protected JFormattedTextField Sg;
    protected JFormattedTextField Sb;
    
    protected JFileChooser fileChooser;

    /**
     * Inherited constructor
     * @param mf
     */
    protected AttributePanel(MainFrame mf, Light light) {

      super(mf, light);
      
    }
    
    /**
     * Builds the attribute panel
     * @param mf
     */
    public void initialize() {
      
      Px = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Py = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Pz = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Sr = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Sg = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Sb = new JFormattedTextField(new DecimalFormat("#0.0#"));

      JPanel colorPanel = new JPanel();
      colorPanel.setLayout(new GridLayout(2,4));

      JLabel lbl;
      lbl = new JLabel("Pos: ");
      lbl.setHorizontalAlignment(SwingConstants.RIGHT);
      colorPanel.add(lbl);
      colorPanel.add(Px);
      colorPanel.add(Py);
      colorPanel.add(Pz);
      Px.getDocument().addDocumentListener(this);
      Py.getDocument().addDocumentListener(this);
      Pz.getDocument().addDocumentListener(this);

      lbl = new JLabel("Pow: ");
      lbl.setHorizontalAlignment(SwingConstants.RIGHT);
      colorPanel.add(lbl);
      colorPanel.add(Sr);
      colorPanel.add(Sg);
      colorPanel.add(Sb);
      Sr.getDocument().addDocumentListener(this);
      Sg.getDocument().addDocumentListener(this);
      Sb.getDocument().addDocumentListener(this);
      
      BorderLayout fld = new BorderLayout();
      this.setLayout(fld);
      this.add(colorPanel, BorderLayout.NORTH);
      
    }

    /**
     * Sets the display fields to the current values of the transformations
     */
    public void refresh() {

      Px.setValue(new Float(position[0]));
      Py.setValue(new Float(position[1]));
      Pz.setValue(new Float(position[2]));
      Sr.setValue(new Float(specular[0]));
      Sg.setValue(new Float(specular[1]));
      Sb.setValue(new Float(specular[2]));
      
    }

    /**
     * Updates the stored data
     */
    private void update() {

      try {
        position[0] = Float.parseFloat(Px.getText());
        position[1] = Float.parseFloat(Py.getText());
        position[2] = Float.parseFloat(Pz.getText());
        specular[0] = Float.parseFloat(Sr.getText());
        specular[1] = Float.parseFloat(Sg.getText());
        specular[2] = Float.parseFloat(Sb.getText());
        diffuse[0] = DIFFUSE_CONSTANT * specular[0];
        diffuse[1] = DIFFUSE_CONSTANT * specular[1];
        diffuse[2] = DIFFUSE_CONSTANT * specular[2];
        ambient[0] = AMBIENT_CONSTANT * specular[0];
        ambient[1] = AMBIENT_CONSTANT * specular[1];
        ambient[2] = AMBIENT_CONSTANT * specular[2];
        mf.refresh();
      }
      catch (NumberFormatException e) {
        // Do nothing just skip those updates for invalid data
      }

    }

    /**
     * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
     */
    public void changedUpdate(DocumentEvent e) {

      update();
    }

    /**
     * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
     */
    public void insertUpdate(DocumentEvent e) {

      update();
    }

    /**
     * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
     */
    public void removeUpdate(DocumentEvent e) {

      update();
    }

  }
}
