private static boolean useJavaMipmapCode = true;

static {
  AccessController.doPrivileged(new PrivilegedAction() {
      public Object run() {
        String val = System.getProperty("jogl.glu.nojava");
        if (val != null && !val.toLowerCase().equals("false")) {
          useJavaMipmapCode = false;
        }
        /** Not required nor forced
        if( !initializeImpl() ) {
            throw new RuntimeException("Initialization failure");
        } */
        return null;
      }
    });
}

/**
 * Instantiates a new OpenGL Utility Library object. A GLU object may
 * be instantiated at any point in the application and is not
 * inherently tied to any particular OpenGL context; however, the GLU
 * object may only be used when an OpenGL context is current on the
 * current thread. Attempts to call most of the methods in the GLU
 * library when no OpenGL context is current will cause an exception
 * to be thrown.
 *
 * <P>
 *
 * The returned GLU object is not guaranteed to be thread-safe and
 * should only be used from one thread at a time. Multiple GLU objects
 * may be instantiated to be used from different threads
 * simultaneously.
 */

public GLUgl2()
{
  project = new ProjectDouble();
}

//----------------------------------------------------------------------
// Utility routines
//

public static final GL2 getCurrentGL2() throws GLException {
  GLContext curContext = GLContext.getCurrent();
  if (curContext == null) {
    throw new GLException("No OpenGL context current on this thread");
  }
  return curContext.getGL().getGL2();
}

/**
 * Returns true if the specified GLU core- or extension-function can be
 * successfully used through this GLU instance. By "successfully" we mean
 * that the function is both <i>callable</i> on the machine running the
 * program and <i>available</i> on the current display.<P>
 *
 * A GLU function is <i>callable</i> if it is a GLU core- or extension-function
 * that is supported by the underlying GLU implementation. The function is
 * <i>available</i> if the OpenGL implementation on the display meets the
 * requirements of the GLU function being called (because GLU functions utilize
 * OpenGL functions). <P>
 *
 * Whether or not a GLU function is <i>callable</i> is determined as follows:
 * <ul>
 *   <li>If the function is a GLU core function (i.e., not an
 *   extension), <code>gluGetString(GLU_VERSION)</code> is used to determine the
 *   version number of the underlying GLU implementation on the host.
 *   then the function name is cross-referenced with that specification to
 *   see if it is part of that version's specification.
 *
 *   <li> If the function is a GLU extension, the function name is
 *   cross-referenced with the list returned by
 *   <code>gluGetString(GLU_EXTENSIONS)</code> to see if the function is one of
 *   the extensions that is supported by the underlying GLU implementation.
 * </ul>
 *
 * Whether or not a GLU function is <i>available</i> is determined as follows:
 * <ul>
 *   <li>If the function is a GLU core function then the function is first
 *   cross-referenced with the GLU specifications to find the minimum GLU
 *   version required to <i>call</i> that GLU function. Then the following table
 *   is consulted to determine the minimum GL version required for that version
 *   of GLU:
 *   <ul>
 *   <li> GLU 1.0 requires OpenGL 1.0
 *   <li> GLU 1.1 requires OpenGL 1.0
 *   <li> GLU 1.2 requires OpenGL 1.1
 *   <li> GLU 1.3 requires OpenGL 1.2
 *   </ul>
 *   Finally, <code>glGetString(GL_VERSION)</code> is used to determine the
 *   highest OpenGL version that both host and display support, and from that it
 *   is possible to determine if the GL facilities required by the GLU function
 *   are <i>available</i> on the display.
 *
 *   <li> If the function is a GLU extension, the function name is
 *   cross-referenced with the list returned by
 *   <code>gluGetString(GLU_EXTENSIONS)</code> to see if the function is one of
 *   the extensions that is supported by the underlying GLU implementation.
 * </ul>
 *
 * <b>NOTE:</b>The availability of a function may change at runtime in
 * response to changes in the display environment. For example, when a window
 * is dragged from one display to another on a multi-display system, or when
 * the properties of the display device are modified (e.g., changing the color
 * depth of the display). Any application that is concerned with handling
 * these situations correctly should confirm availability after a display
 * change before calling a questionable OpenGL function. To detect a change in
 * the display device, please see {@link
 * GLEventListener#displayChanged(GLAutoDrawable,boolean,boolean)}.
 *
 * @param gluFunctionName the name of the OpenGL function (e.g., use
 * "gluNurbsCallbackDataEXT" to check if the <code>
 * gluNurbsCallbackDataEXT(GLUnurbs, GLvoid)</code> extension is available).
 */
public final boolean isFunctionAvailable(String gluFunctionName)
{
  if (useJavaMipmapCode) {
    // All GLU functions are available in Java port
    return true;
  }
  return (gluProcAddressTable.getAddressFor(gluFunctionName) != 0);
}

//----------------------------------------------------------------------
// Projection routines
//

private final ProjectDouble project;

public final void gluOrtho2D(float left, float right, float bottom, float top) {
  project.gluOrtho2D(getCurrentGL2(), (double)left, (double)right, (double)bottom, (double)top);

}

public final void gluOrtho2D(double left, double right, double bottom, double top) {
  project.gluOrtho2D(getCurrentGL2(), left, right, bottom, top);
}

public final void gluPerspective(float fovy, float aspect, float zNear, float zFar) {
  project.gluPerspective(getCurrentGL2(), (double)fovy, (double)aspect, (double)zNear, (double)zFar);
}

public final void gluPerspective(double fovy, double aspect, double zNear, double zFar) {
  project.gluPerspective(getCurrentGL2(), fovy, aspect, zNear, zFar);
}

public final void gluLookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) {
  project.gluLookAt(getCurrentGL2(), (double)eyeX, (double)eyeY, (double)eyeZ, (double)centerX, (double)centerY, (double)centerZ, (double)upX, (double)upY, (double)upZ);
}

public final void gluLookAt(double eyeX, double eyeY, double eyeZ, double centerX, double centerY, double centerZ, double upX, double upY, double upZ) {
  project.gluLookAt(getCurrentGL2(), eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
}

/** Interface to C language function: <br> <code> GLint gluProject(GLdouble objX, GLdouble objY, GLdouble objZ, const GLdouble *  model, const GLdouble *  proj, const GLint *  view, GLdouble *  winX, GLdouble *  winY, GLdouble *  winZ); </code>
 * <P> Accepts the outgoing window coordinates as a single array.
 */
public final boolean gluProject(double objX, double objY, double objZ, double[] model, int model_offset, double[] proj, int proj_offset, int[] view, int view_offset, double[] winPos, int winPos_offset) {
  return project.gluProject(objX, objY, objZ, model, model_offset, proj, proj_offset, view, view_offset, winPos, winPos_offset);
}

/** Interface to C language function: <br> <code> GLint gluProject(GLdouble objX, GLdouble objY, GLdouble objZ, const GLdouble *  model, const GLdouble *  proj, const GLint *  view, GLdouble *  winX, GLdouble *  winY, GLdouble *  winZ); </code>
 * <P> Accepts the outgoing window coordinates as a single buffer.
 */
public final boolean gluProject(double objX, double objY, double objZ, DoubleBuffer model, DoubleBuffer proj, IntBuffer view, DoubleBuffer winPos) {
  return project.gluProject(objX, objY, objZ, model, proj, view, winPos);
}

/** Interface to C language function: <br> <code> GLint gluUnProject(GLdouble winX, GLdouble winY, GLdouble winZ, const GLdouble *  model, const GLdouble *  proj, const GLint *  view, GLdouble *  objX, GLdouble *  objY, GLdouble *  objZ); </code>
 * <P> Accepts the outgoing object coordinates (a 3-vector) as a single array.
 */
public final boolean gluUnProject(double winX, double winY, double winZ, double[] model, int model_offset, double[] proj, int proj_offset, int[] view, int view_offset, double[] objPos, int objPos_offset) {
  return project.gluUnProject(winX, winY, winZ, model, model_offset, proj, proj_offset, view, view_offset, objPos, objPos_offset);
}

/** Interface to C language function: <br> <code> GLint gluUnProject(GLdouble winX, GLdouble winY, GLdouble winZ, const GLdouble *  model, const GLdouble *  proj, const GLint *  view, GLdouble *  objX, GLdouble *  objY, GLdouble *  objZ); </code>
 * <P> Accepts the outgoing object coordinates (a 3-vector) as a single buffer.
 */
public final boolean gluUnProject(double winX, double winY, double winZ, DoubleBuffer model, DoubleBuffer proj, IntBuffer view, DoubleBuffer objPos) {
  return project.gluUnProject(winX, winY, winZ, model, proj, view, objPos);
}

/** Interface to C language function: <br> <code> GLint gluUnProject4(GLdouble winX, GLdouble winY, GLdouble winZ, GLdouble clipW, const GLdouble *  model, const GLdouble *  proj, const GLint *  view, GLdouble nearVal, GLdouble farVal, GLdouble *  objX, GLdouble *  objY, GLdouble *  objZ, GLdouble *  objW); </code>
 * <P> Accepts the outgoing object coordinates (a 4-vector) as a single array.
 */
public final boolean gluUnProject4(double winX, double winY, double winZ, double clipW, double[] model, int model_offset, double[] proj, int proj_offset, int[] view, int view_offset, double nearVal, double farVal, double[] objPos, int objPos_offset) {
  return project.gluUnProject4(winX, winY, winZ, clipW, model, model_offset, proj, proj_offset, view, view_offset, nearVal, farVal, objPos, objPos_offset);
}

/** Interface to C language function: <br> <code> GLint gluUnProject4(GLdouble winX, GLdouble winY, GLdouble winZ, GLdouble clipW, const GLdouble *  model, const GLdouble *  proj, const GLint *  view, GLdouble nearVal, GLdouble farVal, GLdouble *  objX, GLdouble *  objY, GLdouble *  objZ, GLdouble *  objW); </code>
 * <P> Accepts the outgoing object coordinates (a 4-vector) as a single buffer.
 */
public final boolean gluUnProject4(double winX, double winY, double winZ, double clipW, DoubleBuffer model, DoubleBuffer proj, IntBuffer view, double nearVal, double farVal, DoubleBuffer objPos) {
  return project.gluUnProject4(winX, winY, winZ, clipW, model, proj, view, nearVal, farVal, objPos);
}

public final void gluPickMatrix(double x, double y, double delX, double delY, int[] viewport, int viewport_offset) {
  project.gluPickMatrix(getCurrentGL2(), x, y, delX, delY, viewport, viewport_offset);
}

public final void gluPickMatrix(double x, double y, double delX, double delY, IntBuffer viewport) {
  project.gluPickMatrix(getCurrentGL2(), x, y, delX, delY, viewport);
}

//----------------------------------------------------------------------
// Mipmap and image scaling functionality


private final ByteBuffer copyToByteBuffer(Buffer buf) {
  if (buf instanceof ByteBuffer) {
    if (buf.position() == 0) {
      return (ByteBuffer) buf;
    }
    return Buffers.copyByteBuffer((ByteBuffer) buf);
  } else if (buf instanceof ShortBuffer) {
    return Buffers.copyShortBufferAsByteBuffer((ShortBuffer) buf);
  } else if (buf instanceof IntBuffer) {
    return Buffers.copyIntBufferAsByteBuffer((IntBuffer) buf);
  } else if (buf instanceof FloatBuffer) {
    return Buffers.copyFloatBufferAsByteBuffer((FloatBuffer) buf);
  } else {
    throw new IllegalArgumentException("Unsupported buffer type (must be one of byte, short, int, or float)");
  }
}

private final int gluScaleImageJava( int format, int widthin, int heightin,
                               int typein, Buffer datain, int widthout, int heightout,
                               int typeout, Buffer dataout ) {
  ByteBuffer in = null;
  ByteBuffer out = null;
  in = copyToByteBuffer(datain);
  if( dataout instanceof ByteBuffer ) {
    out = (ByteBuffer)dataout;
  } else if( dataout instanceof ShortBuffer ) {
    out = Buffers.newDirectByteBuffer(dataout.remaining() * Buffers.SIZEOF_SHORT);
  } else if ( dataout instanceof IntBuffer ) {
    out = Buffers.newDirectByteBuffer(dataout.remaining() * Buffers.SIZEOF_INT);
  } else if ( dataout instanceof FloatBuffer ) {
    out = Buffers.newDirectByteBuffer(dataout.remaining() * Buffers.SIZEOF_FLOAT);
  } else {
    throw new IllegalArgumentException("Unsupported destination buffer type (must be byte, short, int, or float)");
  }
  int errno = Mipmap.gluScaleImage( getCurrentGL2(), format, widthin, heightin, typein, in, 
            widthout, heightout, typeout, out );
  if( errno == 0 ) {
    out.rewind();
    if (out != dataout) {
      if( dataout instanceof ShortBuffer ) {
        ((ShortBuffer) dataout).put(out.asShortBuffer());
      } else if( dataout instanceof IntBuffer ) {
        ((IntBuffer) dataout).put(out.asIntBuffer());
      } else if( dataout instanceof FloatBuffer ) {
        ((FloatBuffer) dataout).put(out.asFloatBuffer());
      } else {
        throw new RuntimeException("Should not reach here");
      }
    }
  }
  return( errno );
}


private final int gluBuild1DMipmapLevelsJava( int target, int internalFormat, int width,
                                        int format, int type, int userLevel, int baseLevel, int maxLevel,
                                        Buffer data ) {
  ByteBuffer buffer = copyToByteBuffer(data);
  return( Mipmap.gluBuild1DMipmapLevels( getCurrentGL2(), target, internalFormat, width,
          format, type, userLevel, baseLevel, maxLevel, buffer ) );
}


private final int gluBuild1DMipmapsJava( int target, int internalFormat, int width,
                                   int format, int type, Buffer data ) {
  ByteBuffer buffer = copyToByteBuffer(data);
  return( Mipmap.gluBuild1DMipmaps( getCurrentGL2(), target, internalFormat, width, format,
          type, buffer ) );
}


private final int gluBuild2DMipmapLevelsJava( int target, int internalFormat, int width,
                                        int height, int format, int type, int userLevel, int baseLevel,
                                        int maxLevel, Buffer data ) {
  // While the code below handles other data types, it doesn't handle non-ByteBuffers
  data = copyToByteBuffer(data);
  return( Mipmap.gluBuild2DMipmapLevels( getCurrentGL2(), target, internalFormat, width,
          height, format, type, userLevel, baseLevel, maxLevel, data ) );
}

private final int gluBuild2DMipmapsJava( int target, int internalFormat, int width,
                                   int height, int format, int type, Buffer data ) {
  // While the code below handles other data types, it doesn't handle non-ByteBuffers
  data = copyToByteBuffer(data);
  return( Mipmap.gluBuild2DMipmaps( getCurrentGL2(), target, internalFormat, width, height,
          format, type, data) );
}

private final int gluBuild3DMipmapLevelsJava( int target, int internalFormat, int width,
                                        int height, int depth, int format, int type, int userLevel, int baseLevel,
                                        int maxLevel, Buffer data) {
  ByteBuffer buffer = copyToByteBuffer(data);
  return( Mipmap.gluBuild3DMipmapLevels( getCurrentGL2(), target, internalFormat, width,
          height, depth, format, type, userLevel, baseLevel, maxLevel, buffer) );
}

private final int gluBuild3DMipmapsJava( int target, int internalFormat, int width,
                                   int height, int depth, int format, int type, Buffer data ) {
  ByteBuffer buffer = copyToByteBuffer(data);
  return( Mipmap.gluBuild3DMipmaps( getCurrentGL2(), target, internalFormat, width, height,
          depth, format, type, buffer ) );
}


//----------------------------------------------------------------------
// Wrappers for mipmap and image scaling entry points which dispatch either
// to the Java or C versions.
//

/** Interface to C language function: <br> <code> GLint gluBuild1DMipmapLevels(GLenum target, GLint internalFormat, GLsizei width, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *  data); </code>    */
public final int gluBuild1DMipmapLevels(int target, int internalFormat, int width, int format, int type, int level, int base, int max, java.nio.Buffer data) {
  if (useJavaMipmapCode) {
    return gluBuild1DMipmapLevelsJava(target, internalFormat, width, format, type, level, base, max, data);
  } else {
    return gluBuild1DMipmapLevelsC(target, internalFormat, width, format, type, level, base, max, data);
  }
}

/** Interface to C language function: <br> <code> GLint gluBuild1DMipmaps(GLenum target, GLint internalFormat, GLsizei width, GLenum format, GLenum type, const void *  data); </code>    */
public final int gluBuild1DMipmaps(int target, int internalFormat, int width, int format, int type, java.nio.Buffer data) {
  if (useJavaMipmapCode) {
    return gluBuild1DMipmapsJava(target, internalFormat, width, format, type, data);
  } else {
    return gluBuild1DMipmapsC(target, internalFormat, width, format, type, data);
  }
}

/** Interface to C language function: <br> <code> GLint gluBuild2DMipmapLevels(GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *  data); </code>    */
public final int gluBuild2DMipmapLevels(int target, int internalFormat, int width, int height, int format, int type, int level, int base, int max, java.nio.Buffer data) {
  if (useJavaMipmapCode) {
    return gluBuild2DMipmapLevelsJava(target, internalFormat, width, height, format, type, level, base, max, data);
  } else {
    return gluBuild2DMipmapLevelsC(target, internalFormat, width, height, format, type, level, base, max, data);
  }
}


/** Interface to C language function: <br> <code> GLint gluBuild2DMipmaps(GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *  data); </code>    */
public final int gluBuild2DMipmaps(int target, int internalFormat, int width, int height, int format, int type, java.nio.Buffer data) {
  if (useJavaMipmapCode) {
    return gluBuild2DMipmapsJava(target, internalFormat, width, height, format, type, data);
  } else {
    return gluBuild2DMipmapsC(target, internalFormat, width, height, format, type, data);
  }
}


/** Interface to C language function: <br> <code> GLint gluBuild3DMipmapLevels(GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *  data); </code>    */
public final int gluBuild3DMipmapLevels(int target, int internalFormat, int width, int height, int depth, int format, int type, int level, int base, int max, java.nio.Buffer data) {
  if (useJavaMipmapCode) {
    return gluBuild3DMipmapLevelsJava(target, internalFormat, width, height, depth, format, type, level, base, max, data);
  } else {
    return gluBuild3DMipmapLevelsC(target, internalFormat, width, height, depth, format, type, level, base, max, data);
  }
}

/** Interface to C language function: <br> <code> GLint gluBuild3DMipmaps(GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *  data); </code>    */
public final int gluBuild3DMipmaps(int target, int internalFormat, int width, int height, int depth, int format, int type, java.nio.Buffer data) {
  if (useJavaMipmapCode) {
    return gluBuild3DMipmapsJava(target, internalFormat, width, height, depth, format, type, data);
  } else {
    return gluBuild3DMipmapsC(target, internalFormat, width, height, depth, format, type, data);
  }
}


/** Interface to C language function: <br> <code> GLint gluScaleImage(GLenum format, GLsizei wIn, GLsizei hIn, GLenum typeIn, const void *  dataIn, GLsizei wOut, GLsizei hOut, GLenum typeOut, GLvoid *  dataOut); </code>    */
public final int gluScaleImage(int format, int wIn, int hIn, int typeIn, java.nio.Buffer dataIn, int wOut, int hOut, int typeOut, java.nio.Buffer dataOut) {
  if (useJavaMipmapCode) {
    return gluScaleImageJava(format, wIn, hIn, typeIn, dataIn, wOut, hOut, typeOut, dataOut);
  } else {
    return gluScaleImageC(format, wIn, hIn, typeIn, dataIn, wOut, hOut, typeOut, dataOut);
  }
}

//----------------------------------------------------------------------
// NURBS functionality
//

/**
 * Sets a property on a NURBS object. (NOTE: this function is not currently implemented.)
 * 
 * @param r
 *            GLUnurbs object holding NURBS to which a property should be
 *            set
 * @param property
 *            property id
 * @param value
 *            property value
 */
public final void gluNurbsProperty(GLUnurbs r, int property, float value) {
  // TODO glunurbsproperty
  float nurbsValue;
  switch (property) {
  default:
    //				System.out.println("TODO gluwnurbs.glunurbsproperty");
    break;
  }
}

/**
 * Creates a new GLUnurbs object.
 * 
 * @return GLUnurbs object
 */
public final GLUnurbs gluNewNurbsRenderer() {
  // DONE
  return new GLUgl2nurbsImpl();
}

/**
 * Begins a curve definition.
 * 
 * @param r
 *            GLUnurbs object to specify curve to
 */
public final void gluBeginCurve(GLUnurbs r) {
  // DONE
  ((GLUgl2nurbsImpl) r).bgncurve();
}

/**
 * Begins a surface definition.
 * 
 * @param r
 *            GLUnurbs object to specify surface to
 */
public final void gluBeginSurface(GLUnurbs r) {
  // DONE
  ((GLUgl2nurbsImpl) r).bgnsurface();
}

/**
 * Ends a surface.
 * 
 * @param r
 *            GLUnurbs object holding surface
 */
public final void gluEndSurface(GLUnurbs r) {
  // DONE
  ((GLUgl2nurbsImpl) r).endsurface();
}

/**
 * Makes a NURBS surface.
 * 
 * @param r
 *            GLUnurbs object holding the surface
 * @param sknot_count
 *            number of knots in s direction
 * @param sknot
 *            knots in s direction
 * @param tknot_count
 *            number of knots in t direction
 * @param tknot
 *            knots in t direction
 * @param s_stride
 *            number of control points coordinates in s direction
 * @param t_stride
 *            number of control points coordinates in t direction
 * @param ctlarray
 *            control points
 * @param sorder
 *            order of surface in s direction
 * @param torder
 *            order of surface in t direction
 * @param type
 *            surface type
 */
public final void gluNurbsSurface(GLUnurbs r, int sknot_count, float[] sknot,
                            int tknot_count, float[] tknot, int s_stride, int t_stride,
                            float[] ctlarray, int sorder, int torder, int type) {
  // DONE
  ((GLUgl2nurbsImpl) r).nurbssurface(sknot_count, sknot, tknot_count, tknot, s_stride,
                                  t_stride, ctlarray, sorder, torder, type);
}

/**
 * Make a NURBS curve.
 * 
 * @param r
 *            GLUnurbs object holding the curve
 * @param nknots
 *            number of knots
 * @param knot
 *            knot vector
 * @param stride
 *            number of control point coordinates
 * @param ctlarray
 *            control points
 * @param order
 *            order of the curve
 * @param type
 *            curve type
 */
public final void gluNurbsCurve(GLUnurbs r, int nknots, float[] knot, int stride,
                          float[] ctlarray, int order, int type) {
  int realType;
  switch (type) {
    // TODO GLU_MAP1_TRIM_2 etc.
  default:
    realType = type;
    break;
  }
  ((GLUgl2nurbsImpl) r).nurbscurve(nknots, knot, stride, ctlarray, order, realType);
}

/**
 * Ends a curve definition.
 * 
 * @param r
 *            GLUnurbs object holding the curve
 */
public final void gluEndCurve(GLUnurbs r) {
  //DONE
  ((GLUgl2nurbsImpl) r).endcurve();
}

//----------------------------------------------------------------------
// GLUProcAddressTable handling
//

private static GLUgl2ProcAddressTable gluProcAddressTable;

private static final GLUgl2ProcAddressTable getGLUProcAddressTable() {
  if (gluProcAddressTable == null) {
    GLContext curContext = GLContext.getCurrent();
    if (curContext == null) {
        throw new GLException("No OpenGL context current on this thread");
    }
    GLDynamicLookupHelper glLookupHelper = ((GLDrawableImpl) curContext.getGLDrawable()).getGLDynamicLookupHelper();
    glLookupHelper.loadGLULibrary();
    GLUgl2ProcAddressTable tmp = new GLUgl2ProcAddressTable(new GLProcAddressResolver());
    tmp.reset(glLookupHelper);
    gluProcAddressTable = tmp;
  }
  return gluProcAddressTable;
}
