package p5.test_newt_applet;

import java.applet.Applet;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Timer;
import java.util.TimerTask;

import javax.media.nativewindow.GraphicsConfigurationFactory;
import javax.media.nativewindow.NativeWindow;
import javax.media.nativewindow.NativeWindowFactory;
import javax.media.nativewindow.awt.AWTGraphicsConfiguration;
import javax.media.nativewindow.awt.AWTGraphicsDevice;
import javax.media.nativewindow.awt.AWTGraphicsScreen;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLProfile;

import javax.media.opengl.*;

import com.jogamp.newt.awt.NewtCanvasAWT;
import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.util.AnimatorBase;

public class MiniPApplet extends Applet {
  private static final long serialVersionUID = 1L;

  /////////////////////////////////////////////////////////////
  //
  // Test parameters  
  
  public int frameRate = 120;
  public boolean printThreadInfo = false;
  
  /////////////////////////////////////////////////////////////
  //
  // Internal variables
  
  private Frame frame;
  private GLProfile profile;
  private GLCapabilities capabilities;
  private NewtCanvasAWT canvas;
  private GLWindow window;

  private CustomAnimator animator;
  
  private long beforeTime;
  private long overSleepTime;
  private long frameRatePeriod = 1000000000L / frameRate;
  
  private boolean initialized = false;  
  
  private double theta = 0;
  private double s = 0;
  private double c = 0;  
  
  private long millisOffset;
  private int fcount, lastm;
  private float frate;
  private int fint = 3;
  
  void run() {
    Thread loop = new Thread("Animation Thread") {
      public void run() {         
        while (true) {          
          if (!initialized) {
            setup();            
          }
          
          animator.requestRender();
          
          clock();
        }
      }
    };
    loop.start();        
  }

  void setup() {
    if (printThreadInfo) System.out.println("Current thread at setup(): " + Thread.currentThread());
    
    millisOffset = System.currentTimeMillis();    

    // Frame setup ----------------------------------------------------------
    
    GraphicsEnvironment environment =
      GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice displayDevice = environment.getDefaultScreenDevice();
    frame = new Frame(displayDevice.getDefaultConfiguration());
    
    frame.setLayout(null);
    frame.setTitle("MiniPApplet");
    frame.pack();
    frame.setResizable(false);
    
    Insets insets = frame.getInsets();
    int windowW = 300 + insets.left + insets.right;
    int windowH = 300 + insets.top + insets.bottom;
    int locationX = 100; 
    int locationY = 100;
    
    frame.setSize(windowW, windowH);    
    frame.setLocation(locationX, locationY);   
    
    frame.add(this);
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
          System.exit(0);
      }
    });    
    
    int usableWindowH = windowH - insets.top - insets.bottom;
    this.setBounds((windowW - 300)/2, insets.top + (usableWindowH - 300)/2, 300, 300);
    
    frame.setVisible(true);

    // Canvas setup ----------------------------------------------------------
    
    profile = GLProfile.getMaxFixedFunc();
    capabilities = new GLCapabilities(profile); 
    capabilities.setSampleBuffers(false); 
    
    AWTGraphicsScreen screen = (AWTGraphicsScreen)AWTGraphicsScreen.createDefault();
    AWTGraphicsConfiguration config = (AWTGraphicsConfiguration)GraphicsConfigurationFactory
        .getFactory(AWTGraphicsDevice.class).chooseGraphicsConfiguration(capabilities, capabilities, null, screen);
    NativeWindow natWin = NativeWindowFactory.getNativeWindow(this, config);   
    
    window = GLWindow.create(natWin, capabilities);
    canvas = new NewtCanvasAWT(window);
    
    canvas.setBounds(0, 0, 300, 300);
    insets = frame.getInsets();
    canvas.setLocation(insets.left, insets.top);    
      
    this.add(canvas, this.getComponentCount() - 1);
    this.validate(); 
    
    // Setting up animation
    window.addGLEventListener(new Renderer());
    animator = new CustomAnimator(window);
    animator.setThreadName("OpenGL");
    animator.start();
    
    initialized = true;    
  }
 
  void draw(GL2 gl) {
    if (printThreadInfo) System.out.println("Current thread at draw(): " + Thread.currentThread());          
    
    gl.glClearColor(0, 0, 0, 1);
    gl.glClear(GL.GL_COLOR_BUFFER_BIT);
    
    theta += 0.01;
    s = Math.sin(theta);
    c = Math.cos(theta);      
    
    gl.glBegin(GL.GL_TRIANGLES);
    gl.glColor3f(1, 0, 0);
    gl.glVertex2d(-c, -c);
    gl.glColor3f(0, 1, 0);
    gl.glVertex2d(0, c);
    gl.glColor3f(0, 0, 1);
    gl.glVertex2d(s, -s);
    gl.glEnd();     
    
    gl.glFlush();
    
    fcount += 1;
    int m = (int) (System.currentTimeMillis() - millisOffset);
    if (m - lastm > 1000 * fint) {
      frate = (float)(fcount) / fint;
      fcount = 0;
      lastm = m;
      System.err.println("fps: " + frate); 
    }     
  }
  
  void clock() {
    long afterTime = System.nanoTime();
    long timeDiff = afterTime - beforeTime;
    long sleepTime = (frameRatePeriod - timeDiff) - overSleepTime;

    if (sleepTime > 0) {  // some time left in this cycle
      try {
        Thread.sleep(sleepTime / 1000000L, (int) (sleepTime % 1000000L));
      } catch (InterruptedException ex) { }

      overSleepTime = (System.nanoTime() - afterTime) - sleepTime;

    } else {    // sleepTime <= 0; the frame took longer than the period
      overSleepTime = 0L;
    }

    beforeTime = System.nanoTime();    
  }  
  
  class Renderer implements GLEventListener {
    @Override
    public void display(GLAutoDrawable drawable) {
      draw(drawable.getGL().getGL2());
    }

    @Override
    public void dispose(GLAutoDrawable drawable) { }

    @Override
    public void init(GLAutoDrawable drawable) { }

    @Override
    public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) { }    
  }
  
  public static void main(String[] args) {
    MiniPApplet mini;
    try {
      Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(MiniPApplet.class.getName());
      mini = (MiniPApplet) c.newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }    
    if (mini != null) {
      mini.run();
    }
  }      
  
  /** An Animator subclass which renders one frame at the time
   *  upon calls to the requestRender() method. 
   **/
  public class CustomAnimator extends AnimatorBase {    
      private Timer timer = null;
      private TimerTask task = null;
      private String threadName = null;
      private volatile boolean shouldRun;

      protected String getBaseName(String prefix) {
          return "Custom" + prefix + "Animator" ;
      }

      /** Creates an CustomAnimator with an initial drawable to 
       * animate. */
      public CustomAnimator(GLAutoDrawable drawable) {
          if (drawable != null) {
              add(drawable);
          }
      }
      
      public void setThreadName(String name) {
        threadName = name;
      }

      public synchronized void requestRender() {
          shouldRun = true;
      }

      public final boolean isStarted() {
          stateSync.lock();
          try {
              return (timer != null);
          } finally {
              stateSync.unlock();
          }
      }

      public final boolean isAnimating() {
          stateSync.lock();
          try {
              return (timer != null) && (task != null);
          } finally {
              stateSync.unlock();
          }
      }

      private void startTask() {
          if(null != task) {
              return;
          }
          
          task = new TimerTask() {
              private boolean firstRun = true;
              public void run() {
                  if (firstRun) {
                    if (threadName != null) Thread.currentThread().setName(threadName);
                    firstRun = false;
                  }
                  if(CustomAnimator.this.shouldRun) {
                     CustomAnimator.this.animThread = Thread.currentThread();
                      // display impl. uses synchronized block on the animator instance
                      display();                
                      synchronized (this) {
                        // done with current frame.
                        shouldRun = false;
                      }                    
                  }
              }
          };

          fpsCounter.resetFPSCounter();
          shouldRun = false;
          
          timer.schedule(task, 0, 1);
      }
      
      public synchronized boolean  start() {
          if (timer != null) {
              return false;
          }
          stateSync.lock();
          try {
              timer = new Timer();
              startTask();
          } finally {
              stateSync.unlock();
          }
          return true;
      }

      /** Stops this CustomAnimator. */
      public synchronized boolean stop() {
          if (timer == null) {
              return false;
          }
          stateSync.lock();
          try {
              shouldRun = false;
              if(null != task) {
                  task.cancel();
                  task = null;
              }
              if(null != timer) {
                  timer.cancel();
                  timer = null;
              }
              animThread = null;
              try {
                  Thread.sleep(20); // ~ 1/60 hz wait, since we can't ctrl stopped threads
              } catch (InterruptedException e) { }
          } finally {
              stateSync.unlock();
          }
          return true;
      }
      
      public final boolean isPaused() { return false; }
      public synchronized boolean resume() { return false; }
      public synchronized boolean pause() { return false; }    
  }
}