/*
 * 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.*;
/**
 * Insert the type's description here.
 *
 * @author: Yuriy Mikhaylovskiy
 */

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 JLabel coodinates = new JLabel();
  private QSortZ qsort = new QSortZ();
  private int x,y;

  public Canvas3D() {
    addComponentListener(new ComponentAdapter() {
      public void componentResized(ComponentEvent e) {
        offscreen=null;
      }
    });
    addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        x=e.getX(); y=e.getY();
      }
      public void mouseReleased(MouseEvent e) {
        DRAFTMODE = false;
        repaint();
      }
    });
    addMouseMotionListener(new MouseMotionAdapter() {
      public void mouseMoved(MouseEvent e) {
        coodinates.setText(e.getX()+","+e.getY());
      }
      public void mouseDragged(MouseEvent e) {
        DRAFTMODE = true;
        this_mouseDragged(e);
      }
    });
    VMatrix3D.translate(400,-300,0);
    VMatrix3D.scale(1,-1,1);
    view_grid();
  }
  /*public void init(){
    VMatrix3D.translate(getWidth()/2,-getHeight()/2,0);
    VMatrix3D.scale(1,-1,1);
    view_grid();
  }*/
  private synchronized void setPainted(){
    painted = true;
    notifyAll();
  }
  public synchronized void paint(Graphics g){
    if (offscreen == null){
      if (offgraphics != null) offgraphics.dispose();
      if (offscreen != null) offscreen.flush();
      offscreen = (BufferedImage)createImage(getWidth(), getHeight());
      offgraphics = (Graphics2D)offscreen.getGraphics();
    }
    offgraphics.setColor(BGCOLOR);
    offgraphics.fillRect(0, 0, getWidth(), getHeight());
    for(int i=0; i<shp3d.size(); i++){
      ((shp)shp3d.elementAt(i)).paint(offgraphics,this);
    }
    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);
    qsort.sort(shp3d);
  }
  public int add3D(Object obj){
    id_obj3d++;
    ((_Object)obj).set_Id(id_obj3d+"");
    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);
      shp3d.addElement(arr[i]);
    }
    return id_obj3d;
  }
  public _Object getSelectedObject3D(){
    int count = obj3d.size();
    for(int i=0; i<count; i++){
      _Object obj = ((_Object)obj3d.elementAt(i));
      if(obj.isSelected())return obj;
    }
    return null;
  }
  public _Object[] getSelectedObjects3D() {
    int count = obj3d.size();
    int count_sel = 0;
    for(int i=0; i<count; i++){
      _Object obj = ((_Object)obj3d.elementAt(i));
      if(obj.isSelected())count_sel++;
    }
    _Object[] arr = new _Object[count_sel];
    int k=0;
    for(int i=0; i<count; i++){
      _Object obj = ((_Object)obj3d.elementAt(i));
      if(obj.isSelected()){
        arr[k]=obj;
        k++;
      }
    }
    return arr;
  }
  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 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 transform3D(Matrix3D m){
    int count = obj3d.size();
    for(int i=0; i<count; i++)  ((_Object)obj3d.elementAt(i)).transform3D(m);
    tree3d.reload();
    view_reset();
  }
  /*public void copy(Matrix3D m){
    int count = obj3d.size();
    for(int i=0; i<count; i++){
      _Object obj = (_Object)obj3d.elementAt(i);
      Object o = obj.get_Copy(m);
      if(o!=null){
        obj = ((_Object)o);
        id_obj3d++;
        obj.set_Id(id_obj3d);
        obj3d.addElement(obj);
      }
    }
    tree_reset();
    view_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++){
        //System.out.println(arr[j]);
        ((shp)arr[j]).transform2D(VMatrix3D);
        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();
    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{
        Rectangle2D rect = ((Shape)shp3d.elementAt(i)).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 = Math.min(sh,sw);
    //view_translate((float)(-xmin*VMatrix3D.sx-VMatrix3D.xo*VMatrix3D.sx),(float)(-ymin*VMatrix3D.sy-VMatrix3D.yo*VMatrix3D.sy-h),0);
    view_scale((float)s);
    //view_translate((float)(s*xmin-VMatrix3D.xo),(float)(-s*ymin+s*VMatrix3D.yo-s*h),0);
    VMatrix3D.translate((float)(-xmin/s-VMatrix3D.xo/s),0,0);
    painted = true;
    view_repaint();
    //System.out.println(xmin+" "+xmax+" "+ymin+" "+ymax);
  }
  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.transform2D(VMatrix3D);
        shp3d.addElement(gp);
      }
    }
    shpXYZ xyz = new shpXYZ();
    xyz.transform2D(VMatrix3D);
    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()==e.BUTTON1_MASK){
      this.view_yrot(e.getX()-x);
      this.view_xrot(e.getY()-y);
    }else if(e.getModifiers()==e.BUTTON3_MASK){
      this.view_translate(e.getX()-x,y-e.getY(),0);
    }else{
      if(e.getX()-x+y-e.getY()>0)this.view_scale(0.9f);else this.view_scale(1.1f);
    }
    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) {
    try {
      if(file.toLowerCase().indexOf(".jpg")==-1 && file.toLowerCase().indexOf(".jpeg")==-1) file+=".jpg";
      FileOutputStream out = new FileOutputStream(file);
      BufferedImage image = (BufferedImage)createImage(getWidth(), getHeight());
      Graphics g = image.getGraphics();
      paint(g);
      JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
      JPEGEncodeParam jep = encoder.getDefaultJPEGEncodeParam(image);
      jep.setQuality(1.0f, false);
      encoder.setJPEGEncodeParam(jep);
      encoder.encode(image);
      g.dispose();
      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 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 byte MESH_Rod_2=0;
  public static final byte MESH_Beam_2=1;
  public static final byte MESH_Beam_Spring_2=2;
  public static final byte MESH_Contact_Line=3;
  public static final byte MESH_Contact_Triangle=4;
  public static final byte MESH_Shell_C0_3=5;
  public static final byte MESH_Shell_BT_4=6;
  public static final byte MESH_Solid_Iso_6=7;
  public static int POINTSIZE=4;
  public byte RENDERMODE=2;
  public Color SELECTCOLOR=Color.red;
  public boolean SHOW_ID_NODE=true;
  public boolean SHOW_ID_ELEMENT=true;
  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);

}