package tetra;

	import java.io.BufferedReader;
	import java.io.FileReader;
	import java.io.IOException;
	import java.nio.FloatBuffer;
	import java.nio.ShortBuffer;

	import javax.media.opengl.GL;
	import javax.media.opengl.GL2;
	import javax.media.opengl.GL3;
	import javax.media.opengl.GLAutoDrawable;
	import javax.media.opengl.GLCapabilities;
	import javax.media.opengl.GLEventListener;
	import javax.media.opengl.awt.GLCanvas;

	import com.jogamp.common.nio.Buffers;
	import com.jogamp.opengl.util.Animator;

	public class TetraGraphics extends GLCanvas implements GLEventListener {
		private static final long serialVersionUID = 42L;
		
		private static final int VERTEX_IN_POSITION = 0;
		private static final int VERTEX_IN_COLOUR = 1;
		private int VERTEX_UNIFORM_ROTATIONX;
		private int VERTEX_UNIFORM_ROTATIONY;
		private int VERTEX_UNIFORM_ROTATIONZ;
		private int VERTEX_UNIFORM_SCALING;
		private int VERTEX_UNIFORM_TRANSLATION;
		private int VERTEX_UNIFORM_PROJECTION;
		
	    private FloatBuffer VertexBuffer;
	    private ShortBuffer IndexBuffer;
	    private int VBOVertices;
	    private int VBOIndices;
		
		private double theta = 0, delta = 0.05;
		
		TetraGraphics(GLCapabilities caps) {
			super(caps);
	        // Handle events for main window
	        this.addGLEventListener(this);
	        // Set up animation thread
	        Animator animator = new Animator(this);
	        animator.start();
		}
		
		public void setSpeed(double d) {
			delta = d;
		}

		@Override
		public void init(GLAutoDrawable drawable) {
			// OpenGL initialisation
			final GL2 gl = drawable.getGL().getGL2();
			// Diagnostics
			System.out.println("Vendor: " + gl.glGetString(GL.GL_VENDOR));
			System.out.println("Renderer: " + gl.glGetString(GL.GL_RENDERER));
			System.out.println("OpenGL version: " + gl.glGetString(GL.GL_VERSION));
			// Synchronise update with v-sync frame rate
			gl.setSwapInterval(1);
			// Build model
			buildModel (gl);
			// Add shaders
			addShaders (gl);
			gl.glEnable(gl.GL_DEPTH_TEST);
		}
		
		private void buildModel (GL2 gl) {
			// Tetrahedron
			// Specify (x,y,z) and (r,g,b) for each vertex
			float[] vertices = {
					+0.5f, +0.5f, +0.5f, 0.9f, 0.9f, 0.9f,
					+0.5f, -0.5f, -0.5f, 0.9f, 0.1f, 0.1f,
					-0.5f, +0.5f, -0.5f, 0.1f, 0.9f, 0.1f,
					-0.5f, -0.5f, +0.5f, 0.1f, 0.1f, 0.9f,
			};
			// Specify 3 vertices for each triangular face
			short[] faces = {
					0, 1, 2,
					0, 2, 3,
					0, 3, 1,
					3, 1, 2
			};
			
			int[] buffers = new int[2];
		    gl.glGenBuffers(2, buffers, 0);
		    
			VertexBuffer = FloatBuffer.wrap(vertices);
			VBOVertices = buffers[0];
		    gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBOVertices);
		    gl.glBufferData(GL.GL_ARRAY_BUFFER, vertices.length * Buffers.SIZEOF_FLOAT, VertexBuffer, GL.GL_STATIC_DRAW);
		    gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);

		    IndexBuffer = ShortBuffer.wrap(faces);
		    VBOIndices = buffers[1];
		    gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, VBOIndices);
		    gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, faces.length * Buffers.SIZEOF_SHORT, IndexBuffer, GL.GL_STATIC_DRAW);
		    gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0);
		}
		
		private void addShaders (GL2 gl) {
			// Create shader objects and read & compile code
			int v = createShader (gl, GL2.GL_VERTEX_SHADER, "vertexshader.glsl");
			int f = createShader (gl, GL2.GL_FRAGMENT_SHADER, "fragmentshader.glsl");
			// Create a shader program
			int program = gl.glCreateProgram();
			// Attach shaders to program
			gl.glAttachShader(program, v);
			gl.glAttachShader(program, f);
			// Associate attribute IDs with locations of inputs to vertex shader
			gl.glBindAttribLocation(program, VERTEX_IN_POSITION, "position");
			gl.glBindAttribLocation(program, VERTEX_IN_COLOUR, "colour");
			// Link program
			gl.glLinkProgram(program);
			gl.glValidateProgram(program);
			// Bind names of uniform inputs to shader program
			VERTEX_UNIFORM_ROTATIONX = gl.glGetUniformLocation(program, "rotateX");
			VERTEX_UNIFORM_ROTATIONY = gl.glGetUniformLocation(program, "rotateY");
			VERTEX_UNIFORM_ROTATIONZ = gl.glGetUniformLocation(program, "rotateZ");
			VERTEX_UNIFORM_SCALING = gl.glGetUniformLocation(program, "scale");
			VERTEX_UNIFORM_TRANSLATION = gl.glGetUniformLocation(program, "translate");
			VERTEX_UNIFORM_PROJECTION = gl.glGetUniformLocation(program, "projection");
			// Use program
			gl.glUseProgram(program);
		}
		
		private int createShader (GL2 gl, int shader, String file) {
			int s = gl.glCreateShader(shader);
			try {
				BufferedReader brv = new BufferedReader(new FileReader(file));
				String vsrc = "";
				String line;
				while ((line=brv.readLine()) != null) {
					vsrc += line + "\n";
				}
				brv.close();
				gl.glShaderSource(s, 1, new String[] {vsrc}, (int[]) null, 0);
				gl.glCompileShader(s);
			} catch (IOException e) {
				e.printStackTrace();
			}
			return s;
		}
		
		@Override
		public void display(GLAutoDrawable drawable) {
			// Separate drawing into updating the model and then rendering it
		    update();
		    render(drawable);	
		}
		
		private void update() {
		    // animation step for each update
		    theta += delta;
		}
		
		private void render(GLAutoDrawable drawable) {
			final float s = (float) Math.sin(theta);
			final float c = (float) Math.cos(theta);
			
			GL2 gl = drawable.getGL().getGL2();
			
		    // clear background
			gl.glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
		    gl.glClear(GL3.GL_DEPTH_BUFFER_BIT | GL3.GL_COLOR_BUFFER_BIT);
		    
		    // set up transformations
		    float[] identityMatrix = {
		    		1.0f, 0.0f, 0.0f, 0.0f,
		    		0.0f, 1.0f, 0.0f, 0.0f,
		    		0.0f, 0.0f, 1.0f, 0.0f,
		    		0.0f, 0.0f, 0.0f, 1.0f
		    };
		    float[] rotationXMatrix = {
		    		1.0f, 0.0f, 0.0f, 0.0f,
		    		0.0f,    c,   -s, 0.0f,
		    		0.0f,    s,    c, 0.0f,
		    		0.0f, 0.0f, 0.0f, 1.0f
		    };
		    float[] rotationYMatrix = {
		    		   s, 0.0f,    c, 0.0f,
		    		0.0f, 1.0f, 0.0f, 0.0f,
		    		   c, 0.0f,   -s, 0.0f,
		    		0.0f, 0.0f, 0.0f, 1.0f
		    };
		    float[] rotationZMatrix = {
		    		   c,   -s, 0.0f, 0.0f,
		    		   s,    c, 0.0f, 0.0f,
		    		0.0f, 0.0f, 1.0f, 0.0f,
		    		0.0f, 0.0f, 0.0f, 1.0f
		    };
		    float[] scalingMatrix = {
		    		1.0f, 0.0f, 0.0f, 0.0f,
		    		0.0f, 1.0f, 0.0f, 0.0f,
		    		0.0f, 0.0f, 1.0f, 0.0f,
		    		0.0f, 0.0f, 0.0f, 1.0f
		    };
		    float[] translationMatrix = {
		    		1.0f, 0.0f, 0.0f, 0.0f,
		    		0.0f, 1.0f, 0.0f, 0.0f,
		    		0.0f, 0.0f, 1.0f, 0.0f,
		    		0.0f, 0.0f, 0.0f, 1.0f
		    };
		    float[] projectionMatrix = identityMatrix;
		    
		    gl.glUniformMatrix4fv(VERTEX_UNIFORM_ROTATIONX, 1, false, rotationXMatrix, 0);
		    gl.glUniformMatrix4fv(VERTEX_UNIFORM_ROTATIONY, 1, false, rotationYMatrix, 0);
		    gl.glUniformMatrix4fv(VERTEX_UNIFORM_ROTATIONZ, 1, false, rotationZMatrix, 0);
		    gl.glUniformMatrix4fv(VERTEX_UNIFORM_SCALING, 1, false, scalingMatrix, 0);
		    gl.glUniformMatrix4fv(VERTEX_UNIFORM_TRANSLATION, 1, false, translationMatrix, 0);
		    gl.glUniformMatrix4fv(VERTEX_UNIFORM_PROJECTION, 1, false, projectionMatrix, 0);

		    // draw triangular faces
		    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, VBOVertices);
		    
			gl.glEnableVertexAttribArray(VERTEX_IN_POSITION);
			gl.glVertexAttribPointer(VERTEX_IN_POSITION, 3, GL.GL_FLOAT, false, 6 * Buffers.SIZEOF_FLOAT, 0);
		    
			gl.glEnableVertexAttribArray(VERTEX_IN_COLOUR);
			gl.glVertexAttribPointer(VERTEX_IN_COLOUR, 3, GL.GL_FLOAT, false, 6 * Buffers.SIZEOF_FLOAT, 3 * Buffers.SIZEOF_FLOAT);
		    
		    gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, VBOIndices);
		    gl.glDrawElements(GL.GL_TRIANGLES, IndexBuffer.capacity(), GL.GL_UNSIGNED_SHORT, 0);

		    // tidy up
		    gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0);
			gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
			gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
		}
		
		@Override
		public void dispose(GLAutoDrawable drawable) {
			// Clean up - release textures, shaders, vertex buffers and so on
		}

		@Override
		public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
			// handle re-sized window
		}
	}
