package modeler;

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.text.DecimalFormat;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;

import modeler.shape.GLSim;
import net.java.games.jogl.GL;

/**
 * @author andru Transformation provides an interface to a 3D transformation
 *         matrix. It allows independent access to the translational,
 *         rotational, and scaling properties of the matrix. By convention,
 *         rotations are performed around the axes in X,Y,Z order.
 */

public class Transformation extends Treeable {

  // Individual Translation, Rotation, and Scaling components of this transformation
  protected final Vector3f R = new Vector3f(); // Stored in degrees
  protected final Vector3f S = new Vector3f();
  protected final Vector3f T = new Vector3f();

  //Constants defining the coordinate axies
  public static final Vector3f xAxis = new Vector3f(1, 0, 0);
  public static final Vector3f yAxis = new Vector3f(0, 1, 0);
  public static final Vector3f zAxis = new Vector3f(0, 0, 1);

  protected transient Icon icon;
  private transient AttributePanel attribs;
  
  /**
   * Constructor
   * @param name
   */
  public Transformation(String name) {

    this.name = name;
    S.set(1, 1, 1);
  }

  /**
   * Copy constructor
   * @param copy
   */
  public Transformation(Transformation copy) {

    this.R.set(copy.R);
    this.S.set(copy.S);
    this.T.set(copy.T);
  }
  
  /**
   * @see modeler.Treeable#getIcon()
   */
  public Icon getIcon() {

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

  /**
   * Applies the effect of this transformation to the GLSim object
   * @param glSim
   */
  public void applyTranformation(GLSim glSim) {

    glSim.translate(T);
    glSim.scale(S);
    glSim.rotate(xAxis, R.x);
    glSim.rotate(yAxis, R.y);
    glSim.rotate(zAxis, R.z);

  }

  /**
   * Sets the translation vector
   * @param translation
   */
  public void setTranslate(Vector3f translation) {

    T.set(translation);
  }

  /**
   * Sets the scale vector
   * @param scaling
   */
  public void setScale(Vector3f scaling) {

    S.set(scaling);
  }

  /**
   * Sets the rotation angle in degrees
   * @param rotation
   */
  public void setRotate(Vector3f rotation) {

    R.set(rotation);
  }

  /**
   * Gets the translation vector
   * @return
   */
  public Vector3f getTranslate() {

    return new Vector3f(T);
  }

  /**
   * Gets the scale vector
   * @return
   */
  public Vector3f getScale() {

    return new Vector3f(S);
  }

  /**
   * Gets the rotation vector in degrees
   * @return
   */
  public Vector3f getRotate() {

    return new Vector3f(R);
  }

  /**
   * Refreshs the state of the attribute panel
   *
   */
  public void refreshAttribs() {

    if (attribs != null)
      attribs.refresh();
  }

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

    if (attribs == null) {
      attribs = new AttributePanel(mainFrame, this);
    }
    return attribs;
  }

  /**
   * @see modeler.Treeable#render(net.java.games.jogl.GL, javax.vecmath.Matrix4f)
   */
  public void render(GL gl, Matrix4f currMat) {

    Matrix4f workMat = new Matrix4f();
    workMat.setIdentity();
    workMat.setTranslation(T);
    currMat.mul(workMat);
    workMat.setIdentity();
    workMat.setElement(0, 0, S.x);
    workMat.setElement(1, 1, S.y);
    workMat.setElement(2, 2, S.z);
    currMat.mul(workMat);
    workMat.setIdentity();
    workMat.set(new AxisAngle4f(xAxis, (float) Math.toRadians(R.x)));
    currMat.mul(workMat);
    workMat.setIdentity();
    workMat.set(new AxisAngle4f(yAxis, (float) Math.toRadians(R.y)));
    currMat.mul(workMat);
    workMat.setIdentity();
    workMat.set(new AxisAngle4f(zAxis, (float) Math.toRadians(R.z)));
    currMat.mul(workMat);

    super.render(gl, currMat);
    
  }
  
  /**
   * @see javax.swing.tree.TreeNode#getAllowsChildren()
   */
  public boolean getAllowsChildren() {

    return true;

  }

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

    return false;

  }
  
  /**
   * 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 {

    long serialVersionUID = 1L;

    //Text fields for each of the components
    protected JFormattedTextField Tx;
    protected JFormattedTextField Ty;
    protected JFormattedTextField Tz;
    protected JFormattedTextField Rx;
    protected JFormattedTextField Ry;
    protected JFormattedTextField Rz;
    protected JFormattedTextField Sx;
    protected JFormattedTextField Sy;
    protected JFormattedTextField Sz;
    boolean refreshing = false;

    /**
     * Inherited constructor
     * @param mf
     */
    protected AttributePanel(MainFrame mf, Transformation trans) {

      super(mf, trans);
      
    }
    
    /**
     * Builds the attribute panel
     * @param mf
     */
    public void initialize() {

      Tx = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Ty = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Tz = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Rx = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Ry = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Rz = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Sx = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Sy = new JFormattedTextField(new DecimalFormat("#0.0#"));
      Sz = new JFormattedTextField(new DecimalFormat("#0.0#"));
      
      JPanel outPanel = new JPanel();
      outPanel.setLayout(new GridLayout(4, 4));

      outPanel.add(new JLabel(""));
      JLabel lbl = new JLabel("X");
      lbl.setHorizontalAlignment(SwingConstants.CENTER);
      outPanel.add(lbl);
      lbl = new JLabel("Y");
      lbl.setHorizontalAlignment(SwingConstants.CENTER);
      outPanel.add(lbl);
      lbl = new JLabel("Z");
      lbl.setHorizontalAlignment(SwingConstants.CENTER);
      outPanel.add(lbl);

      lbl = new JLabel("T");
      lbl.setHorizontalAlignment(SwingConstants.CENTER);
      outPanel.add(lbl);
      outPanel.add(Tx);
      outPanel.add(Ty);
      outPanel.add(Tz);
      Tx.getDocument().addDocumentListener(this);
      Ty.getDocument().addDocumentListener(this);
      Tz.getDocument().addDocumentListener(this);

      lbl = new JLabel("R");
      lbl.setHorizontalAlignment(SwingConstants.CENTER);
      outPanel.add(lbl);
      outPanel.add(Rx);
      outPanel.add(Ry);
      outPanel.add(Rz);
      Rx.getDocument().addDocumentListener(this);
      Ry.getDocument().addDocumentListener(this);
      Rz.getDocument().addDocumentListener(this);

      lbl = new JLabel("S");
      lbl.setHorizontalAlignment(SwingConstants.CENTER);
      outPanel.add(lbl);
      outPanel.add(Sx);
      outPanel.add(Sy);
      outPanel.add(Sz);
      Sx.getDocument().addDocumentListener(this);
      Sy.getDocument().addDocumentListener(this);
      Sz.getDocument().addDocumentListener(this);
      
      this.setLayout(new GridLayout(2,1));
      this.add(outPanel);

      refresh();
    }

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

      refreshing = true;
      Tx.setValue(new Float(T.x));
      Ty.setValue(new Float(T.y));
      Tz.setValue(new Float(T.z));
      Rx.setValue(new Float(R.x));
      Ry.setValue(new Float(R.y));
      Rz.setValue(new Float(R.z));
      Sx.setValue(new Float(S.x));
      Sy.setValue(new Float(S.y));
      Sz.setValue(new Float(S.z));
      refreshing = false;
    }

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

      if(refreshing)
        return;
      try {
        T.x = Float.parseFloat(Tx.getText());
        T.y = Float.parseFloat(Ty.getText());
        T.z = Float.parseFloat(Tz.getText());
        R.x = Float.parseFloat(Rx.getText());
        R.y = Float.parseFloat(Ry.getText());
        R.z = Float.parseFloat(Rz.getText());
        S.x = Float.parseFloat(Sx.getText());
        S.y = Float.parseFloat(Sy.getText());
        S.z = Float.parseFloat(Sz.getText());
        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();
    }
  }
}
