/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package gov.nasa.jpl.issm;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

public class MyGLRenderer implements GLSurfaceView.Renderer {
	public volatile float mAngle;
	public volatile float mAngleX; /* NEW STUFF */
	public volatile float mAngleY;

	private FloatBuffer[] triangleVert;

	private float[][] mScaleMatrix; // scaling /* NEW STUFF*/
	private float[][] mRotXMatrix; // rotation x
	private float[][] mRotYMatrix; // rotation x

	/**
	 * Store the model matrix. This matrix is used to move models from object
	 * space (where each model can be thought of being located at the center of
	 * the universe) to world space.
	 */
	private float[][] mModelMatrix;

	/**
	 * Store the view matrix. This can be thought of as our camera. This matrix
	 * transforms world space to eye space; it positions things relative to our
	 * eye.
	 */
	private float[][] mViewMatrix;

	/**
	 * Store the projection matrix. This is used to project the scene onto a 2D
	 * viewport.
	 */
	private float[][] mProjectionMatrix;

	/**
	 * Allocate storage for the final combined matrix. This will be passed into
	 * the shader program.
	 */
	private float[][] mMVPMatrix;

	/** This will be used to pass in the transformation matrix. */
	private int mMatrixHandle;

	private float[][] triangleData2DArr;

	private int mPositionHandle;

	private int mColorHandle;

	/** How many bytes per float. */
	private final int mBytesPerFloat = 4;

	/** How many elements per vertex. */
	private final int mStrideBytes = 7 * mBytesPerFloat;

	/** Offset of the position data. */
	private final int mPositionOffset = 0;

	/** Size of the position data in elements. */
	private final int mPositionDataSize = 3;

	/** Offset of the color data. */
	private final int mColorOffset = 3;

	/** Size of the color data in elements. */
	private final int mColorDataSize = 4;

	float scaleX = 1.0f;
	float scaleY = 1.0f;
	float scaleZ = 1.0f;

	public float eyeX;
	public float eyeY;
	public float eyeZ;

	public MyGLRenderer(float[][] vertices) {
		triangleData2DArr = new float[vertices.length][21];

		triangleVert = new FloatBuffer[vertices.length];
		mScaleMatrix = new float[triangleVert.length][16]; // scaling
		mRotXMatrix = new float[triangleVert.length][16]; // rotation x
		mRotYMatrix = new float[triangleVert.length][16]; // rotation x

		for (int i = 0; i < vertices.length; i++) {
			for (int j = 0; j < 21; j++)
				triangleData2DArr[i][j] = vertices[i][j];
		}

		// initialize vertex byte buffer for shape coordinates
		for (int i = 0; i < vertices.length; i++) {
			triangleVert[i] = ByteBuffer
					.allocateDirect(
							triangleData2DArr[i].length * mBytesPerFloat)
					.order(ByteOrder.nativeOrder()).asFloatBuffer();
			triangleVert[i].put(triangleData2DArr[i]).position(0);
		}
	}

	public void changeScale(float scale) {
		if (scaleX * scale > 3.0f)
			return;
		scaleX *= scale;
		scaleY *= scale;
		scaleZ *= scale;

	}

	public void defaultScale() {
		scaleX = 1f;
		scaleY = 1f;
		scaleZ = 1f;
	}

	public void onSurfaceCreated(GL10 unused, EGLConfig config) {
		// Set the background frame color
		GLES20.glClearColor(0.2f, 0.4f, 1.0f, 1.0f);

		// Position the eye behind the origin.
		eyeX = 0.0f;
		eyeY = 0.0f;
		eyeZ = 1.5f;

		// We are looking toward the distance
		final float lookX = 0.0f;
		final float lookY = 0.0f;
		final float lookZ = -5.0f;

		// Set our up vector. This is where our head would be pointing were we
		// holding the camera.
		final float upX = 0.0f;
		final float upY = 1.0f;
		final float upZ = 0.0f;

		// Set the view matrix. This matrix can be said to represent the camera
		// position.
		// NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination
		// of a model and
		// view matrix. In OpenGL 2, we can keep track of these matrices
		// separately if we choose.
		mViewMatrix = new float[triangleVert.length][16];

		for (int i = 0; i < triangleVert.length; i++)
			Matrix.setLookAtM(mViewMatrix[i], 0, eyeX, eyeY, eyeZ, lookX,
					lookY, lookZ, upX, upY, upZ);
		// Matrix.setLookAtM(mVMatrix[i], 0, 0, 0, -5.0f, 0.0f, 0f, 0f, 0f,
		// 1.0f, 0.0f);

		final String vertexShader = "uniform mat4 u_MVPMatrix;      \n" // A
																		// constant
																		// representing
																		// the
																		// combined
																		// model/view/projection
																		// matrix.
				+ "attribute vec4 a_Position;     \n" // Per-vertex position
														// information we will
														// pass in.
				+ "attribute vec4 a_Color;        \n" // Per-vertex color
														// information we will
														// pass in.
				+ "varying vec4 v_Color;          \n" // This will be passed
														// into the fragment
														// shader.
				+ "void main()                    \n" // The entry point for our
														// vertex shader.
				+ "{                              \n"
				+ "   v_Color = a_Color;          \n" // Pass the color through
														// to the fragment
														// shader.

				// It will be interpolated across the triangle.
				+ "   gl_Position = u_MVPMatrix   \n" // gl_Position is a
														// special variable used
														// to store the final
														// position.
				+ "               * a_Position;   \n" // Multiply the vertex by
														// the matrix to get the
														// final point in
				+ "}                              \n"; // normalized screen
														// coordinates.

		final String fragmentShader = "precision mediump float;       \n" // Set
																			// the
																			// default
																			// precision
																			// to
																			// medium.
																			// We
																			// don't
																			// need
																			// as
																			// high
																			// of
																			// a
																			// //
																			// precision
																			// in
																			// the
																			// fragment
																			// shader.
				+ "varying vec4 v_Color;          \n" // This is the color from
														// the vertex shader
														// interpolated across
														// the
														// triangle per
														// fragment.
				+ "void main()                    \n" // The entry point for our
														// fragment shader.
				+ "{                              \n"
				+ "   gl_FragColor = v_Color;     \n" // Pass the color directly
														// through the pipeline.
				+ "}                              \n";

		int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
		// Pass in the shader source.
		GLES20.glShaderSource(vertexShaderHandle, vertexShader);

		// Compile the shader.
		GLES20.glCompileShader(vertexShaderHandle);

		// Get the compilation status.
		final int[] compileStatus1 = new int[1];
		GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS,
				compileStatus1, 0);

		// If the compilation failed, delete the shader.
		if (compileStatus1[0] == 0) {
			GLES20.glDeleteShader(vertexShaderHandle);
			vertexShaderHandle = 0;
		}

		int fragmentShaderHandle = GLES20
				.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
		// Pass in the shader source.
		GLES20.glShaderSource(fragmentShaderHandle, fragmentShader);

		// Compile the shader.
		GLES20.glCompileShader(fragmentShaderHandle);

		// Get the compilation status.
		final int[] compileStatus2 = new int[1];
		GLES20.glGetShaderiv(fragmentShaderHandle, GLES20.GL_COMPILE_STATUS,
				compileStatus2, 0);

		// If the compilation failed, delete the shader.
		if (compileStatus2[0] == 0) {
			GLES20.glDeleteShader(fragmentShaderHandle);
			fragmentShaderHandle = 0;
		}

		int mProgram = GLES20.glCreateProgram();
		// Bind the vertex shader to the program.
		GLES20.glAttachShader(mProgram, vertexShaderHandle);

		// Bind the fragment shader to the program.
		GLES20.glAttachShader(mProgram, fragmentShaderHandle);

		// Bind attributes
		GLES20.glBindAttribLocation(mProgram, 0, "a_Position");
		GLES20.glBindAttribLocation(mProgram, 1, "a_Color");

		// Link the two shaders together into a program.
		GLES20.glLinkProgram(mProgram);

		// Get the link status.
		final int[] linkStatus = new int[1];
		GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);

		// If the link failed, delete the program.
		if (linkStatus[0] == 0) {
			GLES20.glDeleteProgram(mProgram);
			mProgram = 0;
		}

		for (int i = 0; i < triangleVert.length; i++) {
			mMatrixHandle = GLES20
					.glGetUniformLocation(mProgram, "u_MVPMatrix");
			mPositionHandle = GLES20
					.glGetAttribLocation(mProgram, "a_Position");
			mColorHandle = GLES20.glGetAttribLocation(mProgram, "a_Color");
		}

		// Add program to OpenGL environment
		GLES20.glUseProgram(mProgram);
	}

	public void onSurfaceChanged(GL10 unused, int width, int height) {
		// Adjust the viewport based on geometry changes,
		// such as screen rotation
		GLES20.glViewport(0, 0, width, height);

		// Create a new perspective projection matrix. The height will stay the
		// same
		// while the width will vary as per aspect ratio.
		final float ratio = (float) width / height;

		mProjectionMatrix = new float[triangleVert.length][16];
		for (int i = 0; i < triangleVert.length; i++)
			Matrix.frustumM(mProjectionMatrix[i], 0, -ratio, ratio, -1, 1, 1f,
					5);

	}

	public void onDrawFrame(GL10 glUnused) {
		// Draw background color
		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

		mModelMatrix = new float[triangleVert.length][16];
		// Draw the triangle facing straight on.
		for (int i = 0; i < triangleVert.length; i++) {
			Matrix.setIdentityM(mScaleMatrix[i], 0);
			Matrix.scaleM(mScaleMatrix[i], 0, scaleX, scaleY, scaleZ);

			// Rotation along x
			Matrix.setRotateM(mRotXMatrix[i], 0, this.mAngleY, -1.0f, 0.0f,
					0.0f);
			Matrix.setRotateM(mRotYMatrix[i], 0, this.mAngleX, 0.0f, 1.0f, 0.0f);

			Matrix.setLookAtM(mViewMatrix[i], 0, eyeX, eyeY, eyeZ, 0.0f, 0.0f,
					-5.0f, 0.0f, 1.0f, 0.0f);

		}

		mMVPMatrix = new float[triangleVert.length][16];
		draw(triangleVert);

	}

	private void draw(final FloatBuffer[] aTriangleBuffer) {
		for (int i = 0; i < aTriangleBuffer.length; i++) {
			// Pass in the position information
			aTriangleBuffer[i].position(mPositionOffset);
			GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize,
					GLES20.GL_FLOAT, false, mStrideBytes, aTriangleBuffer[i]);

			GLES20.glEnableVertexAttribArray(mPositionHandle);

			// Pass in the color information
			aTriangleBuffer[i].position(mColorOffset);
			GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize,
					GLES20.GL_FLOAT, false, mStrideBytes, aTriangleBuffer[i]);

			GLES20.glEnableVertexAttribArray(mColorHandle);

			Matrix.setRotateM(mRotXMatrix[i], 0, this.mAngleY, -1.0f, 0.0f,
					0.0f);
			Matrix.setRotateM(mRotYMatrix[i], 0, this.mAngleX, 0.0f, 1.0f, 0.0f);

			float tempMatrix[][] = new float[triangleVert.length][16];
			Matrix.multiplyMM(tempMatrix[i], 0, mRotYMatrix[i], 0,
					mRotXMatrix[i], 0);
			Matrix.multiplyMM(mModelMatrix[i], 0, mScaleMatrix[i], 0,
					tempMatrix[i], 0);

			// This multiplies the view matrix by the model matrix, and stores
			// the result in the MVP matrix
			// (which currently contains model * view).
			Matrix.multiplyMM(mMVPMatrix[i], 0, mViewMatrix[i], 0,
					mModelMatrix[i], 0);

			// This multiplies the modelview matrix by the projection matrix,
			// and stores the result in the MVP matrix
			// (which now contains model * view * projection).
			Matrix.multiplyMM(mMVPMatrix[i], 0, mProjectionMatrix[i], 0,
					mMVPMatrix[i], 0);

			GLES20.glUniformMatrix4fv(mMatrixHandle, 1, false, mMVPMatrix[i], 0);

			// Draw the triangle
			GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);

			// Disable vertex array
			GLES20.glDisableVertexAttribArray(mPositionHandle);
		}
	}

}