package xaero.rotatenjump.game.graphics;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.opengl.GLES20;
import android.opengl.Matrix;

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

import xaero.rotatenjump.R;
import xaero.rotatenjump.game.graphics.model.Cube;
import xaero.rotatenjump.game.graphics.model.FileModel;
import xaero.rotatenjump.game.graphics.model.Model;
import xaero.rotatenjump.game.graphics.model.RightPolygon;
import xaero.rotatenjump.game.graphics.model.Sphere;
import xaero.rotatenjump.game.graphics.model.Square;

/**
 * Created by xaero on 18/08/2017.
 */

public class Graphics {

    public static Model[] modelsLoadedFromFiles = new Model[64];
    //private static final float[] toLight = {5, 5, 0};
    private static final float[] toLight = {0, 5, 7};

    public static final Model CIRCLE = new RightPolygon(12);
    public static final Model SPHERE5_10 = new Sphere(5, 10);
    public static final Model SPHERE10_20 = new Sphere(10, 20);
    public static final Model SQUARE = new Square();
    public static final Model CUBE = new Cube();

    public static final float[] YELLOW = {1,1,0,1};
    public static final float[] RED = {1,0,0,1};
    public static final float[] BLACK = {0,0,0,1};
    public static final float[] WHITE = {1,1,1,1};
    public static final float[] DKGRAY = {0.266f,0.266f,0.266f,1};
    public static final float[] BG_COLOUR = {0.965f, 0.965f, 0.965f, 1};

    private static final float[] vpMatrix = new float[16];
    private static final float[] projectionMatrixOrtho = new float[16];
    private static final float[] projectionMatrixPerspective = new float[16];
    private static float[] projectionMatrix = new float[16];
    private static final float[] viewMatrix = new float[16];

    private static int shadersProgram;
    private static int vertexShader;
    private static int fragmentShader;
    private static int vpMatrixLocation;
    private static int mMatrixLocation;
    private static int nMatrixLocation;
    private static int positionLocation;
    private static int uvLocation;
    private static int normalLocation;
    private static int colourLocation;
    private static int vertexColorLocation;
    private static int toLightLocation;
    private static int useLightingLocation;
    private static int textureLocation;
    private static int useTextureLocation;

    public static float[] color = new float[] {1.0f, 1.0f, 1.0f, 1.0f};
    public static boolean lighting = true;

    private static float width;//same for every device
    private static float height;
    public static float screenScale = 1.0f;//for use with coordinates that aren't the same on all devices
    public static FontRenderer fontRenderer;
    private static float FOV;
    public static float ratio;
    public static float halfFOVTan;
    private static double perspectiveScale;//so that ingame 1.0 is equal to on-screen pixel on a 1080p screen
    private static final float perspectiveDistance = 50f;
    //public static Model testModel = new Sphere(10, 20);

    public static void load(Context context, AssetManager assetManager){
        Typeface font = Typeface.createFromAsset(assetManager, "electroharmonix.ttf");
        fontRenderer = new FontRenderer(950, 70, font);
        vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, context.getResources().openRawResource(R.raw.vertex));
        fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, context.getResources().openRawResource(R.raw.fragment));
        //System.exit(0);
        shadersProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(shadersProgram, vertexShader);
        GLES20.glAttachShader(shadersProgram, fragmentShader);
        GLES20.glLinkProgram(shadersProgram);
        GLES20.glUseProgram(shadersProgram);
        positionLocation = GLES20.glGetAttribLocation(shadersProgram, "in_Position");
        uvLocation = GLES20.glGetAttribLocation(shadersProgram, "in_UV");
        normalLocation = GLES20.glGetAttribLocation(shadersProgram, "in_Normal");
        vertexColorLocation = GLES20.glGetAttribLocation(shadersProgram, "in_Colour");
        colourLocation = GLES20.glGetUniformLocation(shadersProgram, "mColor");
        vpMatrixLocation = GLES20.glGetUniformLocation(shadersProgram, "vpMatrix");
        mMatrixLocation = GLES20.glGetUniformLocation(shadersProgram, "mMatrix");
        nMatrixLocation = GLES20.glGetUniformLocation(shadersProgram, "nMatrix");
        textureLocation = GLES20.glGetUniformLocation(shadersProgram, "texture");
        useTextureLocation = GLES20.glGetUniformLocation(shadersProgram, "useTexture");
        toLightLocation = GLES20.glGetUniformLocation(shadersProgram, "toLight");
        useLightingLocation = GLES20.glGetUniformLocation(shadersProgram, "useLighting");
        /*
         * Loading models
         */
        modelsLoadedFromFiles[0] = new FileModel(context.getResources().openRawResource(R.raw.ninjastar2_cut1));
        modelsLoadedFromFiles[1] = new FileModel(context.getResources().openRawResource(R.raw.ninjastar2_cut2));
        modelsLoadedFromFiles[2] = new FileModel(context.getResources().openRawResource(R.raw.coin));
        modelsLoadedFromFiles[3] = new FileModel(context.getResources().openRawResource(R.raw.kunai));
        modelsLoadedFromFiles[4] = new FileModel(context.getResources().openRawResource(R.raw.katana));
        modelsLoadedFromFiles[5] = new FileModel(context.getResources().openRawResource(R.raw.ninjastar2));
        modelsLoadedFromFiles[6] = new FileModel(context.getResources().openRawResource(R.raw.scoreclock));
        //Log.d("test", ""+vertexColorLocation);
        //no idea why this is needed but it's needed
        //Matrix.setIdentityM(nMatrix, 0);
        //GLES20.glUniformMatrix4fv(nMatrixLocation, 1, false, nMatrix, 0);
    }

    public static void onSurfaceChanged(int w, int h){
        ratio = (float) w / h;
        width = 1080f;
        height = width/ratio;
        screenScale = width / w;
        FOV = 80f;
        halfFOVTan = (float) Math.tan(Math.toRadians(FOV/2));
        perspectiveScale = 2.0 * (perspectiveDistance * Math.tan(Math.toRadians(FOV/2))) / height;
        //System.out.println("Distance: " + perspectiveDistance);
        Matrix.perspectiveM(projectionMatrixPerspective, 0, FOV, ratio, 1f, 100f);
        //perspectiveScale = 1;
        //Matrix.orthoM(projectionMatrixPerspective, 0, -width/2, width/2, -height/2, height/2, 1f, 100f);//1 pixel on a 1080p screen = 1.0
        Matrix.orthoM(projectionMatrixOrtho, 0, -width/2, width/2, -height/2, height/2, 1f, 1000f);//1 pixel on a 1080p screen = 1.0
    }

    public static void setPerspective(boolean p){
        projectionMatrix = p ? projectionMatrixPerspective : projectionMatrixOrtho;
    }

    public static void updateView(float cameraX, float cameraY, float scaleX, float scaleY, float scaleZ, float angleX, float angleZ){
        //scaleZ = 1;
        Matrix.setIdentityM(viewMatrix, 0);
        Matrix.translateM(viewMatrix, 0, viewMatrix, 0, 0, 0, projectionMatrix == projectionMatrixPerspective ? -perspectiveDistance : -3);
        if(projectionMatrix == projectionMatrixPerspective) {
            Matrix.rotateM(viewMatrix, 0, angleX, 1, 0, 0);
            //Matrix.rotateM(viewMatrix, 0, 45, 0, 1, 0);
            Matrix.rotateM(viewMatrix, 0, angleZ, 0, 0, 1);
        }
        Matrix.scaleM(viewMatrix, 0, viewMatrix, 0, (float)(scaleX * (projectionMatrix == projectionMatrixPerspective ? perspectiveScale : 1)), (float)(scaleY * (projectionMatrix == projectionMatrixPerspective ? perspectiveScale : 1)),  (float)(scaleZ * (projectionMatrix == projectionMatrixPerspective ? perspectiveScale : 1)));
        Matrix.translateM(viewMatrix, 0, viewMatrix, 0, -cameraX, -cameraY, 0);
        Matrix.multiplyMM(vpMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        sendVPMatrix();
    }

    public static int loadShader(int type, InputStream in){
        int shader = GLES20.glCreateShader(type);
        StringBuilder sourceBuilder = new StringBuilder();
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        String line;
        try {
            while((line = reader.readLine()) != null)
                sourceBuilder.append(line).append("\n");
        } catch (IOException e) {
            GLES20.glDeleteShader(shader);
            e.printStackTrace();
            return -1;
        }
        //Log.d("src", sourceBuilder.toString());
        GLES20.glShaderSource(shader, sourceBuilder.toString());
        GLES20.glCompileShader(shader);
        return shader;
    }

    public static void sendVPMatrix(){
        GLES20.glUniformMatrix4fv(vpMatrixLocation, 1, false, vpMatrix, 0);
    }

    public static void sendMatrices(float[] mMatrix, float[] nMatrix){
        GLES20.glUniformMatrix4fv(mMatrixLocation, 1, false, mMatrix, 0);
        if(lighting)
            GLES20.glUniformMatrix4fv(nMatrixLocation, 1, false, nMatrix, 0);
    }

    public static void sendLightVector(){
        GLES20.glUniform3fv(toLightLocation, 1, toLight, 0);
    }

    public static void enableLighting(){
        GLES20.glUniform1i(useLightingLocation, 1);
        lighting = true;
    }

    public static void disableLighting(){
        GLES20.glUniform1i(useLightingLocation, 0);
        lighting = false;
    }

    public static void setupGuiRendering(){
        setPerspective(false);
        disableLighting();
        updateView(width/2, height/2, 1, -1, 1, 0, 0);//0,0 is now top left
        GLES20.glDisable(GLES20.GL_CULL_FACE);//everything will be facing the other way since Y scale is -1
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
    }

    public static int getVpMatrixLocation() {
        return vpMatrixLocation;
    }

    public static int getMMatrixLocation() {
        return mMatrixLocation;
    }

    public static int getnMatrixLocation() {
        return nMatrixLocation;
    }

    public static int getNormalLocation() {
        return normalLocation;
    }

    public static int getPositionLocation() {
        return positionLocation;
    }

    public static int getUvLocation() {
        return uvLocation;
    }

    public static int getColourLocation() {
        return colourLocation;
    }

    public static int getVertexColourLocation() {
        return vertexColorLocation;
    }

    public static int getTextureLocation() {
        return textureLocation;
    }

    public static int getUseTextureLocation() {
        return useTextureLocation;
    }

    public static int getToLightLocation() {
        return toLightLocation;
    }

    public static int getUseLightingLocation() {
        return useLightingLocation;
    }

    public static float getWidth(){
        return width;
    }

    public static float getHeight(){
        return height;
    }

}
