package demos.xtrans;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.beans.*;
import java.nio.*;
import java.util.*;
import javax.swing.*;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.impl.*;

/** A desktop manager implementation supporting accelerated
    transitions of the components on the desktop via OpenGL. This
    class does not need to be instantiated by end users; it is
    installed automatically when an XTDesktopPane is constructed. */
public class XTDesktopManager extends OffscreenDesktopManager {
  private GLContext j2dContext;
  private Object    j2dContextSurfaceIdentifier;
  private int       oglTextureId;
  private int       prevBackBufferWidth;
  private int       prevBackBufferHeight;

  private int textureTarget = GL.GL_TEXTURE_2D;

  /** Returns the OpenGL texture object ID associated with the
      off-screen back buffer for all of the components on the
      desktop. */
  public int getOpenGLTextureObject() {
    return oglTextureId;
  }

  /** Returns a rectangle specifying the OpenGL texture coordinates of
      the passed component in the texture object. The x and y
      coordinates of the returned rectangle specify the lower left
      corner of the component's image. */
  public Rectangle2D getOpenGLTextureCoords(Component c) {
    Rectangle rect = getBoundsOnBackBuffer(c);
    if (rect == null) {
      throw new RuntimeException("Unknown component " + c);
    }
    double offscreenWidth  = getOffscreenBackBufferWidth();
    double offscreenHeight = getOffscreenBackBufferHeight();
    return new Rectangle2D.Double(rect.x / offscreenWidth,
                                  (offscreenHeight - rect.y - rect.height) / offscreenHeight,
                                  rect.width / offscreenWidth,
                                  rect.height / offscreenHeight);
  }

  /** Updates the off-screen buffer of this desktop manager and makes
      the rendering results available to OpenGL in the form of a
      texture object. */
  public void updateOffscreenBuffer(OffscreenDesktopPane parent) {
    boolean needsCopy = needsCopyBack();
    boolean hadPrevBackBuffer = false;
    super.updateOffscreenBuffer(parent);
    Image img = getOffscreenBackBuffer();
    final boolean mustResizeOGLTexture = ((oglTextureId == 0) ||
                                          (img == null) ||
                                          (prevBackBufferWidth  != img.getWidth(null)) ||
                                          (prevBackBufferHeight != img.getHeight(null)));
    if (needsCopy) {
      final Graphics g = getOffscreenGraphics();
      // Capture off-screen buffer contents into OpenGL texture
      Java2D.invokeWithOGLContextCurrent(g, new Runnable() {
          public void run() {
            // Get valid Java2D context
            if (j2dContext == null ||
                j2dContextSurfaceIdentifier != Java2D.getOGLSurfaceIdentifier(g)) {
              j2dContext = GLDrawableFactory.getFactory().createExternalGLContext();
              j2dContext.setGL(new DebugGL(j2dContext.getGL()));
              j2dContextSurfaceIdentifier = Java2D.getOGLSurfaceIdentifier(g);
            }

            j2dContext.makeCurrent(); // No-op
            try {
              GL gl = j2dContext.getGL();

              if (oglTextureId == 0) {
                // Set up and initialize texture
                int[] tmp = new int[1];

                gl.glGenTextures(1, tmp, 0);
                oglTextureId = tmp[0];
                if (oglTextureId == 0) {
                  throw new RuntimeException("Error generating OpenGL back buffer texture");
                }
                assert mustResizeOGLTexture : "Must know we need to resize";
              }

              gl.glBindTexture(textureTarget, oglTextureId);

              int offscreenWidth  = getOffscreenBackBufferWidth();
              int offscreenHeight = getOffscreenBackBufferHeight();

              if (mustResizeOGLTexture) {
                prevBackBufferWidth  = offscreenWidth;
                prevBackBufferHeight = offscreenHeight;

                gl.glTexImage2D(textureTarget,
                                0,
                                GL.GL_RGBA8,
                                offscreenWidth,
                                offscreenHeight,
                                0,
                                GL.GL_RGBA,
                                GL.GL_UNSIGNED_BYTE,
                                null);
              }

              // Copy texture from offscreen buffer
              // NOTE: assumes read buffer is set up
              // FIXME: could be more efficient by copying only bounding rectangle

              gl.glPixelStorei(GL.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE);
              gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, GL.GL_FALSE);
              gl.glPixelStorei(GL.GL_UNPACK_LSB_FIRST, GL.GL_FALSE);
              gl.glPixelStorei(GL.GL_PACK_LSB_FIRST, GL.GL_FALSE);
              gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, 0);
              gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, 0);
              gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, 0);
              gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0);
              gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, 0);
              gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, 0);
              gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
              gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1);
              gl.glPixelTransferf(GL.GL_RED_SCALE, 1);
              gl.glPixelTransferf(GL.GL_GREEN_SCALE, 1);
              gl.glPixelTransferf(GL.GL_BLUE_SCALE, 1);
              gl.glPixelTransferf(GL.GL_ALPHA_SCALE, 1);
              gl.glPixelTransferf(GL.GL_RED_BIAS, 0);
              gl.glPixelTransferf(GL.GL_GREEN_BIAS, 0);
              gl.glPixelTransferf(GL.GL_BLUE_BIAS, 0);
              gl.glPixelTransferf(GL.GL_ALPHA_BIAS, 0);

              // long start = System.currentTimeMillis();
              gl.glCopyTexSubImage2D(textureTarget,
                                     0,
                                     0,
                                     0,
                                     0,
                                     0,
                                     offscreenWidth,
                                     offscreenHeight);
              // long end = System.currentTimeMillis();
              // System.err.println("glCopyTexSubImage2D " + offscreenWidth + "x" + offscreenHeight + " took " + (end - start) + " ms");

            } finally {
              j2dContext.release();
            }
          }
        });
    }
  }

  // Ideally we would force a repaint only of the 2D bounds of the 3D
  // component projected onto the desktop. However for expedience
  // we'll currently just repaint the entire desktop to get correct
  // results.
  protected void repaintPortionOfDesktop(JDesktopPane desktop, Component comp) {
    desktop.repaint();
  }
}