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

/*
 * NurbsCurve.java
 *
 * author   Jonas Forssell, Yuriy Mikhaylovskiy
 */
package j3d;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;
import java.io.*;
import java.util.Enumeration;
import java.util.Vector;

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

import gui.*;

/**
 * Implementing the NurbsCurve prototype.
 * @author   Timothy F. Rohaly / Jonas Forssell, Yuriy Mikhaylovskiy
 */
public class _NurbCurve extends _Object implements Serializable{
  public Color color;
  protected int order;
  protected int div=5;
  protected int numKnots;
  protected Vector geomDB = new Vector();
  protected Vector nodesDB = new Vector();
  protected Vector elementsDB = new Vector();
  protected Vector arr = new Vector();
  
  public _CtrlPoint[] controlPoints;
  protected float[] knots, knots_v;
  private String[] hdr;

  public String msh_name = "Dummy";
  public int msh_type = Canvas3D.MESH_Dummy_2;
  public float diameter, mesh_size;
  public int mesh_div;
  public Float factor,friction;
  public String contact;
  public Material material = null;

  protected Canvas3D J3D;
  protected PreProcessor PreP;
  protected JComboBox cComboBox1, cComboBox2;
  protected JTextField cTextField1, cTextField2;
  protected String[] type = {"No Mesh","Rod_2","Beam_2","Contact_Line"};
  protected int mesh_type_index;
  protected Object material_index;
  protected JButton jButton1;

  public _NurbCurve(boolean add) {
      this.color = Color.BLUE;
      this.diameter = 1.0f;
      this.mesh_size = 1.0f;
      this.add = add;
      this.geometry_type = Canvas3D.CURVE;
    }
  
  
  private void writeObject(ObjectOutputStream out)  throws IOException{
    out.writeObject(controlPoints);
    out.writeObject(knots);
    out.writeInt(div);
    out.writeObject(color);
    out.writeInt(order);
    out.writeInt(geometry_type);

    out.writeObject(geomDB);
    out.writeObject(nodesDB);
    out.writeObject(elementsDB);
    out.writeObject(Id);

    out.writeObject(msh_name);
    out.writeInt(msh_type);
    out.writeFloat(diameter);
    out.writeFloat(mesh_size);

    out.writeInt(mesh_div);
    out.writeObject(factor);
    out.writeObject(friction);
    out.writeObject(contact);

    out.writeObject(material);
    out.writeObject(type);
    out.writeInt(mesh_type_index);
    out.writeObject(material_index);
  }
  
  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
    controlPoints = (_CtrlPoint[])in.readObject();
    knots = (float[])in.readObject();
    div = in.readInt();
    color = (Color)in.readObject();
    order = in.readInt();
    geometry_type = in.readInt();

    geomDB = (Vector)in.readObject();
    nodesDB = (Vector)in.readObject();
    elementsDB = (Vector)in.readObject();
    Id = (String)in.readObject();

    msh_name = (String)in.readObject();
    msh_type = in.readInt();
    diameter = in.readFloat();
    mesh_size = in.readFloat();

    mesh_div = in.readInt();
    factor = (Float)in.readObject();
    friction = (Float)in.readObject();
    contact = (String)in.readObject();

    material = (Material)in.readObject();
    type = (String[])in.readObject();
    mesh_type_index = in.readInt();
    material_index = in.readObject();

    selected = false;
  }

  public void reset(){
      // Generate geometry
    generate();
      // Generate mesh
    mesh(msh_type,mesh_size);
  }


  /**
   * This method generates the points which makes up the curve.
   * Note: before calling this method, make sure you have set the following
   * knots 			- the knot vector
   * controlPoints 	- The controlPoint vector
   * order			- Curve segment degree
   * 
   * @param div		The number of divisions for the curve (number of points)
   */
  
  
  public void generate() {

    int numControlPoints = controlPoints.length;
    int numKnots         = knots.length;
    geomDB = new Vector();
    Vector tmp = new Vector();
    float distance,us,ue;
    _Point s,e,p;
    
    // Create start vector of u-values along the curve. Use the knot vector and avoid double knots
    tmp.add(new Float(knots[order]));
    for (int i=order+1; i < numKnots-order; i++) 
        if (((Float)tmp.lastElement()).floatValue() != knots[i]) tmp.add(new Float(knots[i]));
    
    // Refine the vector to within tolerance
    for (int i=0; i<tmp.size()-1; i++) {
        us = ((Float)tmp.elementAt(i)).floatValue();
        ue = ((Float)tmp.elementAt(i+1)).floatValue();
        
        s = lib3D.curvePoint(numControlPoints, order, knots, controlPoints, us);
        e = lib3D.curvePoint(numControlPoints, order, knots, controlPoints, ue);
        p = lib3D.curvePoint(numControlPoints, order, knots, controlPoints, (us+ue)/2.0f);
        
        distance = lib3D.distancePoint2Line(p,s,e);
        
        if (distance > j3d.Canvas3D.GeometricTolerance) {
            tmp.insertElementAt(new Float((us+ue)/2.0f),i+1);
            i--;
        }
    }
    
    // Add resulting points to the geometric database
    for (int i=0; i<tmp.size(); i++) {
      geomDB.add(lib3D.curvePoint(numControlPoints, order, knots, controlPoints, ((Float)tmp.elementAt(i)).floatValue()));
    }
  }

  public Object[] get_Array(Canvas3D j3d){
    _Point tmp1,tmp2;
    arr = new Vector();
    Object[] shparr;
    
    // Generate geometry
    for (int i=0; i<geomDB.size()-1; i++) {
        tmp1 = (_Point)geomDB.elementAt(i);
        tmp2 = (_Point)geomDB.elementAt(i+1);
      arr.add(new shpLine(tmp1.x,tmp1.y,tmp1.z,tmp2.x,tmp2.y,tmp2.z, selected ? j3d.SELECTCOLOR : color));
    }
    
    for (int i=0; i<arr.size(); i++)
        ((shp)arr.elementAt(i)).setShow(show);

    // Generate nodes
    for(int i=0; i<nodesDB.size(); i++){
      _Node nd = (_Node)nodesDB.elementAt(i);

      shparr = nd.get_Array(j3d);
      for (int j=0; j<shparr.length; j++)
          arr.add(shparr[j]);
    }

    // Generate elements
    for(int i=0; i<elementsDB.size(); i++){
      _Element2 el = (_Element2)elementsDB.elementAt(i);
      shparr = el.get_Array(j3d);
      for (int j=0; j<shparr.length; j++)
          arr.add(shparr[j]);
    }
 
    
    return arr.toArray();
  }

  public boolean isPickPoint(int x, int y, boolean shw) {
      boolean check = false;
      _Node nd;
      _Element2 el;
      
      // select this nurb curve if geometry is picked
      if (show == shw)
      for (int i=0; i< geomDB.size()-1; i++) 
      	check = (((shp)arr.elementAt(i)).isPickPoint(x,y) == true ? true : check);
      
      // Check nodes
      for (int i= 0 ; i < nodesDB.size(); i++) {
          nd = (_Node)nodesDB.elementAt(i);
          if (nd.isPickPoint(x,y,shw)) nd.Select("");
      }

      // Check elements
      for (int i= 0 ; i < elementsDB.size(); i++) {
          el = (_Element2)elementsDB.elementAt(i);
          if (el.isPickPoint(x,y,shw)) el.Select("");
      }
      
      return check;
  }


public boolean isPickPoint(Rectangle2D r, boolean shw) {
      boolean check = true;
      _Node nd;
      _Element2 el;
      
      if (show == shw)
      for (int i=0; i< geomDB.size()-1; i++) 
      	check = (((shp)arr.elementAt(i)).isPickPoint(r) == false ? false : check);
      
      // Check nodes
      for (int i= 0 ; i < nodesDB.size(); i++) {
          nd = (_Node)nodesDB.elementAt(i);
          if (nd.isPickPoint(r,shw)) nd.Select("");
      }

      // Check elements
      for (int i= 0 ; i < elementsDB.size(); i++) {
          el = (_Element2)elementsDB.elementAt(i);
          if (el.isPickPoint(r,shw)) el.Select("");
      }
      
      return check;
  }
  
  
  /**
   * This method generates the nodes and elements which makes up the curve mesh.
   * Note: before calling this method, make sure you have set the following
   * knots 			- the knot vector
   * controlPoints 	- The controlPoint vector
   * order			- Curve segment degree
   * 
   * @param type	The element type for the mesh
   * @param size	The length of each element assuming uniform mesh
   */
  
  public void mesh(int type, float size){
      nodesDB = new Vector();
      elementsDB = new Vector();
      
      int numControlPoints = controlPoints.length;
      int numKnots         = knots.length;
      float length;
      _Point op, np;
      _Node on, nn;
      _Element2 ne;

      // Determine length
      length = 0;
      op = lib3D.curvePoint(numControlPoints, order, knots, controlPoints, 0.0f/(float)div);
      for (int i=1; i<=div; i++) {
        np = lib3D.curvePoint(numControlPoints, order, knots, controlPoints, (float)i/(float)div);
        length += Math.sqrt((np.x-op.x)*(np.x-op.x)+(np.y-op.y)*(np.y-op.y)+(np.z-op.z)*(np.z-op.z));
        op = np;
      }

      mesh_div = (int)(length/size);
      
      // Generate mesh
      if (type != Canvas3D.MESH_Dummy_2) {
          op = lib3D.curvePoint(numControlPoints, order, knots, controlPoints, 0.0f/(float)mesh_div);
          on = new _Node(op.x,op.y,op.z);
          on.set_Id(Id + "." + 0);
          on.setMaster_type(Canvas3D.CURVE);
          on.setShow(show);
          nodesDB.add(on);
          
          for (int i=1; i<=mesh_div; i++) {
              
              np = lib3D.curvePoint(numControlPoints, order, knots, controlPoints, (float)i/(float)mesh_div);
              nn = new _Node(np.x,np.y,np.z);
              nn.set_Id(Id + "." + i);
              nn.setMaster_type(Canvas3D.CURVE);
              nn.setShow(show);
              nodesDB.add(nn);
              
              ne = new _Element2(on,nn,type,material,diameter);
              ne.set_Id(Id + "." + (i-1));
              ne.setShow(show);
              elementsDB.add(ne);
              
              on = nn;
              op = np;
          }
      }
      
  }
  
  public boolean delete(){
      for(int i=0; i<elementsDB.size(); i++){
        if(((_Object)elementsDB.elementAt(i)).isSelected()){
          elementsDB.removeElementAt(i);
          i--;
        }
      }
      for(int i=0; i<nodesDB.size(); i++){
        _Node nd = (_Node)nodesDB.elementAt(i);
        if(nd.isSelected()){
          boolean using = false;
          for(int j=0; j<elementsDB.size(); j++){
            _Node[] nds = ((_Object)elementsDB.elementAt(j)).get_Nodes();
            for(int k=0; k<nds.length; k++){
              if(nds[k].equals(nd))using=true;
            }
          }
          if(!using){
            nodesDB.removeElementAt(i);
            i--;
          }
        }
      }
      return selected;
    }

  public void Select(String st) {
        selected = st != null;
        if(st==null){
            for(int i=0; i<nodesDB.size(); i++) ((_Node)nodesDB.elementAt(i)).Select(st);
            for(int i=0; i<elementsDB.size(); i++) ((_Object)elementsDB.elementAt(i)).Select(st);
          } 
    }
  public boolean isSelected(){return selected;}

  public void addSelectedObjects(Vector v) {
      for(int i=0; i<nodesDB.size(); i++) ((_Node)nodesDB.elementAt(i)).addSelectedObjects(v);
      for(int i=0; i<elementsDB.size(); i++) ((_Object)elementsDB.elementAt(i)).addSelectedObjects(v);
      
  }
  
  public String toString(){ return "Nurb Curve ID="+Id;}
  public void transform3D(Matrix3D t){
    if(!selected || processed)return;
    for (int i=0; i<controlPoints.length; i++){
      controlPoints[i].Select("");
      controlPoints[i].transform3D(t);
      controlPoints[i].Select(null);
    }
    reset();
  }

  public MutableTreeNode get_TreeNode(){
      DefaultMutableTreeNode node = new DefaultMutableTreeNode("Mesh");
      DefaultMutableTreeNode n_nodes = new DefaultMutableTreeNode("Nodes");
      DefaultMutableTreeNode n_elements = new DefaultMutableTreeNode("Elements");

      for(int i=0; i<nodesDB.size(); i++) n_nodes.add(((_Object)nodesDB.elementAt(i)).get_TreeNode());
      node.add(n_nodes);

      for(int i=0; i<elementsDB.size(); i++) n_elements.add(((_Object)elementsDB.elementAt(i)).get_TreeNode());
      node.add(n_elements);
      
      return node;
  }

  
  public JPanel getEditPanel(Canvas3D j3d, PreProcessor pp) {
      this.J3D = j3d;
      this.PreP = pp;
      
      JPanel cPanel0 = new JPanel();
      JPanel cPanel1 = new JPanel();
      JPanel cPanel2 = new JPanel();
      JPanel cPanel3 = new JPanel();
      cComboBox1 = new JComboBox(type);
      cComboBox2 = new JComboBox();
      cTextField1 = new JTextField("" + diameter);
      cTextField2 = new JTextField("" + mesh_size);
      
      cPanel0.setLayout(new BoxLayout(cPanel0, BoxLayout.Y_AXIS));
      cPanel0.add(cComboBox1);
    	cComboBox1.setForeground(Color.blue);
        cComboBox1.setSelectedIndex(mesh_type_index);
        cComboBox1.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                int c = e.getKeyCode();

                if (c == KeyEvent.VK_ENTER) {
                    cTextField1.requestFocus();
                    cTextField1.selectAll();
                }
            }
        });

      cPanel0.add(cPanel2);
      cPanel2.setLayout(new GridLayout());
      cPanel2.add(new JLabel("Diameter"), null);
      cPanel2.add(cTextField1, null);
      cTextField1.addKeyListener(new KeyAdapter() {
          public void keyReleased(KeyEvent e) {
              int c = e.getKeyCode();

              if (c == KeyEvent.VK_ENTER) {
                  cComboBox2.requestFocus();
              }
          }
      });
      	
      cPanel0.add(cPanel1);
      cPanel1.setLayout(new BorderLayout());
      cPanel1.add(new JLabel("Material"), BorderLayout.WEST);
      cPanel1.add(cComboBox2, BorderLayout.EAST);
      cComboBox2.setActionCommand("");
      cComboBox2.setLightWeightPopupEnabled(false);
      for(Enumeration en = PreP.MatDB.keys(); en.hasMoreElements();)  cComboBox2.addItem(en.nextElement()); 
      if (material_index == null)
          cComboBox2.setSelectedIndex(0);
      else
          cComboBox2.setSelectedItem(material_index);
      cComboBox2.addKeyListener(new KeyAdapter() {
          public void keyReleased(KeyEvent e) {
              int c = e.getKeyCode();

              if (c == KeyEvent.VK_ENTER) {
                  cTextField2.requestFocus();
                  cTextField2.selectAll();
              }
          }
      });

      cPanel0.add(cPanel3);
      cPanel3.setLayout(new GridLayout());
      cPanel3.add(new JLabel("Element Size"), null);
      cPanel3.add(cTextField2, null);
      cTextField2.addKeyListener(new KeyAdapter() {
          public void keyReleased(KeyEvent e) {
              int c = e.getKeyCode();

              if (c == KeyEvent.VK_ENTER) {
                  jButton1.requestFocus();
              }
          }
      });
      
      
      return cPanel0;
  }

  
  public void requestAction() {
      
  }
      
  public void update(ActionEvent e) {
      mesh_type_index = cComboBox1.getSelectedIndex();
      if(mesh_type_index==0) msh_type = Canvas3D.MESH_Dummy_2;
      if(mesh_type_index==1) msh_type = Canvas3D.MESH_Rod_2;
      if(mesh_type_index==2) msh_type = Canvas3D.MESH_Beam_2;
      if(mesh_type_index==3) msh_type = Canvas3D.MESH_Contact_Line;
      diameter = Float.parseFloat(cTextField1.getText());
      material_index = cComboBox2.getSelectedItem();
      material = (Material)PreP.MatDB.get(material_index+"");
      mesh_size = Float.parseFloat(cTextField2.getText());
  }
  
  
  
  public _Node[] get_Nodes(){
      _Node[] arr = new _Node[nodesDB.size()];
      for(int i=0; i<nodesDB.size(); i++) arr[i] = (_Node)nodesDB.elementAt(i);
      return arr;
    }
  
  public _Object[] get_Elements(){
      _Object[] arr = new _Object[elementsDB.size()];
      for(int i=0; i<elementsDB.size(); i++) arr[i] = (_Object)elementsDB.elementAt(i);
      return arr;
    }
  
  public void insertKnots(Vector ins) {
      _CtrlPoint[] newcontrolPoints = new _CtrlPoint[controlPoints.length + ins.size()];
      float[] newknot = new float[controlPoints.length + ins.size() + order + 1];	

      float[] insknts = new float[ins.size()];
      for (int i=0; i<ins.size(); i++) 
          insknts[i] = ((Float)ins.elementAt(i)).floatValue();
      
      lib3D.refineKnotVectCurve(order, knots, controlPoints, insknts, newknot, newcontrolPoints);
      
      knots = newknot;
      controlPoints = newcontrolPoints;
  }

  public _NurbCurve copy() {
      _NurbCurve c = new _NurbCurve(false);
      try {
        c = (_NurbCurve)this.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    
    return c;
  }
  
  public void elevate(int times) {
      
      // Dimension for worst case
      _CtrlPoint[] newcontrolPoints = new _CtrlPoint[controlPoints.length*(times+1)];
      float[] newknots = new float[knots.length*(times+1)];	
      int[] cl;

      cl = lib3D.degreeElevateCurve(order, knots, controlPoints, times, newknots, newcontrolPoints);
      
      knots = new float[cl[0]];
      for (int i=0; i<cl[0]; i++)
          knots[i] = newknots[i];

      controlPoints = new _CtrlPoint[cl[1]];
      for (int i=0; i<cl[1]; i++)
          controlPoints[i] = new _CtrlPoint(newcontrolPoints[i]);

      order = cl[2];
  }

  public _Object duplicate(Canvas3D out, boolean add){ 
      _NurbCurve o = null;
      try {
        o = (_NurbCurve)this.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
      return o; 
      }

  public Vector3D getCenter() {
      _Point s = lib3D.curvePoint(controlPoints.length, order, knots, controlPoints, 0.5f);

      return s.getVector();
  }

  protected _Point[] calculateDerivate(float u, int derivate_order) {
      _CtrlPoint[] dp = new _CtrlPoint[controlPoints.length];
      _Point[] p;
      
      if (derivate_order > order) return null;
      
      dp = lib3D.curveDerivsAlg1(controlPoints.length,order,knots,controlPoints,u,derivate_order);
      p = lib3D.ratCurveDerivs(dp,derivate_order);
      
      return p;
  }
  
}

