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

  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 JComboBox cComboBox1, cComboBox2;
  protected JTextField cTextField1, cTextField2;
  private JButton cButton1;
  protected String[] type = {"No Mesh","Rod_2","Beam_2","Contact_Line"};
  protected int mesh_type_index;
  protected Object material_index;
  protected JButton jButton1;

  private boolean by_constructor = false;

  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;
    }

  public _NurbCurve(_CtrlPoint[] p, float[] knots, int order, float mesh_size, int msh_type, float diameter, Material material) {
      this.add = false;
      this.geometry_type = Canvas3D.CURVE;
      this.color = Color.BLUE;
      this.knots = (float[])knots.clone();
      this.msh_type = msh_type;
      this.diameter = diameter;
      if (material != null)
          this.material = new Material(material);
      this.mesh_size = mesh_size;
      this.order = order;

      this.controlPoints = new _CtrlPoint[p.length];
      for (int i=0; i<p.length; i++)
          controlPoints[i] = new _CtrlPoint(p[i]);

      by_constructor = true;

      reset(false);
  }


  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(boolean do_mesh){
      // Generate geometry
    generate();
      // Generate mesh
    if (do_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.getGeometricTolerance()) {
            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 ogl) {
      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,ogl) == true ? true : check);

      // Check nodes
      for (int i= 0 ; i < nodesDB.size(); i++) {
          nd = (_Node)nodesDB.elementAt(i);
          if (nd.isPickPoint(x,y,shw,ogl)) nd.Select("");
      }

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

      return check;
  }


public boolean isPickPoint(Rectangle2D r, boolean shw, boolean ogl) {
      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,ogl) == false ? false : check);

      // Check nodes
      for (int i= 0 ; i < nodesDB.size(); i++) {
          nd = (_Node)nodesDB.elementAt(i);
          if (nd.isPickPoint(r,shw,ogl)) nd.Select("");
      }

      // Check elements
      for (int i= 0 ; i < elementsDB.size(); i++) {
          el = (_Element2)elementsDB.elementAt(i);
          if (el.isPickPoint(r,shw,ogl)) 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){
    mesh_size = size;
      nodesDB = new Vector();
      elementsDB = new Vector();

      int numControlPoints = controlPoints.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/mesh_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);
      if (selected) v.add(this);
  }

  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(true);
  }

  public MutableTreeNode get_TreeNode(){
      DefaultMutableTreeNode curve = new DefaultMutableTreeNode(this);
      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);

      if (by_constructor) {
          curve.add(node);
          return curve;
      }
      else
          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();
      JPanel cPanel4 = new JPanel();
      cComboBox1 = new JComboBox(type);
      cComboBox2 = new JComboBox();
      cTextField1 = new JTextField("" + diameter);
      cTextField2 = new JTextField("" + mesh_size);
      cButton1 = new JButton("Update");

      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 keyPressed(KeyEvent e) {
                checkDefaultKey(e);
            }

            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 keyPressed(KeyEvent e) {
              checkDefaultKey(e);
          }

          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 keyPressed(KeyEvent e) {
              checkDefaultKey(e);
          }

          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 keyPressed(KeyEvent e) {
              checkDefaultKey(e);
          }

          public void keyReleased(KeyEvent e) {
              int c = e.getKeyCode();

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

      if (by_constructor) {

          cPanel0.add(cPanel4);
          cPanel4.add(cButton1);
          cButton1.addActionListener(new java.awt.event.ActionListener() {
              public void actionPerformed(ActionEvent e) {
                  cButton1_actionPerformed(e);
              }
          });
          cButton1.addKeyListener(new KeyAdapter() {

              public void keyPressed(KeyEvent e) {
                  checkDefaultKey(e);
              }

              public void keyReleased(KeyEvent e) {
                  int c = e.getKeyCode();

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

      return cPanel0;
  }


  void cButton1_actionPerformed(ActionEvent e) {
      _NurbCurve tmp = this;
      int i,n;

      tmp.update(e);

      tmp.reset(true);

      J3D.tree_reset();
      J3D.view_reset();
      J3D.repaint();
    }


  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) {

      if (ins.size() == 0) return;

      _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();
    }

    o.controlPoints = (_CtrlPoint[])controlPoints.clone();
    for (int i=0; i<controlPoints.length; i++)
        o.controlPoints[i] = new _CtrlPoint(controlPoints[i]);

    o.knots = (float[])knots.clone();

    if (add) out.add3D(o);

      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;
  }

  protected _Point[] getEndPoints() {
      _Point[] p = new _Point[2];

      p[0] = (_Point)controlPoints[0];
      p[1] = (_Point)controlPoints[controlPoints.length-1];

      return p;
  }

  public void reverseDirection() {
      int n = knots.length;
      int m = controlPoints.length;
      int i;
      float[] newknot = new float[n];
      _CtrlPoint[] ncp = new _CtrlPoint[m];

      for (i=0; i<n; i++)
          newknot[i] = knots[n-1] - knots[n-1-i];

      for (i=0; i<m; i++)
          ncp[i] = controlPoints[m-1-i];

      knots = newknot;
      controlPoints = ncp;
  }

  public _Object[] getBorderObjects() {
      _Point[] p = new _Point[2];

      p[0] = new _Point(controlPoints[0].x, controlPoints[0].y, controlPoints[0].z,0f,0f,0f);
      p[1] = new _Point(controlPoints[controlPoints.length-1].x, controlPoints[controlPoints.length-1].y, controlPoints[controlPoints.length-1].z,0f,0f,0f);

      return p;
  }

  public _Point getPointAt(float u, boolean deriv) {
      _Point a;
      _Point[] da;
      float l;
      
      
      if (deriv == false)
            return lib3D.curvePoint(controlPoints.length, order, knots, controlPoints, u);
      else {
          a = lib3D.curvePoint(controlPoints.length, order, knots, controlPoints, u);
          da = this.calculateDerivate(u,1);
          
          l = (float)Math.sqrt(da[1].x * da[1].x + da[1].y * da[1].y + da[1].z * da[1].z);
          
          a.vx = da[1].x / l;
          a.vy = da[1].y / l;
          a.vz = da[1].z / l;
          
          a.reset(true);
          
          return a;
      }
      
      
      
      
  }


  public float getLength() {
      int numControlPoints = controlPoints.length;
      float length;
      _Point op, np;

      // 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;
      }

      return length;
  }
  
  
}

