package org.simtk.moleculargraphics;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.SwingUtilities;
import javax.swing.JPanel;
import vtk.*;

public class vtkSwingPanel extends JPanel implements 
  MouseListener, 
  MouseMotionListener, 
  KeyListener
{
  protected vtkRenderWindow rw = new vtkRenderWindow();     
  protected vtkRenderer ren = new vtkRenderer();
  protected vtkCamera cam = null;
  protected vtkLight lgt = new vtkLight();
  protected int lastX;
  protected int lastY;
  protected int windowset = 0;
  protected int lightingset = 0;
  protected int LightFollowCamera = 1;
  protected int InteractionMode = 1;
  protected boolean rendering = false;
  protected WindowObservable windowSetObservable = new WindowObservable();

  private PublicNativeVtkPanel privatePanel = new PublicNativeVtkPanel();
  
  static { 
    System.loadLibrary("vtkCommonJava"); 
    System.loadLibrary("vtkFilteringJava"); 
    System.loadLibrary("vtkIOJava"); 
    System.loadLibrary("vtkImagingJava"); 
    System.loadLibrary("vtkGraphicsJava"); 
    System.loadLibrary("vtkRenderingJava"); 
    try {
      System.loadLibrary("vtkHybridJava");
    } catch (Throwable e) {
      System.out.println("cannot load vtkHybrid, skipping...");
    }
    try {
      System.loadLibrary("vtkVolumeRenderingJava");
    } catch (Throwable e) {
      System.out.println("cannot load vtkVolumeRendering, skipping...");
    }
  }

  protected int RenderCreate(vtkRenderWindow id0) {
      return privatePanel.RenderCreate(id0);
  }
  protected int Lock() {
      return privatePanel.Lock();
  }
  protected int UnLock() {
      return privatePanel.UnLock();
  }

  public vtkSwingPanel()
  {
    rw.AddRenderer(ren);
    addMouseListener(this);
    addMouseMotionListener(this);
    addKeyListener(this);
    super.setSize(200,200);
    rw.SetSize(200,200);
    addWindowSetObserver(new WindowSetObserver());
  }

  public void Report() {

    // must be performed on awt event thread
    Runnable updateAComponent = new Runnable() {
        public void run() {
          Lock();
          System.out.println("direct rendering = " + (rw.IsDirect()==1));
          System.out.println("opengl supported = " + (rw.SupportsOpenGL()==1));
          System.out.println("report = " + rw.ReportCapabilities());
          UnLock();
        }
      };

    SwingUtilities.invokeLater(updateAComponent);

  }

  public vtkRenderer GetRenderer()
  {
    return ren;
  }
  
  public vtkRenderWindow GetRenderWindow()
  {
    return rw;
  }

  public void addWindowSetObserver(Observer obs) {
    windowSetObservable.addObserver(obs);
  }

  public void removeWindowSetObserver(Observer obs) {
    windowSetObservable.deleteObserver(obs);
  }
  
  public void setSize(int x, int y)
  {
    super.setSize(x,y);
    if (windowset == 1) 
      {
        Lock();
        rw.SetSize(x,y);
        UnLock();
      }
  }

  public void addNotify() 
  {
    super.addNotify();
    windowset = 0;
    rw.SetForceMakeCurrent();
    rendering = false;
  }

  public void removeNotify() 
  {
    rendering = true;
    super.removeNotify();
  }
  
  public synchronized void Render() 
  {
    if (!rendering)
      {
        rendering = true;
        if (ren.VisibleActorCount() == 0) {rendering = false; return;}
        if (rw != null)
          {
            if (windowset == 0)
              {
                // set the window id and the active camera
                cam = ren.GetActiveCamera();
                if (lightingset == 0) {
                  ren.AddLight(lgt);
                  lgt.SetPosition(cam.GetPosition());
                  lgt.SetFocalPoint(cam.GetFocalPoint());
                  lightingset = 1;
                }
                RenderCreate(rw);
                Lock();
                rw.SetSize(getWidth(), getHeight());
                UnLock();
                windowset = 1;
                // notify observers that we have a renderwindow created
                windowSetObservable.notifyObservers();
              }
            Lock();
            rw.Render();
            UnLock();
            rendering = false;
          }
      }
  }

  public boolean isWindowSet() 
  {
    return (this.windowset==1);
  }
  
  public void paint(Graphics g)
  {
    this.Render();
  }

  public void update(Graphics g) {
    paint(g);
  }
  
  public void LightFollowCameraOn()
  {
    this.LightFollowCamera = 1;
  }
  
  public void LightFollowCameraOff()
  {
    this.LightFollowCamera = 0;
  }
  
  public void InteractionModeRotate()
  {
    this.InteractionMode = 1;
  }
  
  public void InteractionModeTranslate()
  {
    this.InteractionMode = 2;
  }
  
  public void InteractionModeZoom()
  {
    this.InteractionMode = 3;
  }
  
  public void UpdateLight()
  {
    lgt.SetPosition(cam.GetPosition());
    lgt.SetFocalPoint(cam.GetFocalPoint());
  }

  public void resetCameraClippingRange() {
    Lock();
    ren.ResetCameraClippingRange();
    UnLock();
  }

  public void resetCamera() {
    Lock();
    ren.ResetCamera();
    UnLock();
  }
  
  public void mouseClicked(MouseEvent e) {
    
  }
  
  public void mousePressed(MouseEvent e)
  {
      
    if (ren.VisibleActorCount() == 0) return;
    rw.SetDesiredUpdateRate(5.0);
    lastX = e.getX();
    lastY = e.getY();
    if ((e.getModifiers()==InputEvent.BUTTON2_MASK) ||
        (e.getModifiers()==(InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK)))
      {
        InteractionModeTranslate();
      }
    else if (e.getModifiers()==InputEvent.BUTTON3_MASK)
      {
        InteractionModeZoom();
      }
    else 
      {
        InteractionModeRotate();
      }
  }
  
  public void mouseReleased(MouseEvent e)
  {
    rw.SetDesiredUpdateRate(0.01);
  }
  
  public void mouseEntered(MouseEvent e) 
  {
    this.requestFocus();
  }
  
  public void mouseExited(MouseEvent e) {}
  
  public void mouseMoved(MouseEvent e) 
  {
    lastX = e.getX();
    lastY = e.getY();
  }
  
  
  public void mouseDragged(MouseEvent e)
  {
    if (ren.VisibleActorCount() == 0) return;
    int x = e.getX();
    int y = e.getY();
    // rotate
    if (this.InteractionMode == 1)
      {
        cam.Azimuth(lastX - x);
        cam.Elevation(y - lastY);
        cam.OrthogonalizeViewUp();
        resetCameraClippingRange();
        if (this.LightFollowCamera == 1)
          {
            lgt.SetPosition(cam.GetPosition());
            lgt.SetFocalPoint(cam.GetFocalPoint());
          }
      }
    // translate
    if (this.InteractionMode == 2)
      {
        double  FPoint[];
        double  PPoint[];
        double  APoint[] = new double[3];
        double  RPoint[];
        double focalDepth;
        
        // get the current focal point and position
        FPoint = cam.GetFocalPoint();
        PPoint = cam.GetPosition();
        
        // calculate the focal depth since we'll be using it a lot
        ren.SetWorldPoint(FPoint[0],FPoint[1],FPoint[2],1.0);
        ren.WorldToDisplay();
        focalDepth = ren.GetDisplayPoint()[2];
        
        APoint[0] = rw.GetSize()[0]/2.0 + (x - lastX);
        APoint[1] = rw.GetSize()[1]/2.0 - (y - lastY);
        APoint[2] = focalDepth;
        ren.SetDisplayPoint(APoint);
        ren.DisplayToWorld();
        RPoint = ren.GetWorldPoint();
        if (RPoint[3] != 0.0)
          {
            RPoint[0] = RPoint[0]/RPoint[3];
            RPoint[1] = RPoint[1]/RPoint[3];
            RPoint[2] = RPoint[2]/RPoint[3];
          }
        
        /*
         * Compute a translation vector, moving everything 1/2 
         * the distance to the cursor. (Arbitrary scale factor)
         */
        cam.SetFocalPoint(
                          (FPoint[0]-RPoint[0])/2.0 + FPoint[0],
                          (FPoint[1]-RPoint[1])/2.0 + FPoint[1],
                          (FPoint[2]-RPoint[2])/2.0 + FPoint[2]);
        cam.SetPosition(
                        (FPoint[0]-RPoint[0])/2.0 + PPoint[0],
                        (FPoint[1]-RPoint[1])/2.0 + PPoint[1],
                        (FPoint[2]-RPoint[2])/2.0 + PPoint[2]);
        resetCameraClippingRange();
      }
    // zoom
    if (this.InteractionMode == 3)
      {
        double zoomFactor;
        double clippingRange[];
        
        zoomFactor = Math.pow(1.02,(y - lastY));
        if (cam.GetParallelProjection() == 1)
          {
            cam.SetParallelScale(cam.GetParallelScale()/zoomFactor);
          }
        else
          {
            cam.Dolly(zoomFactor);
            resetCameraClippingRange();
          }
      }
    lastX = x;
    lastY = y;
    this.Render();
  }
  
  public void keyTyped(KeyEvent e) {}
  
  public void keyPressed(KeyEvent e)
  {
    if (ren.VisibleActorCount() == 0) return;
    char keyChar = e.getKeyChar();
      
    if ('r' == keyChar)
      {
        resetCamera();
        this.Render();
      }
    if ('u' == keyChar)
      {
        pickActor(lastX, lastY);
      }
    if ('w' == keyChar)
      {
        vtkActorCollection ac;
        vtkActor anActor;
        vtkActor aPart;
        int i, j;
        
        ac = ren.GetActors();
        ac.InitTraversal();
        for (i = 0; i < ac.GetNumberOfItems(); i++)
          {
            anActor = ac.GetNextActor();
            anActor.InitPartTraversal();
            for (j = 0; j < anActor.GetNumberOfParts(); j++)
              { 
                aPart = anActor.GetNextPart();
                aPart.GetProperty().SetRepresentationToWireframe();
              }
          }
        this.Render();
      }
    if ('s' == keyChar)
      {
        vtkActorCollection ac;
        vtkActor anActor;
        vtkActor aPart;
        int i, j;
        
        ac = ren.GetActors();
        ac.InitTraversal();
        for (i = 0; i < ac.GetNumberOfItems(); i++)
          {
            anActor = ac.GetNextActor();
            anActor.InitPartTraversal();
            for (j = 0; j < anActor.GetNumberOfParts(); j++)
              { 
                aPart = anActor.GetNextPart();
                aPart.GetProperty().SetRepresentationToSurface();
              }
          }
        this.Render();
      }
  }

  public void HardCopy(String filename, int mag) { 

    Lock();

    vtkWindowToImageFilter w2if = new vtkWindowToImageFilter();
    w2if.SetInput(rw);

    w2if.SetMagnification(mag);
    w2if.Update();

    vtkTIFFWriter writer = new vtkTIFFWriter();
    writer.SetInput(w2if.GetOutput());
    writer.SetFileName(filename);
    writer.Write();

    UnLock();
  }

  public void pickActor(int x, int y) {

    vtkPropPicker picker = new vtkPropPicker();

    Lock();
    picker.PickProp(x, rw.GetSize()[1] - y , ren);
    UnLock();

    if (picker.GetActor() != null)
      System.out.println(picker.GetActor().GetClassName());
  }
  
  public void keyReleased(KeyEvent e) {}

  private class WindowObservable extends Observable {

    public void notifyObservers() {
      this.setChanged();
      super.notifyObservers();
    }
     
    public void notifyObservers(Object message) {
      this.setChanged();
      super.notifyObservers(message);
    }
    
    public boolean hasObservers() {
      return 0 < super.countObservers();
    }
  }

  private class WindowSetObserver implements Observer {
    
    public void update(Observable o, Object arg) {
      // we know the window is set, so changes to the render window size
      // will actually take place
//       if (getWidth() > 0 && getHeight() > 0)
//         rw.SetSize(getWidth(), getHeight());
    }
    
  }

}
