/*
 * 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 PURPOUSE. See the GNU
 * General Public License for more details.
 *
 * You should have recieved a copy of the GNU General Public License
 * along with this program; if not write to the Free Software
 * Foundation, inc., 59 Temple Place, Suite 330, Boston MA 02111-1307
 * USA */

/*
 * author  Jonas Forssell
 */
package j3d;
import java.awt.*;
import java.io.*;
import java.util.Vector;

import javax.swing.tree.*;
import javax.swing.*;

import java.awt.event.*;

import gui.*;

/**
 * Creates a nurbcurve in the shape of an arc (or circle)
 * @author   Jonas Forssell, Yuriy Mikhaylovskiy
 */
public class _SurfDir extends _NurbSurface implements Serializable{
  private _NurbCurve profile, spine;
  private boolean spine_reversed = false;
  
  JTextField jTextField1, jTextField2, jTextField3, jTextField4, jTextField5, jTextField6, jTextField7;
  JButton jButton2, jButton3, jButton4, jButton5;
  JComboBox jComboBox1, jComboBox2;
  JCheckBox jCheckBox1;
  
  
  /**
   * Constructs a NURB surface defined by a profile and a spine
   * 
   * 
   */
  public _SurfDir(boolean add) {
    super(add);
  }
  
  private void writeObject(ObjectOutputStream out)  throws IOException{
      out.writeObject(profile);
      out.writeObject(spine);
      out.writeObject(new Boolean(spine_reversed));
    }
    
  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
      profile = (_NurbCurve)in.readObject();
      spine = (_NurbCurve)in.readObject();
      spine_reversed = ((Boolean)in.readObject()).booleanValue();
    }

  public void reset(boolean do_mesh) {
      Vector3D x,y,z,B0,B,T;
      Matrix3D A,A_inv,tmp;
      _CtrlPoint p;
      float u,du;
      _CtrlPoint[] cp,cpu,cpv;

      _NurbCurve tspine = spine.copy();

        if (spine_reversed)
            tspine.reverseDirection();
      
        // Initialize
        cp = tspine.controlPoints;
        cpu = new _CtrlPoint[cp.length];
        for (int i=0; i< cp.length; i++) {
            cpu[i] = new _CtrlPoint(cp[i]);
            cpu[i].stripWeight();
        }
            
        cp = profile.controlPoints;
        cpv = new _CtrlPoint[cp.length];
        for (int i=0; i< cp.length; i++) {
            cpv[i] = new _CtrlPoint(cp[i]);
            cpv[i].stripWeight();
        }

        controlPoints = new _CtrlPoint[cpu.length][cpv.length];

        y = new Vector3D();
        z = new Vector3D();
        B = new Vector3D();
        B0 = new Vector3D();
        A_inv = new Matrix3D();
        
        // Generate the knots
        uorder = tspine.order;
        uknots = tspine.knots;

        vorder = profile.order;
        vknots = profile.knots;
        
        // Generate control points
        du = 1f / (float)(cpu.length - 1);
        u = 0;

        // Move section to origo (center around spine end)
        for (int i = cpv.length-1; i >= 0; i--) {
            cpv[i].x = cpv[i].x - cpu[0].x;
            cpv[i].y = cpv[i].y - cpu[0].y;
            cpv[i].z = cpv[i].z - cpu[0].z;
        }

        
        // Choose initial B0 vector (arbitraty orthogonal to derivate will do)
        x = tspine.calculateDerivate(u,1)[1].getVector();
        B = new Vector3D(1,0,0); 
        B0.cross(x,B);
        
        if (B0.getLength() < 1E-5) {
            B = new Vector3D(0,1,0); 
            B0.cross(x,B);
        }
        
        B0.toUnitLength();
        
        for (int i = 0; i < cpu.length; i++) {
            
            // Determine A
            T = tspine.calculateDerivate(u,1)[1].getVector();
            T.toUnitLength();
            
            // bi = B[i-1] - (B[i-1] dot T[i])*T[i]
            	T.scale(B0.dot(B0,T));
            	B.sub(B0,T);
            
            B.toUnitLength();
            	
            x = tspine.calculateDerivate(u,1)[1].getVector();
            x.toUnitLength();
            
            z.copy(B);

            y.cross(z,x);
            
            tmp = new Matrix3D(x,y,z);
            
            if (i == 0) {
                A_inv = new Matrix3D(tmp);
                A_inv.invert();
            }
            
            A = new Matrix3D(A_inv);
            
            A.mult(tmp);
            

            for (int j = 0; j < cpv.length; j++) {
                // Insert tranformation of section (cpv) here for each new spine section
                p = new _CtrlPoint(cpv[j]);
                p.Select("");
                p.transform3D(A);		// Rotate according to derivate angle change
                controlPoints[i][j] = new _CtrlPoint(cpu[i].x + p.x/cpu[i].w, cpu[i].y + p.y/cpu[i].w, cpu[i].z + p.z/cpu[i].w, cpu[i].w * p.w, Color.GRAY);
                controlPoints[i][j].applyWeight();
            }
            
            u += du;
            B0.copy(B);
        }
            
        // Set mesh data
        mesh_div[0] = tspine.mesh_div;
        mesh_div[1] = profile.mesh_div;
        mesh_div[2] = mesh_div[0];
        mesh_div[3] = mesh_div[1];

        // Generate geometry & mesh
        super.reset(do_mesh);
    }

  public String toString(){ return "DirSurface ID="+Id;}

  public MutableTreeNode get_TreeNode(){
    DefaultMutableTreeNode node = new DefaultMutableTreeNode(this);
    
    // Add unique data for this class
    node.add(profile.get_TreeNode());
    node.add(spine.get_TreeNode());

    // Add mesh data from _NurbCurve
    node.add(super.get_TreeNode());

    return node;
  }

  public JPanel getEditPanel(Canvas3D j3d, PreProcessor pp){
      this.J3D = j3d;
      this.PreP = pp;

      JPanel jPanel0 = new JPanel();
      JPanel jPanel1 = new JPanel();
      JPanel jPanel2 = new JPanel();
      JPanel jPanel7 = new JPanel();
      jButton1 = new JButton(add == true?"Add":"Update");
      jButton2 = new JButton("<<");
      jButton3 = new JButton("<<");
      jTextField1 = new JTextField((profile != null ? profile.toString() : "")+"",3);
      jTextField2 = new JTextField((spine != null ? spine.toString() : "")+"",3);

      jCheckBox1 = new JCheckBox("Spine reverse direction", spine_reversed);
      
      jPanel0.setLayout(new BoxLayout(jPanel0, BoxLayout.Y_AXIS));
      jPanel0.add(jPanel1);
      	jPanel1.setLayout(new GridLayout(2,3));

      jPanel1.add(new JLabel("Profile"), null);
      jPanel1.add(jTextField1, null);
      	jTextField1.setEditable(false);
      jPanel1.add(jButton2, null);
      	jButton2.addActionListener(new java.awt.event.ActionListener() {
          public void actionPerformed(ActionEvent e) {
            jButton2_actionPerformed(e);
          }
        });
        jButton2.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                checkDefaultKey(e);
            }
            public void keyReleased(KeyEvent e) {
                int c = e.getKeyCode();

                if (c == KeyEvent.VK_ENTER) {
                    jButton2_actionPerformed(null);
                }
            }
        });
      jPanel1.add(new JLabel("Spine"), null);
      jPanel1.add(jTextField2, null);
      	jTextField2.setEditable(false);
      jPanel1.add(jButton3, null);
      	jButton3.addActionListener(new java.awt.event.ActionListener() {
      	    public void actionPerformed(ActionEvent e) {
      	        jButton3_actionPerformed(e);
      	    }
      	});
        jButton3.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                checkDefaultKey(e);
            }
            public void keyReleased(KeyEvent e) {
                int c = e.getKeyCode();

                if (c == KeyEvent.VK_ENTER) {
                    jButton3_actionPerformed(null);
                }
            }
        });

      jPanel0.add(jPanel2);
      jPanel2.add(jCheckBox1);
      	
      jPanel0.add(super.getEditPanel(j3d,pp));
      
      jPanel0.add(jPanel7);
        jPanel7.add(jButton1);
      jButton1.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(ActionEvent e) {
          jButton1_actionPerformed(e);
        }
      });
      jButton1.addKeyListener(new KeyAdapter() {
          public void keyPressed(KeyEvent e) {
              checkDefaultKey(e);
          }
          public void keyReleased(KeyEvent e) {
              int c = e.getKeyCode();

              if (c == KeyEvent.VK_ENTER) {
                  jButton1_actionPerformed(null);
              }
          }
      });

      jPanel0.validate();

      return jPanel0;
  }

  void jButton1_actionPerformed(ActionEvent e) {
      _SurfDir tmp = this;
      
        if(profile==null || spine==null) return;

        spine_reversed = jCheckBox1.isSelected();
        
        tmp.update(e);

        if(add == true) {
            try {
                tmp = (_SurfDir)this.clone();
                J3D.add3D(tmp);
              } catch (CloneNotSupportedException e1) {
                  e1.printStackTrace();
              }
            tmp.add = false;
            jButton2.requestFocus();
            
        } else
            tmp.reset(true);
        
        J3D.tree_reset();
        J3D.view_reset();
        J3D.repaint();
    }
  
  
  void jButton2_actionPerformed(ActionEvent e) {
      boolean set = false;
      Object[] obj = J3D.getSelectedObjects3D();
      for (int i=0; i< obj.length; i++) 
          if(obj[i] !=null && obj[i] instanceof _NurbCurve){
              profile=(_NurbCurve)obj[i];
              jTextField1.setText(obj[i].toString());
              set = true;
              jButton3.requestFocus();
          }
          
      if (!set) {
              profile=null;
              jTextField1.setText("");
          }
    }

    void jButton3_actionPerformed(ActionEvent e) {
        boolean set = false;
        Object[] obj = J3D.getSelectedObjects3D();
        for (int i=0; i< obj.length; i++) 
            if(obj[i] !=null && obj[i] instanceof _NurbCurve){
                spine=(_NurbCurve)obj[i];
                jTextField2.setText(obj[i].toString());
                set = true;
                cComboBox1.requestFocus();
            }
            
        if (!set) {
                spine=null;
                jTextField2.setText("");
            }
    }

    public void requestFocus() {
        jButton2.requestFocus();
    }

    public void requestAction() {
        if (add == true)
            if (jButton2.hasFocus()) 
                jButton2_actionPerformed(null);
            else if (jButton3.hasFocus()) 
                jButton3_actionPerformed(null);
    }
    
    
    public _Object duplicate(Canvas3D out, boolean add){ 
        _SurfDir o = null;
        try {
          o = (_SurfDir)this.clone();
      } catch (CloneNotSupportedException e) {
          e.printStackTrace();
      }
      
      o.spine = (_NurbCurve)spine.duplicate(out,add);
      o.profile = (_NurbCurve)profile.duplicate(out,add);
      
      if (add) out.add3D(o);
      
      return o; 
    }
    
    public void transform3D(Matrix3D t){
        if(!selected || processed)return;
        super.transform3D(t);
          spine.Select("");
          spine.transform3D(t);
          spine.Select(null);
          spine.setProcessed(true);
          profile.Select("");
          profile.transform3D(t);
          profile.Select(null);
          profile.setProcessed(true);
          reset(true);
      }

    public void addSelectedObjects(Vector v) {
        if (selected) v.add(this);
        super.addSelectedObjects(v);
    }    
    
}

