package com.theta.glview;

import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;

import com.theta.glview.model.UVSphere;
import com.theta.model.Constants;
import com.theta.model.Photo;

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


/**
 * Renderer class for photo display
 */
public class GLRenderer implements Renderer {

    private final String VSHADER_SRC =
            "attribute vec4 aPosition;\n" +
            "attribute vec2 aUV;\n" +
            "uniform mat4 uProjection;\n" +
            "uniform mat4 uView;\n" +
            "uniform mat4 uModel;\n" +
            "varying vec2 vUV;\n" +
            "void main() {\n" +
            "  gl_Position = uProjection * uView * uModel * aPosition;\n" +
            "  vUV = aUV;\n" +
            "}\n";

    private final String FSHADER_SRC =
            "precision mediump float;\n" +
            "varying vec2 vUV;\n" +
            "uniform sampler2D uTex;\n" +
            "void main() {\n" +
            "  gl_FragColor = texture2D(uTex, vUV);\n" +
            "}\n";


    private static final float Z_NEAR = 0.1f;
    private static final float Z_FAR = 100.0f;

    private UVSphere mEastShell = null;
    private UVSphere mWestShell = null;

    private double mRotationAngleY;
    private double mRotationAngleXZ;

    private Photo mTexture;
    private boolean mTextureUpdate = false;

    private float mScreenAspectRatio;

    private float mCameraPosX = 0.0f;
    private float mCameraPosY = 0.0f;
    private float mCameraPosZ = 0.0f;

    private float mCameraDirectionX = 0.0f;
    private float mCameraDirectionY = 0.0f;
    private float mCameraDirectionZ = 1.0f;

    //既存ソース
//    private float mCameraFovDegree = 45;
    private float mCameraFovDegree = 100;
    private int[] mTextures = new int[2];

    private int mPositionHandle;
    private int mProjectionMatrixHandle;
    private int mViewMatrixHandle;
    private int mUVHandle;
    private int mTexHandle;
    private int mModelMatrixHandle;

    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    private final float[] mModelMatrix = new float[16];

    /**
     * Constructor
     */
    public GLRenderer() {
        mEastShell = new UVSphere(Constants.TEXTURE_SHELL_RADIUS, Constants.SHELL_DIVIDES, true);
        mWestShell = new UVSphere(Constants.TEXTURE_SHELL_RADIUS, Constants.SHELL_DIVIDES, false);

        mRotationAngleY = 0.0f;
        mRotationAngleXZ = 0.0f;
    }


    /**
     * onDrawFrame Method
     * @param gl GLObject (not used)
     */
    @Override
    public void onDrawFrame(final GL10 gl) {
        if (mTexture == null) {
            return;
        }
        mCameraDirectionX = (float) (Math.cos(mRotationAngleXZ)*Math.cos(mRotationAngleY));
        mCameraDirectionZ = (float) (Math.sin(mRotationAngleXZ)*Math.cos(mRotationAngleY));
        mCameraDirectionY = (float) Math.sin(mRotationAngleY);

        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.setIdentityM(mViewMatrix, 0);
        Matrix.setIdentityM(mProjectionMatrix, 0);

        if (mTextureUpdate && null != mTexture && !mTexture.getPhoto().isRecycled()) {
            Log.d("", "load texture1");
            loadTexture(mTexture.getPhoto());
            mTextureUpdate = false;
        }

        Matrix.setLookAtM(mViewMatrix, 0, mCameraPosX, mCameraPosY, mCameraPosZ, mCameraDirectionX, mCameraDirectionY, mCameraDirectionZ, 0.0f, 1.0f, 0.0f);
        Matrix.perspectiveM(mProjectionMatrix, 0, mCameraFovDegree, mScreenAspectRatio, Z_NEAR, Z_FAR);


        if (null != mTexture.getElevetionAngle()) {
            float elevationAngle = mTexture.getElevetionAngle().floatValue();
            Matrix.rotateM(mModelMatrix, 0, (float) elevationAngle, 0, 0, 1);
        }
        if (null != mTexture.getHorizontalAngle()) {
            float horizontalAngle = mTexture.getHorizontalAngle().floatValue();
            Matrix.rotateM(mModelMatrix, 0, horizontalAngle, 1, 0, 0);
        }

        GLES20.glUniformMatrix4fv(mModelMatrixHandle, 1, false, mModelMatrix, 0);
        GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);
        GLES20.glUniformMatrix4fv(mViewMatrixHandle, 1, false, mViewMatrix, 0);

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
        GLES20.glUniform1i(mTexHandle, 0);

        mEastShell.draw(mPositionHandle, mUVHandle);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[1]);
        GLES20.glUniform1i(mTexHandle, 0);

        mWestShell.draw(mPositionHandle, mUVHandle);

        return;
    }

    /**
     * onSurfaceChanged Method
     * @param gl GLObject (not used)
     * @param width Screen width
     * @param height Screen height
     */
    @Override
    public void onSurfaceChanged(final GL10 gl, final int width, final int height) {
        //既存ソース
//        int _height = height/2;
//        mScreenAspectRatio = (float) width / (float) (_height == 0 ? 1 : _height);
//        GLES20.glViewport(0, _height / 2, width, _height);

        //全画面表示のため、ソース修正
        int _height = height;
        mScreenAspectRatio = (float) width / (float) (_height == 0 ? 1 : _height);
        GLES20.glViewport(0, 0, width, _height);

        Matrix.setLookAtM(mViewMatrix, 0, mCameraPosX, mCameraPosY, mCameraPosZ, mCameraDirectionX, mCameraDirectionY, mCameraDirectionZ, 0.0f, 1.0f, 0.0f);
        Matrix.perspectiveM(mProjectionMatrix, 0, mCameraFovDegree, mScreenAspectRatio, Z_NEAR, Z_FAR);

        return;
    }

    /**
     * onSurfaceCreated Method
     * @param gl GLObject (not used)
     * @param config EGL Setting Object
     */
    @Override
    public void onSurfaceCreated(final GL10 gl, final EGLConfig config) {

        int vShader;
        int fShader;
        int program;

        vShader = loadShader(GLES20.GL_VERTEX_SHADER, VSHADER_SRC);
        fShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FSHADER_SRC);

        program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, vShader);
        GLES20.glAttachShader(program, fShader);
        GLES20.glLinkProgram(program);

        GLES20.glUseProgram(program);

        mPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
        mUVHandle = GLES20.glGetAttribLocation(program, "aUV");
        mProjectionMatrixHandle = GLES20.glGetUniformLocation(program, "uProjection");
        mViewMatrixHandle = GLES20.glGetUniformLocation(program, "uView");
        mTexHandle = GLES20.glGetUniformLocation(program, "uTex");
        mModelMatrixHandle = GLES20.glGetUniformLocation(program, "uModel");

        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        return;
    }


    /**
     * Rotation process method
     * @param xz X axis direction rotation value
     * @param y Y axis direction rotation value
     */
    public void rotate(float xz, float y) {
        mRotationAngleXZ += xz;
        mRotationAngleY += y;
        if (mRotationAngleY > (Math.PI/2)) {
            mRotationAngleY = (Math.PI/2);
        }
        if (mRotationAngleY < -(Math.PI/2)) {
            mRotationAngleY = -(Math.PI/2);
        }
        return;
    }


    /**
     * Zoom in/Zoom out method
     * @param ratio Scale value: Zoom in process performed if the value is 1.0 or more; zoom out process is performed if the value is less than 1.0
     */
    public void scale(float ratio) {

        if (ratio < 1.0) {
            mCameraFovDegree = mCameraFovDegree * (Constants.SCALE_RATIO_TICK_EXPANSION);
            if (mCameraFovDegree > Constants.CAMERA_FOV_DEGREE_MAX) {
                mCameraFovDegree = Constants.CAMERA_FOV_DEGREE_MAX;
            }
        }
        else {
            mCameraFovDegree = mCameraFovDegree * (Constants.SCALE_RATIO_TICK_REDUCTION);
            if (mCameraFovDegree < Constants.CAMERA_FOV_DEGREE_MIN) {
                mCameraFovDegree = Constants.CAMERA_FOV_DEGREE_MIN;
            }
        }

        return;
    }


    /**
     * Sets the texture for the sphere
     * @param texture Photo object for texture
     */
    public void setTexture(Photo texture) {
        mTexture = texture;
        mTextureUpdate = true;
        return;
    }

    /**
     * Acquires the set texture
     * @return Photo object for texture
     */
    public Photo getTexture() {
        return mTexture;
    }


    /**
     * GL error judgment method for debugging
     * @param TAG TAG output character string
     * @param glOperation Message output character string
     */
    public static void checkGlError(String TAG, String glOperation) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e(TAG, glOperation + ": glError " + error);
            throw new RuntimeException(glOperation + ": glError " + error);
        }
        return;
    }


    /**
     * Texture setting method
     * @param texture Setting texture
     */
    public void loadTexture(final Bitmap texture) {

        final Bitmap bitmap = texture;
        int dividedWidth = bitmap.getWidth() / 2;

        GLES20.glGenTextures(2, mTextures, 0);

        for (int textureIndex = 0; textureIndex < 2; textureIndex++) {
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[textureIndex]);

            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

            Bitmap dividedBitmap = Bitmap.createBitmap(bitmap, (dividedWidth * textureIndex), 0, dividedWidth, bitmap.getHeight());

            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, dividedBitmap, 0);
            dividedBitmap.recycle();
        }

        return;
    }


    private int loadShader(int type, String shaderCode){

        int shader = GLES20.glCreateShader(type);

        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}