package de.frajul.particlelab.rendering;

import android.content.Context;
import android.opengl.GLES20;

import de.frajul.particlelab.entities.Entity;
import de.frajul.particlelab.data.Color;
import de.frajul.particlelab.entities.textures.Texture;
import de.frajul.particlelab.main.GameLog;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * Created by Julian on 23.11.2015.
 */
public class ShaderProgram {

    private Context context;
    private int vertexShader, fragmentShader, program;
    private int location_mvpMatrix;
    private int location_transformationMatrix;
    private int location_alpha;
    private int location_texAtlasSize;
    private int location_texAtlasIndex;
    private int location_isTerrain;
    private int location_color;

    public ShaderProgram(Context context) throws Exception {
        this.context = context;
        vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, "vertexShader.glsl");
        fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, "fragmentShader.glsl");
        program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, vertexShader);
        GLES20.glAttachShader(program, fragmentShader);
        GLES20.glLinkProgram(program);
        int[] linkStatus = new int[1];
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
        if (linkStatus[0] == GLES20.GL_FALSE) {
            GLES20.glDeleteProgram(program);
            throw new Exception("Could not link program: "
                    + GLES20.glGetProgramInfoLog(program));
        }

        bindAttribLocations();
        loadUniformLocations();
        GameLog.d("ShaderProgram successfully loaded");
    }

    private void loadUniformLocations() {
        location_mvpMatrix = GLES20.glGetUniformLocation(program, "mvpMatrix");
        location_transformationMatrix = GLES20.glGetUniformLocation(program, "transformationMatrix");
        location_alpha = GLES20.glGetUniformLocation(program, "alpha");
        location_texAtlasSize = GLES20.glGetUniformLocation(program, "texAtlasSize");
        location_texAtlasIndex = GLES20.glGetUniformLocation(program, "texAtlasIndex");
        location_isTerrain = GLES20.glGetUniformLocation(program, "isTerrain");
        location_color = GLES20.glGetUniformLocation(program, "color");
    }

    private void bindAttribLocations() {
        GLES20.glBindAttribLocation(program, 0, "position");
        GLES20.glBindAttribLocation(program, 1, "texCoords");
    }

    public void start() {
        GLES20.glUseProgram(program);
    }

    public void stop() {
        GLES20.glUseProgram(0);
    }

    public void loadMVPMatrix(MatrixCreator matrixCreator, float cameraX) {
        float[] mvpMatrix = matrixCreator.createModelViewProjectionMatrix(cameraX);
        GLES20.glUniformMatrix4fv(location_mvpMatrix, 1, false, mvpMatrix, 0);
    }

    public void loadTransformationMatrix(MatrixCreator matrixCreator, Entity entity) {
        float[] transformationMatrix = matrixCreator.createTransformationMatrix(entity);
        GLES20.glUniformMatrix4fv(location_transformationMatrix, 1, false, transformationMatrix, 0);
    }

    public void loadColor(Color color){
        GLES20.glUniform3f(location_color, color.getR(), color.getG(), color.getB());
    }

    public void loadAlpha(float alpha) {
        GLES20.glUniform1f(location_alpha, alpha);
    }

    public void loadTextureAtlasInfos(Texture texture) {
        GLES20.glUniform2f(location_texAtlasSize, texture.getAtlasWidth(), texture.getAtlasHeight());
        GLES20.glUniform1f(location_texAtlasIndex, texture.getAtlasIndex());
    }

    public void loadIsTerrain(boolean isTerrain) {
        float fIsTerrain = isTerrain ? 1 : 0;
        GLES20.glUniform1f(location_isTerrain, fIsTerrain);
    }

    public void cleanUp() {
        stop();
        GLES20.glDeleteShader(vertexShader);
        GLES20.glDeleteShader(fragmentShader);
        GLES20.glDeleteProgram(program);
        GameLog.d("Shader cleaned");
    }

    private int loadShader(int type, String shaderName) throws Exception {
        try {
            InputStream is = context.getAssets().open(shaderName);
            InputStreamReader isReader = new InputStreamReader(is);
            BufferedReader reader = new BufferedReader(isReader);
            StringBuilder source = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null)
                source.append(line);

            int shader = GLES20.glCreateShader(type);
            GLES20.glShaderSource(shader, source.toString());
            GLES20.glCompileShader(shader);

            int[] compiled = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == GLES20.GL_FALSE) {
                GLES20.glDeleteShader(shader);
                throw new Exception("Could not compile shader \"" + shaderName + "\": "
                        + GLES20.glGetShaderInfoLog(shader));
            }
            GameLog.d("Shader \"" + shaderName + "\" successfully loaded");
            return shader;
        } catch (Exception e) {
            throw new Exception("Could not load Shader \"" + shaderName + "\"", e);
        }
    }

}
