/*
 * Portions Copyright (C) 2003 Sun Microsystems, Inc.
 * All rights reserved.
 */

/*
 *
 * COPYRIGHT NVIDIA CORPORATION 2003. ALL RIGHTS RESERVED.
 * BY ACCESSING OR USING THIS SOFTWARE, YOU AGREE TO:
 *
 *  1) ACKNOWLEDGE NVIDIA'S EXCLUSIVE OWNERSHIP OF ALL RIGHTS
 *     IN AND TO THE SOFTWARE;
 *
 *  2) NOT MAKE OR DISTRIBUTE COPIES OF THE SOFTWARE WITHOUT
 *     INCLUDING THIS NOTICE AND AGREEMENT;
 *
 *  3) ACKNOWLEDGE THAT TO THE MAXIMUM EXTENT PERMITTED BY
 *     APPLICABLE LAW, THIS SOFTWARE IS PROVIDED *AS IS* AND
 *     THAT NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES,
 *     EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED
 *     TO, IMPLIED WARRANTIES OF MERCHANTABILITY  AND FITNESS
 *     FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS BE LIABLE FOR ANY
 * SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
 * OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS
 * INFORMATION, OR ANY OTHER PECUNIARY LOSS), INCLUDING ATTORNEYS'
 * FEES, RELATING TO THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 */

package demos.cg.runtime_ogl_vertex_fragment;

import com.jogamp.opengl.cg.*;
import com.jogamp.opengl.*;
import com.jogamp.opengl.awt.*;
import com.jogamp.opengl.glu.*;
import com.jogamp.opengl.util.*;
import com.jogamp.opengl.util.*;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.*;
import java.util.*;

/**
 * Basic example of the use of the Cg runtime in a simple OpenGL program.
 * Ported to Java from NVidia's original C source by Christopher Kline, 06
 * June 2003. Original NVidia copyright is preserved in the source code.
 */
public class runtime_ogl_vertex_fragment implements GLEventListener 
{
  // Global variables: hold the Cg context that we're storing our programs
  // in as well as handles to the vertex and fragment program used in this
  // demo.

  private GLU glu = new GLU();
  CGcontext context;
  CGprogram vertexProgram, fragmentProgram;

  ///////////////////////////////////////////////////////////////////////////

  // Main program; do basic GLUT and Cg setup, but leave most of the work
  // to the display() function.

  public static void main(String[] argv)
  {    
    Frame frame = new Frame("Cg demo (runtime_ogl_vertex_fragment)");
    GLCanvas canvas = new GLCanvas();
    canvas.addGLEventListener(new runtime_ogl_vertex_fragment());

    frame.add(canvas);
    frame.setSize(512, 512);
    final Animator animator = new Animator(canvas);
    frame.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          // Run this on another thread than the AWT event queue to
          // make sure the call to Animator.stop() completes before
          // exiting
          new Thread(new Runnable() {
              public void run() {
                animator.stop();
                System.exit(0);
              }
            }).start();
        }
      });
    frame.setVisible(true);
    animator.start();

    // and all the rest happens in the display function...
  }

  public void init(GLAutoDrawable drawable) 
  {
    // Use debug pipeline
    // drawable.setGL(new DebugGL(drawable.getGL()));

    GL2 gl = drawable.getGL().getGL2();
    
    // Basic Cg setup; register a callback function for any errors
    // and create an initial context
    //cgSetErrorCallback(handleCgError); // not yet exposed in Cg binding
    context = CgGL.cgCreateContext();
  
    // Do one-time setup only once; setup Cg programs and textures
    // and set up OpenGL state.
    ChooseProfiles();
    LoadCgPrograms();
    LoadTextures(gl);

    gl.glEnable(GL.GL_DEPTH_TEST);
  }

  public void dispose(GLAutoDrawable drawable) {
  }

  private void CheckCgError()
  {
    /*CGerror*/ int err = CgGL.cgGetError();

    if (err != CgGL.CG_NO_ERROR)
    {
      throw new RuntimeException("CG error: " + CgGL.cgGetErrorString(err));
    }
  }

  private static int curTime = 0;
  
  // display callback function
  public void display(GLAutoDrawable drawable) 
  {

    GL2 gl = drawable.getGL().getGL2();
    
    // The usual OpenGL stuff to clear the screen and set up viewing.
    gl.glClearColor(.25f, .25f, .25f, 1.0f);
    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

    gl.glMatrixMode(GL2ES1.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective(30.0f, 1.0f, .1f, 100);

    gl.glMatrixMode(GL2ES1.GL_MODELVIEW);
    gl.glLoadIdentity();
    glu.gluLookAt(4, 4, -4, 0, 0, 0, 0, 1, 0);

    // Make the object rotate a bit each time the display function
    // is called
    gl.glRotatef(curTime, 0, 1, 0);

    // Now make sure that the vertex and fragment programs, loaded
    // in LoadCgPrograms() are bound.
    CgGL.cgGLBindProgram(vertexProgram);
    CgGL.cgGLBindProgram(fragmentProgram);

    // Bind uniform parameters to vertex shader
    CgGL.cgGLSetStateMatrixParameter(CgGL.cgGetNamedParameter(vertexProgram, "ModelViewProj"),
                                     CgGL.CG_GL_MODELVIEW_PROJECTION_MATRIX,
                                     CgGL.CG_GL_MATRIX_IDENTITY);
    CgGL.cgGLSetStateMatrixParameter(CgGL.cgGetNamedParameter(vertexProgram, "ModelView"),
                                     CgGL.CG_GL_MODELVIEW_MATRIX,
                                     CgGL.CG_GL_MATRIX_IDENTITY);
    CgGL.cgGLSetStateMatrixParameter(CgGL.cgGetNamedParameter(vertexProgram, "ModelViewIT"),
                                     CgGL.CG_GL_MODELVIEW_MATRIX,
                                     CgGL.CG_GL_MATRIX_INVERSE_TRANSPOSE);

    // We can also go ahead and bind varying parameters to vertex shader
    // that we just want to have the same value for all vertices.  The
    // vertex shader could be modified so that these were uniform for
    // better efficiency, but this gives us flexibility for the future.
    float Kd[] = { .7f, .2f, .2f }, Ks[] = { .9f, .9f, .9f };
    CgGL.cgGLSetParameter3fv(CgGL.cgGetNamedParameter(vertexProgram, "diffuse"), Kd, 0);
    CgGL.cgGLSetParameter3fv(CgGL.cgGetNamedParameter(vertexProgram, "specular"), Ks, 0);

    // Now bind uniform parameters to fragment shader
    float lightPos[] = { 3, 2, -3 };
    CgGL.cgGLSetParameter3fv(CgGL.cgGetNamedParameter(fragmentProgram, "Plight"), lightPos, 0);
    float lightColor[] = { 1, 1, 1 };
    CgGL.cgGLSetParameter3fv(CgGL.cgGetNamedParameter(fragmentProgram, "lightColor"), 
                             lightColor, 0);
    CgGL.cgGLSetParameter1f(CgGL.cgGetNamedParameter(fragmentProgram, "shininess"), 40);

    // And finally, enable the approprate texture for fragment shader; the
    // texture was originally set up in LoadTextures().
    CgGL.cgGLEnableTextureParameter(CgGL.cgGetNamedParameter(fragmentProgram,
                                                             "diffuseMap"));
    // And go ahead and draw the scene geometry
    DrawGeometry(gl);

    // Disable the texture now that we're done with it.
    CgGL.cgGLDisableTextureParameter(CgGL.cgGetNamedParameter(fragmentProgram,
                                                              "diffuseMap"));

    ++curTime;
  }


  // Choose the vertex and fragment profiles to use.  Try to use
  // CG_PROFILE_ARBVFP1 and CG_PROFILE_ARBFP1, depending on hardware support.
  // If those aren't available, fall back to CG_PROFILE_VP30 and
  // CG_PROFILE_FP30, respectively.

  int /*CGprofile*/ vertexProfile, fragmentProfile;

  void ChooseProfiles()
  {
    // Make sure that the appropriate profiles are available on the
    // user's system.
    if (CgGL.cgGLIsProfileSupported(CgGL.CG_PROFILE_ARBVP1))
      vertexProfile = CgGL.CG_PROFILE_ARBVP1;
    else {
      // try VP30
      if (CgGL.cgGLIsProfileSupported(CgGL.CG_PROFILE_VP30))
        vertexProfile = CgGL.CG_PROFILE_VP30;
      else {
        System.out.println("Neither arbvp1 or vp30 vertex profiles supported on this system.\n");
        System.exit(1);
      }
    }

    if (CgGL.cgGLIsProfileSupported(CgGL.CG_PROFILE_ARBFP1))
      fragmentProfile = CgGL.CG_PROFILE_ARBFP1;
    else {
      // try FP30
      if (CgGL.cgGLIsProfileSupported(CgGL.CG_PROFILE_FP30))
        fragmentProfile = CgGL.CG_PROFILE_FP30;
      else {
        System.out.println("Neither arbfp1 or fp30 fragment profiles supported on this system.\n");
        System.exit(1);
      }
    }
  }


  void LoadCgPrograms()
  {
    assert(CgGL.cgIsContext(context));

    // Load and compile the vertex program from demo_vert.cg; hold on to the
    // handle to it that is returned.
    try {
      vertexProgram = CgGL.cgCreateProgramFromStream(context, CgGL.CG_SOURCE,
                                                     getClass().getClassLoader().getResourceAsStream("demos/cg/runtime_ogl_vertex_fragment/demo_vert.cg"),
                                                     vertexProfile, null, null);
    } catch (IOException e) {
      throw new RuntimeException("Error loading Cg vertex program", e);
    }
    if (!CgGL.cgIsProgramCompiled(vertexProgram))
      CgGL.cgCompileProgram(vertexProgram);

    // Enable the appropriate vertex profile and load the vertex program.
    CgGL.cgGLEnableProfile(vertexProfile);
    CgGL.cgGLLoadProgram(vertexProgram);

    // And similarly set things up for the fragment program.
    try {
      fragmentProgram = CgGL.cgCreateProgramFromStream(context, CgGL.CG_SOURCE,
                                                       getClass().getClassLoader().getResourceAsStream("demos/cg/runtime_ogl_vertex_fragment/demo_frag.cg"),
                                                       fragmentProfile, null, null);
    } catch (IOException e) {
      throw new RuntimeException("Error loading Cg fragment program", e);
    }
    if (!CgGL.cgIsProgramCompiled(fragmentProgram)) {
      CgGL.cgCompileProgram(fragmentProgram);
    }
    
    CgGL.cgGLEnableProfile(fragmentProfile);
    CgGL.cgGLLoadProgram(fragmentProgram);
  }

  void LoadTextures(GL2 gl)
  {
    // There is only one texture needed here--we'll set up a basic
    // checkerboard--which is used to modulate the diffuse channel in the
    // fragment shader.
    int[] handle = new int[1];
    gl.glGenTextures(1, handle, 0);

    // Basic OpenGL texture state setup
    gl.glBindTexture(GL.GL_TEXTURE_2D, handle[0]);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, gl.GL_GENERATE_MIPMAP, GL.GL_TRUE);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);

    // Fill in the texture map.
    final int RES = 512;
    float[] data = new float[RES*RES*4];
    int dp = 0;
    for (int i = 0; i < RES; ++i) {
      for (int j = 0; j < RES; ++j) {
        if ((i/32+j/32) % 2 != 0) {
          data[dp++] = .7f;
          data[dp++] = .7f;
          data[dp++] = .7f;
        }
        else {
          data[dp++] = .1f;
          data[dp++] = .1f;
          data[dp++] = .1f;
        }
        data[dp++] = 1.0f;
      }
    }

    gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, RES, RES, 0, GL.GL_RGBA, GL.GL_FLOAT, FloatBuffer.wrap(data));

    // Tell Cg which texture handle should be associated with the sampler2D
    // parameter to the fragment shader.
    CgGL.cgGLSetTextureParameter(CgGL.cgGetNamedParameter(fragmentProgram, "diffuseMap"),
                                 handle[0]);
  }
  
  private int VERTEX(int u, int v, int nu) { return (u + v * nu); }

  // Geometry creation and drawing function; we'll just draw a sphere.

  private static FloatBuffer P, N, uv;
  private static IntBuffer indices;
  void DrawGeometry(GL2 gl)
  {
    // Cache the sphere positions, normals, texture coordinates, and 
    // vertex indices in a local array; we only need to fill them in the
    // first time through this function.
    int nu  = 30, nv = 30;
    int nTris = 2*(nu-1)*(nv-1), nVerts = nu*nv;
    if (P == null) {
      int u, v;

      P = GLBuffers.newDirectFloatBuffer(3*nVerts);
      N = GLBuffers.newDirectFloatBuffer(3*nVerts);
      uv = GLBuffers.newDirectFloatBuffer(2*nVerts);

      // Fill in the position, normal, and texture coordinate arrays.
      // Just loop over all of the vertices, compute their parametreic
      // (u,v) coordinates (which we use for texture coordinates as
      // well), and call the ParametricEval() function, which turns (u,v)
      // coordinates into positions and normals on the surface of the
      // object.
      int pp = 0, np = 0, uvp = 0;
      for (v = 0; v < nv; ++v) {
        float fv = (float)v / (float)(nv-1);
        for (u = 0; u < nu; ++u) {
          float fu = (float)u / (float)(nu-1);
          uv.put(uvp, fu);
          uv.put(uvp+1, fv);
          ParametricEval(fu, fv, pp, P, np, N);
          pp += 3;
          np += 3;
          uvp += 2;
        }
      }

      // Now fill in the vertex index arrays
      indices = GLBuffers.newDirectIntBuffer(3*nTris);
      int ip = 0;
      for (v = 0; v < nv-1; ++v) {
        for (u = 0; u < nu-1; ++u) {
          indices.put(ip++, VERTEX(u, v, nu));
          indices.put(ip++, VERTEX(u+1, v, nu));
          indices.put(ip++, VERTEX(u+1, v+1, nu));

          indices.put(ip++, VERTEX(u, v, nu));
          indices.put(ip++, VERTEX(u+1, v+1, nu));
          indices.put(ip++, VERTEX(u, v+1, nu));
        }
      }
      // Tell Cg which of these data pointers are associated with which
      // parameters to the vertex shader, so that when we call
      // cgGLEnableClientState() and then glDrawElements(), the shader
      // gets the right input information.
      CGparameter param = CgGL.cgGetNamedParameter(vertexProgram, "Pobject");
      CgGL.cgGLSetParameterPointer(param, 3, GL.GL_FLOAT, 0, P);
      param = CgGL.cgGetNamedParameter(vertexProgram, "Nobject");
      CgGL.cgGLSetParameterPointer(param, 3, GL.GL_FLOAT, 0, N);
      param = CgGL.cgGetNamedParameter(vertexProgram, "TexUV");
      CgGL.cgGLSetParameterPointer(param, 2, GL.GL_FLOAT, 0, uv);
    }

    // And now, each time through, enable the bindings to the parameters
    // that we set up the first time through
    CGparameter param = CgGL.cgGetNamedParameter(vertexProgram, "Pobject");
    CgGL.cgGLEnableClientState(param);
    param = CgGL.cgGetNamedParameter(vertexProgram, "Nobject");
    CgGL.cgGLEnableClientState(param);
    param = CgGL.cgGetNamedParameter(vertexProgram, "TexUV");
    CgGL.cgGLEnableClientState(param);

    // Enable the texture parameter as well.
    param = CgGL.cgGetNamedParameter(fragmentProgram, "diffuseMap");
    CgGL.cgGLEnableTextureParameter(param);

    // And now, draw the geometry.
    gl.glDrawElements(GL.GL_TRIANGLES, 3*nTris, gl.GL_UNSIGNED_INT, indices);

    // Be a good citizen and disable the various bindings we set up above.
    param = CgGL.cgGetNamedParameter(vertexProgram, "Pobject");
    CgGL.cgGLDisableClientState(param);
    param = CgGL.cgGetNamedParameter(vertexProgram, "Nobject");
    CgGL.cgGLDisableClientState(param);
    param = CgGL.cgGetNamedParameter(vertexProgram, "TexUV");
    CgGL.cgGLDisableClientState(param);

    param = CgGL.cgGetNamedParameter(fragmentProgram, "diffuseMap");
    CgGL.cgGLDisableTextureParameter(param);
  }

  void ParametricEval(float u, float v, int offsetP, FloatBuffer p, int offsetN, FloatBuffer N)
  {
    float theta = (float)Math.PI * u, phi = (float)(2.0 * Math.PI * v);
    P.put(offsetP + 0, (float)(Math.sin(theta) * Math.sin(phi)));
    P.put(offsetP + 1, (float)(Math.sin(theta) * Math.cos(phi)));
    P.put(offsetP + 2, (float)(Math.cos(theta)));

    N.put(offsetN + 0, P.get(offsetP + 0));
    N.put(offsetN + 1, P.get(offsetP + 1));
    N.put(offsetN + 2, P.get(offsetP + 2));
  }

  public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged)
  {
    // nothing
  }

  public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
  {
    // do nothing
  }

}