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

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;

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

import com.sun.image.codec.jpeg.*;
import gui.PreProcessor;
import gui.util.PngEncoderB;
/**
 * Insert the type's description here.
 *
 * @author: Yuriy Mikhaylovskiy, Jonas Forssell
 */

public class Canvas3D extends JPanel implements Serializable {
  private DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode();
  public DefaultTreeModel tree3d = new DefaultTreeModel(treeNode);
  private BufferedImage offscreen;
  private Graphics2D  offgraphics;
  boolean painted = false;
  private Vector obj3d = new Vector();
  private Vector shp3d = new Vector();
  private int id_obj3d=0;
  public Matrix3D VMatrix3D = new Matrix3D();
  public Vector3D center_of_rotation = new Vector3D();
  public JLabel coodinates = new JLabel();
  private QSortZ qsort = new QSortZ();
  private int x,y,x0,y0;
  private PreProcessor pre;
  private boolean show = true;

  public Canvas3D(PreProcessor pre) {
    this.pre = pre;

    addComponentListener(new ComponentAdapter() {
      public void componentResized(ComponentEvent e) {
        offscreen=null;
      }
    });
    
    addMouseListener(new MouseAdapter() {

        public void mousePressed(MouseEvent e) {
        x=e.getX(); y=e.getY();
        x0 = x; y0 = y;
      }

        public void mouseReleased(MouseEvent e) {
            if (e.getModifiers() == MouseEvent.BUTTON1_MASK && DRAG == true) {
                boxSelect(Math.min(x0,x),Math.min(y0,y),Math.max(x0,x),Math.max(y0,y));
            }
        DRAG = false;
        DRAFTMODE = false;
        
        view_reset();
      }

        public void mouseClicked(MouseEvent e) {
            if(e.getModifiers() == MouseEvent.BUTTON1_MASK + MouseEvent.CTRL_MASK || e.getModifiers() == MouseEvent.BUTTON1_MASK){
                if (!e.isControlDown()) 
                    clearTree();
                
                setPickedObject(e.getX(), e.getY());
                
                view_reset();
                
                if (e.getClickCount() == 2)
                    showEditPanel(e);
                
                requestAction();
            }
            
            if(e.getModifiers() == MouseEvent.BUTTON2_MASK){
                view_all();
            }            
        }     
    });
    
    addMouseMotionListener(new MouseMotionAdapter() {
        
        public void mouseMoved(MouseEvent e) {
            coodinates.setText(e.getX()+","+e.getY());
        }
        
        public void mouseDragged(MouseEvent e) {
            if(e.getModifiers() == MouseEvent.BUTTON1_MASK)
                DRAG = true;
            else
                DRAFTMODE = true;
            this_mouseDragged(e);
        }
    });
    
    VMatrix3D.translate(400,-300,0);
    VMatrix3D.scale(1,-1,1);
    view_grid();
  }

  
  
  private synchronized void setPainted(){
    painted = true;
    notifyAll();
  }
  
  public synchronized void paint(Graphics g){
      shp tmp;
      
    if (offscreen == null){
      if (offgraphics != null) offgraphics.dispose();
      if (offscreen != null) offscreen.flush();
      offscreen = (BufferedImage)createImage(getWidth(), getHeight());
      offgraphics = (Graphics2D)offscreen.getGraphics();
    }
    
    // Create background
    offgraphics.setColor(BGCOLOR);
    offgraphics.fillRect(0, 0, getWidth(), getHeight());
    
    // Draw each element
    for(int i=0; i<shp3d.size(); i++){
        tmp = (shp)shp3d.elementAt(i);
        if (tmp.isShow() == show)
            tmp.paint(offgraphics,this);
    }
    
    // Draw bounding box if used
    if (DRAG == true) {
        offgraphics.setColor(Color.RED);
        offgraphics.drawRect(Math.min(x0,x),Math.min(y0,y),Math.abs(x-x0),Math.abs(y-y0));
    }
    
    g.drawImage(offscreen, 0, 0,this);
    setPainted();
  }
  
  public void update(Graphics g){
    paint(g);
  }
  
  private synchronized void view_transform(){
    int count = shp3d.size();
    for(int i=0; i<count; i++)  
        ((shp)shp3d.elementAt(i)).transform2D(VMatrix3D, center_of_rotation);
    
    if (!DRAFTMODE)    
        qsort.sort(shp3d);
    
  }
  
  public int add3D(Object obj){
    id_obj3d++;
    ((_Object)obj).set_Id(id_obj3d+"");
    ((_Object)obj).reset();
    obj3d.addElement(obj);
    treeNode.add(((_Object)obj).get_TreeNode());
    try{
      tree3d.reload();
    }catch(Exception e1){tree3d.reload();}
    Object[] arr = ((_Object)obj).get_Array(this);
    for(int i=0; i<arr.length; i++){
      ((shp)arr[i]).transform2D(VMatrix3D, center_of_rotation);
      shp3d.addElement(arr[i]);
    }
    return id_obj3d;
  }
  
  private void clearTree() {
      if (pre != null) pre.clearTree();
  }
  
  private void showEditPanel(MouseEvent e) {
      if (pre != null) pre.showEditPanel(e);
  }
  
  private void setPickedObject(int x, int y) {
      int count = obj3d.size();
      _Object o;
      
      for (int i=0; i<count; i++) {
          o = ((_Object)obj3d.elementAt(i));
          if (o.isPickPoint(x,y,show))
              o.Select("");
      }
      
      if (pre != null) pre.rescanTree();
  }
  
  
  private void boxSelect(int sx, int sy, int ex, int ey) {
      int count = obj3d.size();
      _Object o;
      
      Rectangle2D r = new Rectangle(sx,sy,ex-sx,ey-sy);
      
      for (int i=0; i<count; i++) {
          o = ((_Object)obj3d.elementAt(i));
          if (o.isPickPoint(r,show))
              o.Select("");
      }
      
      if (pre != null) pre.rescanTree();
  }
  
  
  public _Object getSelectedObject3D(){
      _Object[] obj = getSelectedObjects3D();
      
      if(obj.length > 0) return obj[0];

      return null;
  }
  
  public _Object[] getSelectedObjects3D() {
      int count = obj3d.size();
      Vector v = new Vector();
      _Object o;
      _Object[] p;
      Object[] q;
      
      for (int i=0; i<count; i++) {
          o = ((_Object)obj3d.elementAt(i));
          o.addSelectedObjects(v);
      }
      
      p = new _Object[v.size()];
      q = v.toArray();
      for (int i=0; i<q.length; i++)
          p[i] = (_Object)q[i];
      
      return p;
  }
  
  public _Object[] getAllObjects3D() {
    _Object[] arr = new _Object[obj3d.size()];
    for(int i=0; i<obj3d.size(); i++) arr[i] = ((_Object)obj3d.elementAt(i));
    return arr;
  }
  
  public _Node[] getAllNodes3D() {
      Vector arr = new Vector();
      _Node[] o;
      
      for(int i=0; i<obj3d.size(); i++) {
          o = ((_Object)obj3d.elementAt(i)).get_Nodes();
          if (o != null) 
              for (int j=0; j<o.length; j++) arr.add(o[j]);
      }
      
      _Node[] out = new _Node[arr.size()];
      for (int i=0; i<out.length; i++)
          out[i] = (_Node)arr.elementAt(i);
      
      return out;
  }

  public void remove_all(){
    id_obj3d=0;
    obj3d.removeAllElements();
    shp3d.removeAllElements();
    treeNode.removeAllChildren();
    tree3d.reload();
    view_grid();
    repaint();
    painted=true;
  }
  public void remove_select(){
    int count = obj3d.size();
    for(int i=0; i<count; i++) ((_Object)obj3d.elementAt(i)).Select(null);
  }
  public void remove(){
    treeNode.removeAllChildren();
    int count = obj3d.size();
    for(int i=0; i<count; i++) if(((_Object)obj3d.elementAt(i)).delete()){
      obj3d.removeElementAt(i);
      i--;
      count--;
    } else treeNode.add(((_Object)obj3d.elementAt(i)).get_TreeNode());
    tree3d.reload();
    view_reset();
  }

  public void duplicate(){
      int count = obj3d.size();
      _Object o;
      Vector tmp = new Vector();
      
      for(int i=0; i<count; i++) {
          o = (_Object)obj3d.elementAt(i);
          if(o.isSelected()){
              tmp.add(o);
          }
      }

      for (int i=0; i<tmp.size(); i++)
          ((_Object)tmp.elementAt(i)).duplicate(this,true);
      
      tree3d.reload();
      view_reset();
    }

  public void transform3D(Matrix3D m){
    int count = obj3d.size();
    _Object o;
    
    _Object[] p = getSelectedObjects3D();
    
    for (int i=0; i<p.length; i++) 
        if (!p[i].isProcessed()) {
            p[i].transform3D(m);
            p[i].setProcessed(true);
        }
            
    
/*    for(int i=0; i<count; i++)  {
        o = (_Object)obj3d.elementAt(i);
        if (!o.isProcessed() && o.isSelected()) {
            o.transform3D(m);
            o.setProcessed(true);
        }
        
    }
    */
    
    for(int i=0; i<count; i++)  
        ((_Object)obj3d.elementAt(i)).setProcessed(false);

    view_reset();
    view_repaint();
    tree_reset();
    
  }
  
  public void mesh(int type, float size){
    int count = obj3d.size();
    for(int i=0; i<count; i++)  ((_Object)obj3d.elementAt(i)).mesh(type,size);
    tree3d.reload();
    view_reset();
  }
  
  public void tree_reset(){
    treeNode.removeAllChildren();
    int count = obj3d.size();
    for(int i=0; i<count; i++) treeNode.add(((_Object)obj3d.elementAt(i)).get_TreeNode());
    tree3d.reload();
  }
  public synchronized void view_reset(){
    int count = obj3d.size();
    shp3d.removeAllElements();
    for(int i=0; i<count; i++){
      Object[] arr = ((_Object)obj3d.elementAt(i)).get_Array(this);
      for(int j=0; j<arr.length; j++){
        ((shp)arr[j]).transform2D(VMatrix3D, center_of_rotation);
        shp3d.addElement(arr[j]);
      }
    }
    view_grid();
  }
  public synchronized void view_repaint(){
    if(painted){
      painted = false;
      view_transform();
      repaint();
    }
  }
  public synchronized void view_translate(float dx, float dy, float dz){
    VMatrix3D.translate(dx,-dy,dz);
    view_repaint();
  }
  public synchronized void view_xrot(double theta){
    float dx = VMatrix3D.xo;
    float dy = VMatrix3D.yo;
    float dz = VMatrix3D.zo;
    VMatrix3D.translate(-dx,-dy,-dz);
    VMatrix3D.xrot(theta);
    VMatrix3D.translate(dx,dy,dz);
    view_repaint();
  }
  public synchronized void view_yrot(double theta){
    float dx = VMatrix3D.xo;
    float dy = VMatrix3D.yo;
    float dz = VMatrix3D.zo;
    VMatrix3D.translate(-dx,-dy,-dz);
    VMatrix3D.yrot(theta);
    VMatrix3D.translate(dx,dy,dz);
    view_repaint();
  }
  public synchronized void view_zrot(double theta){
    float dx = VMatrix3D.xo;
    float dy = VMatrix3D.yo;
    float dz = VMatrix3D.zo;
    VMatrix3D.translate(-dx,-dy,-dz);
    VMatrix3D.zrot(theta);
    VMatrix3D.translate(dx,dy,dz);
    view_repaint();
  }
  public synchronized void view_top(){
    VMatrix3D.view_top();
    view_repaint();
  }
  public synchronized void view_bottom(){
    VMatrix3D.view_bottom();
    view_repaint();
  }
  public synchronized void view_ne(){
    VMatrix3D.view_ne();
    view_repaint();
  }
  public synchronized void view_sw(){
    VMatrix3D.view_sw();
    view_repaint();
  }
  public synchronized void view_se(){
    VMatrix3D.view_se();
    view_repaint();
  }
  public synchronized void view_nw(){
    VMatrix3D.view_nw();
    view_repaint();
  }
  public synchronized void view_left(){
    VMatrix3D.view_left();
    view_repaint();
  }
  public synchronized void view_right(){
    VMatrix3D.view_right();
    view_repaint();
  }
  public synchronized void view_front(){
    VMatrix3D.view_front();
    view_repaint();
  }
  public synchronized void view_back(){
    VMatrix3D.view_back();
    view_repaint();
  }
  
  public synchronized void view_all(){
      int h = this.getHeight();
      int w = this.getWidth();
      Shape shp;
      
      double xmin = Double.MAX_VALUE;
      double ymin = Double.MAX_VALUE;
      double xmax = Double.MIN_VALUE;
      double ymax = Double.MIN_VALUE;
      
      for(int i=0; i<shp3d.size(); i++){
          try{
              shp = (Shape)shp3d.elementAt(i);  
              
              if (!(shp instanceof shpPointGrid)) { 
                  
                  Rectangle2D rect = shp.getBounds2D();
                  double xi_max = rect.getMaxX();
                  double xi_min = rect.getMinX();
                  double yi_max = rect.getMaxY();
                  double yi_min = rect.getMinY();
                  if(xmax<xi_max)xmax=xi_max;
                  if(xmin>xi_min)xmin=xi_min;
                  if(ymax<yi_max)ymax=yi_max;
                  if(ymin>yi_min)ymin=yi_min;
              }
          }catch(Exception e){}
      }
      
      double sw = w / (xmax-xmin);
      double sh = h / (ymax-ymin);
      double s = 0.8 * Math.min(sh,sw);
      
      view_scale((float)s);
      
      painted = true;
      view_repaint();
      
      xmin = Double.MAX_VALUE;
      ymin = Double.MAX_VALUE;
      xmax = Double.MIN_VALUE;
      ymax = Double.MIN_VALUE;
      
      for(int i=0; i<shp3d.size(); i++){
          try{
              shp = (Shape)shp3d.elementAt(i);  
              
              if (!(shp instanceof shpPointGrid)) { 
                  Rectangle2D rect = shp.getBounds2D();
                  double xi_max = rect.getMaxX();
                  double xi_min = rect.getMinX();
                  double yi_max = rect.getMaxY();
                  double yi_min = rect.getMinY();
                  if(xmax<xi_max)xmax=xi_max;
                  if(xmin>xi_min)xmin=xi_min;
                  if(ymax<yi_max)ymax=yi_max;
                  if(ymin>yi_min)ymin=yi_min;
              }
          }catch(Exception e){}
      }
      
      int mx = (int)((w / 2.0f) - (xmax+xmin) / 2.0f);
      int my = (int)((h / 2.0f) - (ymax+ymin) / 2.0f);
      
      this.view_translate(mx,-my,0);
      
      painted = true;
      view_repaint();
      
  }
  
  public synchronized boolean showhide() {

      _Object[] obj = this.getSelectedObjects3D();
      if (obj.length != 0)
          for (int i=0; i<obj.length; i++) 
              obj[i].setShow(!show);
      else
          show = !show;
      
      remove_select();
      if (pre != null) pre.clearTree();
      view_reset();
      view_repaint();
      
      return show;
  }
  
  
  public synchronized void view_grid(){
    for(int i=0; i<shp3d.size(); i++){
      if(shp3d.elementAt(i) instanceof shpPointGrid){
        shp3d.removeElementAt(i);
        i--;
      }
    }
    if(GRIDMODE)for(float i=LIMITS[0]; i<=LIMITS[2]; i+=GRIDSIZE){
      for(float j=LIMITS[1]; j<=LIMITS[3]; j+=GRIDSIZE){
        float x,y,z;
        if(GRIDPLANE==GRIDPLANE_XY){
          x=i;
          y=j;
          z=GRIDLEVEL;
        }else if(GRIDPLANE==GRIDPLANE_YZ){
          x=GRIDLEVEL;
          y=j;
          z=i;
        }else{
          x=i;
          y=GRIDLEVEL;
          z=j;
        }
        shpPointGrid gp = new shpPointGrid(x,y,z,GRIDCOLOR);
        gp.setShow(show);
        gp.transform2D(VMatrix3D, center_of_rotation);
        shp3d.addElement(gp);
      }
    }
    shpXYZ xyz = new shpXYZ();
    xyz.transform2D(VMatrix3D, center_of_rotation);
    shp3d.addElement(xyz);
    view_repaint();
  }
  
  public synchronized void view_scale(float s){
    float dx = VMatrix3D.xo;
    float dy = VMatrix3D.yo;
    float dz = VMatrix3D.zo;
    VMatrix3D.translate(-dx,-dy,-dz);
    VMatrix3D.scale(s);
    VMatrix3D.translate(dx,dy,dz);
    view_repaint();
  }

  void this_mouseDragged(MouseEvent e) {
      
      if(e.getModifiers()==MouseEvent.BUTTON3_MASK + MouseEvent.CTRL_MASK + MouseEvent.SHIFT_MASK){
          this.view_zrot(e.getY()-y);
      } else
          if (e.getModifiers()==MouseEvent.BUTTON3_MASK + MouseEvent.CTRL_MASK) {
              if(y-e.getY()>0) this.view_scale(0.9f);
              if(y-e.getY()<0) this.view_scale(1.1f);
          }else 
              if(e.getModifiers()==MouseEvent.BUTTON3_MASK + MouseEvent.SHIFT_MASK){
                  this.view_translate(e.getX()-x,y-e.getY(),0);
              }else
                  if(e.getModifiers()==MouseEvent.BUTTON3_MASK) {
                      this.view_yrot(e.getX()-x);
                      this.view_xrot(e.getY()-y);
                  } else
                      if (e.getModifiers()==MouseEvent.BUTTON1_MASK && DRAG == true)
                          view_repaint();
                      
      x=e.getX();
      y=e.getY();
  }

  public void save(String file) {
    try {
      FileOutputStream ostream = new FileOutputStream(file);
      ObjectOutputStream p = new ObjectOutputStream(ostream);
      p.writeObject(VMatrix3D);
      for(int i=0; i<obj3d.size(); i++) p.writeObject(obj3d.elementAt(i));
      p.flush();
      ostream.close();
    }catch(FileNotFoundException fnfe) {
      System.err.println("File not found: " + file);
    }catch(IOException ioe){ System.err.println("Could not write file: " + file); ioe.printStackTrace();}
  }
  public void save(ObjectOutputStream oos) {
    try {
      oos.writeObject(VMatrix3D);
      for(int i=0; i<obj3d.size(); i++) oos.writeObject(obj3d.elementAt(i));
    }catch(Exception e) { e.printStackTrace();}
  }
  public void open(String file) {
    try {
      FileInputStream istream = new FileInputStream(file);
      ObjectInputStream p = new ObjectInputStream(istream);
      remove_all();
      VMatrix3D=(Matrix3D)p.readObject();
      Object obj;
      try { while((obj = p.readObject())!=null) add3D(obj); }catch(EOFException eof){}
      view_repaint();
      p.close();
      istream.close();
    }catch(Exception ioe){ System.err.println("Could not open file: " + file+"\n"+ioe); }
  }
  public void open(ObjectInputStream ois) {
    try {
      //remove_all();
      VMatrix3D=(Matrix3D)ois.readObject();
      Object obj;
      try { while((obj = ois.readObject())!=null) add3D(obj); }catch(EOFException eof){}
      view_repaint();
    }catch(Exception e) { e.printStackTrace();}
  }

  public void save_Image(String file) {
      byte[] pngbytes;
      PngEncoderB png;
      
    try {
      if(file.toLowerCase().indexOf(".png")==-1) file+=".png";
      FileOutputStream out = new FileOutputStream(file);
      BufferedImage image = (BufferedImage)createImage(getWidth(), getHeight());
      Graphics g = image.getGraphics();
      paint(g);
      
      png = new PngEncoderB(image,false,0,5); // Encode at level 5 compression
      pngbytes = png.pngEncode();
      out.write(pngbytes);
      
      g.dispose();
      out.flush();
      out.close();
    }catch(FileNotFoundException fnfe) {
      System.err.println("File not found: " + file);
    }catch(IOException ioe){ System.err.println("Could not write file: " + file); }
  }

  public void open_stl(String file){
    this.add3D(new _Stl(file));
    view_repaint();
  }

  public void error(Object st){
    JOptionPane.showMessageDialog(null,"Error: "+st,"Error!",JOptionPane.ERROR_MESSAGE);
    if(st instanceof Exception) ((Exception)st).printStackTrace(); else System.out.println("Error: "+st);
  }

  public void rebuild() {
      int count = obj3d.size();
      for (int i=0; i<4; i++)
          for(int j=0; j<count; j++){
              _Object obj = ((_Object)obj3d.elementAt(j));
              if (obj.getGeometryType() == i)
                  obj.reset();
          }
      view_reset();
      view_repaint();
      tree_reset();
  }
  
  public void setCenterOfRotation() {
      _Object obj = this.getSelectedObject3D();
      if (obj != null) {
         center_of_rotation = obj.getCenter();
         center_of_rotation.sub(new Vector3D(0.0f,0.0f,0.0f),center_of_rotation);
      }
      view_reset();
      view_repaint();
  }
  
  /*
  public void addState(int c) {

  	STATE = STATE | c;

  }


  public void removeState(int c) {

  	STATE = STATE & (FULL-c);

  }
  */
  private void requestAction() {

      if (pre != null)
          pre.requestAction();
  }
  
  public Color BGCOLOR = Color.white;
  public static boolean DRAFTMODE=false;
  public static final byte GRAPHICSMODE_WIREFRAME=0;
  public static final byte GRAPHICSMODE_SURFACE=1;
  public static final byte GRAPHICSMODE_SOLID=2;
  public static final byte RENDERMODE_HIDE=0;
  public static final byte RENDERMODE_SHADE=1;
  public static final byte RENDERMODE_RENDER=2;
  //public static float GRADIENTCONTRAST=0.2f;
  //public static int GRADIENTCOLOR=50;
  public byte GRAPHICSMODE=2;
  public Color GRIDCOLOR=Color.black;
  public float GRIDLEVEL=0;
  public static final byte GRIDPLANE_XY=0;
  public static final byte GRIDPLANE_YZ=1;
  public static final byte GRIDPLANE_XZ=2;
  public byte GRIDPLANE=0;
  public boolean GRIDMODE=true;
  public float GRIDSIZE=10;
  public float[] LIMITS={0,0,210,297};
  public static final int MESH_Dummy_2=0;
  public static final int MESH_Rod_2=1;
  public static final int MESH_Beam_2=2;
  public static final int MESH_Beam_Spring_2=3;
  public static final int MESH_Contact_Line=4;
  public static final int MESH_Contact_Triangle=5;
  public static final int MESH_Dummy_4=6;
  public static final int MESH_Shell_C0_3=7;
  public static final int MESH_Shell_BT_4=8;
  public static final int MESH_Solid_Iso_6=9;
  public static int POINTSIZE=8;
  public static int NODESIZE=6;
  public byte RENDERMODE=2;
  public Color SELECTCOLOR=Color.red;
  public boolean SHOW_ID_NODE=false;
  public boolean SHOW_ID_ELEMENT=false;
  public boolean SHOW_ID_CONSTRAINTS=true;
  public boolean SHOW_ID_LOADS=true;
  public boolean SHOW_ID_TRACKERS=true;
  public Color STLCOLOR=new Color(0.7f,0.7f,0.7f);
  private boolean DRAG = false;
  
  // Codes for handling of geometry
  public static float GeometricTolerance = 0.1f;	// Tolerance for calculation of geometry
  public static float NODE_MERGE_TOLERANCE = 1e-3F;
  public static final int NONE = 0;
  public static final int CURVE = 1;
  public static final int SURFACE = 2;
  public static final int VOLUME = 3;
  public static final double PICKDISTANCE = 50; // Picking sensing distance squared (d*d)
  
  
}