/*
 *
 * OpenSimBaseCanvas
 * Author(s): Ayman Habib & Jeff Reinbolt
 * Copyright (c) 2005-2006, Stanford University, Ayman Habib & Jeff Reinbolt
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.opensim.view.base;

import com.sun.opengl.impl.Project;
import com.sun.opengl.util.GLUT;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import javax.swing.JPopupMenu;
import vtk.vtkCamera;

import javax.media.opengl.*;
import org.opensim.view.pub.ViewDB;
import vtk.vtkRenderer;
import vtk.vtkPanel;


class Camera {
   Project project = new Project();

   private float rotx = 0;
   private float roty = 0;
   private float rotz = 0;
   private float distance = 3;
   private float fovy = 60;

   public float getRotX() { return rotx; }
   public float getRotY() { return roty; }
   public float getRotZ() { return rotz; }
   public float getDistance() { return distance; }

   public void setRotX(float val) { rotx = val; }
   public void setRotY(float val) { roty = val; }
   public void setRotZ(float val) { rotz = val; }
   public void setDistance(float val) { distance = val; }

   public void applyCameraTransform(GL gl)
   {
      gl.glTranslatef(0.0f, 0.0f, -distance);
      gl.glRotatef(rotx, 1.0f, 0.0f, 0.0f);
      gl.glRotatef(roty, 0.0f, 1.0f, 0.0f);
      gl.glRotatef(rotz, 0.0f, 0.0f, 1.0f);
   }

   public void applyProjectionTransform(GL gl, int width, int height)
   {
      double h = (double)height / (double)width;
      //gl.glFrustum(-1.0f, 1.0f, -h, h, 0.1f, 1.0f);
      project.gluPerspective(gl, fovy, 1/h, 0.1, 1.0);
   }
}

/**
 *
 * @author Ayman Habib
 *
 * Base class for the Canvas to collect all the properties to be shared by OpenSim based
 * applications and to enforce behvior (e.g colors, camera, mouse interaction) that's not specific
 * to OpenSim's Top Gui Application.
 */
public class OpenSimBaseCanvas extends GLCanvas implements GLEventListener, MouseListener, MouseMotionListener, KeyListener {

   public enum InteractionMode { Rotate, Translate, Zoom };
   InteractionMode currentInteractionMode;
   
   String defaultBackgroundColor="0.15, 0.15, 0.15";
   int    numAAFrames=0;
   JPopupMenu settingsMenu = new JPopupMenu();
   CamerasMenu camerasMenu;

   Camera camera = new Camera();

   // Enable opoups to display on top of heavy weight component/canvas
   static {
      JPopupMenu.setDefaultLightWeightPopupEnabled(false);
      javax.swing.ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);

      // Force the vtkPanel static code to be called, which loads the vtk libraries (important for backwards compatibility for now)
      vtkPanel panel = new vtkPanel();
   }

   /** Creates a new instance of OpenSimBaseCanvas */
   public OpenSimBaseCanvas() {
      super.addGLEventListener(this);

      /*
      defaultBackgroundColor = Preferences.userNodeForPackage(TheApp.class).get("BackgroundColor", defaultBackgroundColor);
      double[] background = Prefs.getInstance().parseColor(defaultBackgroundColor);
      GetRenderer().SetBackground(background);
      createSettingsMenu();
      camerasMenu = new CamerasMenu(this);
      addKeyListener(this);
      // AntiAliasing
      int desiredAAFrames = Preferences.userNodeForPackage(TheApp.class).getInt("AntiAliasingFrames", numAAFrames);
      if (desiredAAFrames >=0 && desiredAAFrames<=10){
         numAAFrames=desiredAAFrames;
      }
      GetRenderWindow().SetAAFrames(numAAFrames);
      */ 
   }

  private float view_rotx = 20.0f, view_roty = 30.0f, view_rotz = 0.0f;
  private int sphere;
  private int cylinder;
  private float angle = 0.0f;

  public int getSphereDisplayList() { return sphere; }
  public int getCylinderDisplayList() { return sphere; }

  private int prevMouseX, prevMouseY;
  private boolean mouseRButtonDown = false;


   public void init(GLAutoDrawable drawable)
   {
      System.out.println("==================init");

      GL gl = drawable.getGL();

      System.err.println("INIT GL IS: " + gl.getClass().getName());
      System.err.println("Chosen GLCapabilities: " + drawable.getChosenGLCapabilities());

      gl.setSwapInterval(1);

      float pos[] = { 5.0f, 5.0f, 10.0f, 0.0f };
      float red[] = { 0.8f, 0.1f, 0.0f, 1.0f };
      float green[] = { 0.0f, 0.8f, 0.2f, 1.0f };
      float blue[] = { 0.2f, 0.2f, 1.0f, 1.0f };

      gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, pos, 0);
      gl.glEnable(GL.GL_CULL_FACE);
      gl.glEnable(GL.GL_LIGHTING);
      gl.glEnable(GL.GL_LIGHT0);
      gl.glEnable(GL.GL_DEPTH_TEST);
            
      sphere = gl.glGenLists(1);
      gl.glNewList(sphere, GL.GL_COMPILE);
      gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, blue, 0);
      makeSphere(gl, 0.005f);
      gl.glEndList();

      cylinder = gl.glGenLists(1);
      gl.glNewList(cylinder, GL.GL_COMPILE);
      gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, red, 0);
      makeCylinder(gl, 0.005f, 1f);
      gl.glEndList();
            
      gl.glEnable(GL.GL_NORMALIZE);
                
      drawable.addMouseListener(this);
      drawable.addMouseMotionListener(this);

      gl.glMatrixMode(GL.GL_MODELVIEW);
      gl.glLoadIdentity();
   }

   public void updateProjectionTransform(GL gl, int width, int height)
   {
      gl.glMatrixMode(GL.GL_PROJECTION);
      gl.glLoadIdentity();
      camera.applyProjectionTransform(gl, width, height);
   }

   public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) 
   {
      GL gl = drawable.getGL();
      updateProjectionTransform(gl, width, height);
   }

  public void display(GLAutoDrawable drawable) {

    GL gl = drawable.getGL();
    if ((drawable instanceof GLJPanel) &&
        !((GLJPanel) drawable).isOpaque() &&
        ((GLJPanel) drawable).shouldPreserveColorBufferIfTranslucent()) {
      gl.glClear(GL.GL_DEPTH_BUFFER_BIT);
    } else {
      gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
    }
            
    gl.glPushMatrix();
    camera.applyCameraTransform(gl);

    ViewDB.getInstance().displayAllModels(drawable, this);

     /*
    angle += 2.0f;
    int n=10;
    for(int i=0; i<n; i++) for(int j=0; j<n; j++) for(int k=0; k<n; k++) {
       gl.glPushMatrix();
       gl.glTranslatef((float)i,(float)j,(float)k);
       gl.glRotatef(-2.0f * angle - 25.0f, 0.0f, 0.0f, 1.0f);
       gl.glCallList(sphere);
       gl.glPopMatrix();
   }
            
    for(int i=0; i<n; i++) for(int j=0; j<n; j++) for(int k=0; k<n; k++) {
       gl.glPushMatrix();
       gl.glTranslatef((float)i,(float)j,(float)k);
       gl.glRotatef(-2.0f * angle - 25.0f, 0.0f, 0.0f, 1.0f);
       gl.glCallList(cylinder);
       gl.glPopMatrix();
   }
    */
            
    gl.glPopMatrix();
  }

   public void update(Graphics g) {
      long before = System.nanoTime();
      super.update(g);
      long after = System.nanoTime();
      System.out.println("Update time "+(1e-6*(after-before))+" ms");
   }

  public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}

  public static void makeSphere(GL gl, float radius)
  {
    gl.glNormal3f(0.0f, 0.0f, 1.0f);
    gl.glShadeModel(GL.GL_SMOOTH);
    GLUT glut = new GLUT();
    glut.glutSolidSphere(radius,8,8);
  }

  public static void makeCylinder(GL gl, float radius, float height)
  {
    gl.glNormal3f(0.0f, 0.0f, 1.0f);
    gl.glShadeModel(GL.GL_SMOOTH);
    GLUT glut = new GLUT();
    glut.glutSolidCylinder(radius,height,8,8);
  }

   
   public void mouseClicked(MouseEvent e) {}
   public void mouseReleased(MouseEvent e) {}
   public void mouseMoved(MouseEvent e) {}

   public void mousePressed(MouseEvent e) {
      lastX = e.getX();
      lastY = e.getY();

      // Show popup if right mouse and Shift key, otherwise pass along to super implementation
      if ((e.getModifiers() == (InputEvent.BUTTON3_MASK | InputEvent.SHIFT_MASK))) {
         settingsMenu.show(this, e.getX(), e.getY());
      } else if ((e.getModifiers()==InputEvent.BUTTON2_MASK) ||
        (e.getModifiers()==(InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK))) {
         currentInteractionMode = InteractionMode.Translate;
      } else if (e.getModifiers()==InputEvent.BUTTON3_MASK) {
         currentInteractionMode = InteractionMode.Zoom;
      } else {
         currentInteractionMode = InteractionMode.Rotate;
      }
   }
   
   public void mouseDragged(MouseEvent e) {
      // do nothing (handled by settingsMenu) if right mouse and Shift, otherwise pass along to super implementation
      if ((e.getModifiers() == (InputEvent.BUTTON3_MASK | InputEvent.SHIFT_MASK))) return;

      int x = e.getX();
      int y = e.getY();

      if(currentInteractionMode == InteractionMode.Rotate) {
         Dimension size = e.getComponent().getSize();
         float thetaY = 360.0f * ( (float)(x-lastX)/(float)size.width);
         float thetaX = 360.0f * ( (float)(lastY-y)/(float)size.height);
         camera.setRotX(camera.getRotX() + thetaX);
         camera.setRotY(camera.getRotY() + thetaY);
      } else if(currentInteractionMode == InteractionMode.Translate) {



      } else if(currentInteractionMode == InteractionMode.Zoom) {
        double zoomFactor = Math.pow(1.02,(y - lastY));
        camera.setDistance(camera.getDistance()*(float)zoomFactor);
      }

      lastX = x;
      lastY = y;

      repaint();
   }


   // Mouse enter / exit
   public void mouseEntered(MouseEvent e) { this.requestFocus(); }
   public void mouseExited(MouseEvent e) {}
  
  
   
   public void createSettingsMenu() {
      settingsMenu = new JPopupMenu();
      /** This should work and is more netBeans like style, but somehow fails to find Actions
       * possibly because of layer file issues*/
      try {
         
         settingsMenu.add((ModifyWindowSettingsAction) (ModifyWindowSettingsAction.findObject(
                 Class.forName("org.opensim.view.base.ModifyWindowSettingsAction"), true)));
         settingsMenu.add((ToggleAxesAction) ToggleAxesAction.findObject(
                 Class.forName("org.opensim.view.base.ToggleAxesAction"), true));
      } catch (ClassNotFoundException ex) {
         ex.printStackTrace();
      }
       /*
      settingsMenu.add(new ModifyWindowSettingsAction());
      settingsMenu.add(new ToggleAxesAction());
        **/
   }
   
   public JPopupMenu getMenu() {
      return settingsMenu;
   }
   /**
    * Handle keys for default cameras, otherwise pass on to super
    * to get default vtkPanel behavior.
    */
   public void keyPressed(KeyEvent e) {
      /*
      char keyChar = e.getKeyChar();
      
      if ('x' == keyChar) {
         applyCameraMinusX();
      } else if ('y' == keyChar) {
         applyCameraMinusY();
      } else if ('z' == keyChar) {
         applyCameraMinusZ();
      } else if ('i' == keyChar) {
         GetRenderer().GetActiveCamera().Zoom(1.01);
         repaint();
      } else if ('o' == keyChar) {
         GetRenderer().GetActiveCamera().Zoom(0.99);
         repaint();
      }
      super.keyPressed(e);
      */
   }

   public void keyReleased(KeyEvent e) {}

   public void keyTyped(KeyEvent e) {}
   
   public void applyCameraPlusY() {
      applyCamera(camerasMenu.pickStandardCamera("Bottom"));
   }
   
   public void applyCameraMinusY() {
      //applyCamera(camerasMenu.pickStandardCamera("Top"));
   }
   
<<<<<<< .mine
   public void applyCameraZ() {
      //applyCamera(camerasMenu.pickStandardCamera("Side"));
=======
   public void applyCameraPlusZ() {
      applyCamera(camerasMenu.pickStandardCamera("Left"));
>>>>>>> .r3337
   }
   
   public void applyCameraMinusZ() {
      applyCamera(camerasMenu.pickStandardCamera("Right"));
   }
   
   public void applyCameraPlusX() {
      applyCamera(camerasMenu.pickStandardCamera("Back"));
   }
   
   public void applyCameraMinusX() {
      //applyCamera(camerasMenu.pickStandardCamera("Front"));
   }
   
   /**
    * A method to apply a prespecified Camera (selectedCamera) to the current Canvas
    */
   public void applyCamera(vtkCamera selectedCamera) {
      /*
      vtkCamera currentCamera = GetRenderer().GetActiveCamera();
      currentCamera.SetPosition(selectedCamera.GetPosition());
      currentCamera.SetFocalPoint(selectedCamera.GetFocalPoint());
      currentCamera.SetViewAngle(selectedCamera.GetViewAngle());
      currentCamera.SetDistance(selectedCamera.GetDistance());
      currentCamera.SetClippingRange(selectedCamera.GetClippingRange());
      currentCamera.SetViewUp(selectedCamera.GetViewUp());
      currentCamera.SetParallelScale(selectedCamera.GetParallelScale());
      
      vtkLightCollection lights = GetRenderer().GetLights();
      lights.RemoveAllItems();
      GetRenderer().CreateLight();
      GetRenderer().ResetCamera();
      //GetRenderer().Render();
      repaint();
      */
   }
  
   // Methods from vtkPanel which we don't have...
   protected vtkRenderer ren = new vtkRenderer();
   protected int lastX;
   protected int lastY;
   public void HardCopy(String filename, int mag) {}
   public void resetCamera() {}
   public void resetCameraClippingRange() {}
   public synchronized void Render() {}
   public vtkRenderer GetRenderer() { return ren; }
}
