// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Locale;
import java.util.Scanner;

import javax.imageio.ImageIO;
import com.jogamp.opengl.*;
import com.jogamp.opengl.awt.GLCanvas;
import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.GL.*;  
import com.jogamp.opengl.GL2.*; 
import com.jogamp.opengl.glu.GLU;

import javax.swing.JFrame;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.FPSAnimator;

class Renderer {

  private GLU glu = new GLU();

  public float t = 0.0f;
  public int mode = 0;

  private int[] vertBufID = new int[1];
  private int texID = 0;
  private int vertNo = 0;

  public void init(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL graphics context
    gl.glEnable(GL2.GL_DEPTH_TEST);

    // assuming the following structure of input vertex data
    // struct Vertex {
    //   float position[3];
    //   float color[4];
    //   float texCoord[2];
    //   float normal[3];
    // };
	
	int perVertexFloats = (3+4+2+3);
    float vertexData[] = loadVertexData("pushbike.vbo", perVertexFloats);

    vertNo = vertexData.length / perVertexFloats;
    FloatBuffer dataIn = Buffers.newDirectFloatBuffer(vertexData.length);
    dataIn.put(vertexData);
    dataIn.flip();

    // generating vertex VBO
    gl.glGenBuffers(1, vertBufID, 0);
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertBufID[0]);
    gl.glBufferData(GL2.GL_ARRAY_BUFFER, dataIn.capacity()*Buffers.SIZEOF_FLOAT, dataIn, GL2.GL_STATIC_DRAW);
    
    texID = loadTexture(d, "checkerboard.png");
  }


  
  public void resize(GLAutoDrawable d, int w, int h) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context
    gl.glViewport(0, 0, w, h);
    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective (30.0, (float)w/(float)h, 1.0, 10.0);
  }

  public void display(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    
    gl.glClearColor(0.3f, 0.3f, 0.3f, 0.0f);
    gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);

    gl.glMatrixMode(GL2.GL_MODELVIEW);
    gl.glLoadIdentity();
	
    // camera orbits in the z=1.5 plane
    // and looks at origin
    double rad = Math.PI / 180.0f * t;
    glu.gluLookAt(1.5*Math.cos(rad), 1.5*Math.sin(rad), 1.5f, // eye
                  0.0, 0.0, 0.0, // look at
                  0.0, 0.0, 1.0); // up

    // activating VBO
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertBufID[0]);
    int stride = (3+4+2+3)*Buffers.SIZEOF_FLOAT;
    int offset = 0;

    // position
    gl.glVertexPointer(3, GL2.GL_FLOAT, stride, offset);
    gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);

    // color
    offset = 0 + 3*Buffers.SIZEOF_FLOAT;
    gl.glColorPointer(4, GL2.GL_FLOAT, stride, offset);
    gl.glEnableClientState(GL2.GL_COLOR_ARRAY);

    // texture
    offset = 0 + (3+4)*Buffers.SIZEOF_FLOAT;
    gl.glTexCoordPointer(2, GL2.GL_FLOAT, stride, offset);
    gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);

    // normals
    offset = 0 + (3+4+2)*Buffers.SIZEOF_FLOAT;
    gl.glNormalPointer(GL2.GL_FLOAT, stride, offset);
    gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);

    // bind texture
    if(mode == 0) {
      gl.glEnable(GL2.GL_TEXTURE_2D);
      gl.glBindTexture(GL2.GL_TEXTURE_2D, texID);
    }else{
      gl.glDisable(GL2.GL_TEXTURE_2D);
      gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
    }

    // render data
    gl.glDrawArrays(GL2.GL_TRIANGLES, 0, vertNo);

    gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
    gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
    gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
    gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
    gl.glDisable(GL2.GL_TEXTURE_2D);

    gl.glFlush();
  }
  
  private float[] loadVertexData(String filename, int perVertexFloats) {

    float[] floatArray = new float[0];
    // read vertex data from file
    int vertSize = 0;
    try {
      InputStream is = new FileInputStream(new File(filename));
      BufferedReader br = new BufferedReader(new InputStreamReader(is));
      String line = br.readLine();
      if (line != null) {
        vertSize = Integer.parseInt(line);
        floatArray = new float[vertSize];
      }
      int i = 0;
      while ((line = br.readLine()) != null && i < floatArray.length) {
        floatArray[i] = Float.parseFloat(line);
        i++;
      }
      if (i != vertSize || (vertSize % perVertexFloats) != 0) {
        floatArray = new float[0];
      }
      br.close();
    } catch (FileNotFoundException e) {
      System.out.println("Can not find vbo data file " + filename);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return floatArray;
  }
  
  // returns a valid textureID on success, otherwise 0
  private int loadTexture(GLAutoDrawable d, String filename) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context

    int width;
    int height;
    int level = 0;
    int border = 0;

    try {
      // open file
      FileInputStream fileInputStream = new FileInputStream(new File(filename));

      // read image
      BufferedImage bufferedImage = ImageIO.read(fileInputStream);
      fileInputStream.close();

      width = bufferedImage.getWidth();
      height = bufferedImage.getHeight();

      // convert image to ByteBuffer
      int[] pixelIntData = new int[width * height];
      bufferedImage.getRGB(0, 0, width, height, pixelIntData, 0, width);
      ByteBuffer buffer = ByteBuffer.allocateDirect(pixelIntData.length * 4);
      buffer.order(ByteOrder.nativeOrder());
      // Unpack the data, each integer into 4 bytes of the ByteBuffer.
      // Also we need to vertically flip the image because the image origin
      // in OpenGL is the lower-left corner.
      for (int y = 0; y < height; y++) {
        int k = (height - 1 - y) * width;
        for (int x = 0; x < width; x++) {
          buffer.put((byte) (pixelIntData[k] >>> 16));
          buffer.put((byte) (pixelIntData[k] >>> 8));
          buffer.put((byte) (pixelIntData[k]));
          buffer.put((byte) (pixelIntData[k] >>> 24));
          k++;
        }
      }
      buffer.rewind();

      // data is aligned in byte order
      gl.glPixelStorei(GL2.GL_UNPACK_ALIGNMENT, 1);

      // request textureID
      final int[] textureID = new int[1];
      gl.glGenTextures(1, textureID, 0);

      // bind texture
      gl.glBindTexture(GL2.GL_TEXTURE_2D, textureID[0]);

      // define how to filter the texture (important but ignore for now)
      gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER,
          GL2.GL_LINEAR);
      gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER,
          GL2.GL_LINEAR);

      // texture colors should modulate the original color values
      gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);

      // specify the 2D texture map
      gl.glTexImage2D(GL2.GL_TEXTURE_2D, level, GL2.GL_RGB, width, height,
          border, GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE, buffer);

      return textureID[0];
    } catch (FileNotFoundException e) {
      System.out.println("Can not find texture data file " + filename);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return 0;
  }
  
}

class MyGui extends JFrame implements GLEventListener {

  private Renderer renderer;

  public void createGUI() {
    setTitle("Interleaved Data VBO Demo");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    GLProfile glp = GLProfile.getDefault();
    GLCapabilities caps = new GLCapabilities(glp);
    GLCanvas canvas = new GLCanvas(caps);
    setSize(320, 320);

    getContentPane().add(canvas);
    final FPSAnimator ani = new FPSAnimator(canvas, 60, true);
    canvas.addGLEventListener(this);
    setVisible(true);
    renderer = new Renderer();
    
    canvas.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent event) {
        boolean redraw = false;
        switch(event.getKeyCode()) {
          case '1':
            if(renderer.mode == 1) renderer.mode = 0;
            else renderer.mode = 1;
            redraw = true;
            break;
        }
        if(redraw) { // done automatically
        }
      }
    });
    
    
    ani.start();
  }

  @Override
  public void init(GLAutoDrawable d) {
    renderer.init(d);
  }

  @Override
  public void reshape(GLAutoDrawable d, int x, int y, int width, int height) {
    renderer.resize(d, width, height);
  }

  @Override
  public void display(GLAutoDrawable d) {
    float offset = 1.0f;
    renderer.t += offset;
    renderer.display(d);
  }

  @Override
  public void dispose(GLAutoDrawable d) {
  }
}

public class ObjToVboExample {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}
