package core.engine.renderer;

import core.engine.Engine;
import core.engine.entity.Camera;
import core.engine.entity.Entity;
import core.engine.entity.Light;
import core.engine.model.TexturedModel;
import core.engine.shader.StaticShader;
import core.engine.shader.TerrainShader;
import core.engine.terrain.Terrain;
import core.engine.terrain.TerrainTile;
import org.lwjgl.opengl.GL11;
import utils.vectors.Matrix4f;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.lwjgl.opengl.GL11C.*;

public class MasterRenderer {
    private static final float FOV = 70;
    private static final float NEAR_PLEANE = 0.1f;
    private static final float FAR_PLANE = 1000;
    private StaticShader shader = new StaticShader();
    private TerrainShader terrainShader = new TerrainShader();
    private EntityRenderer renderer;
    private TerrainRenderer terrainRenderer;
    private Map<TexturedModel, List<Entity>> entities = new HashMap<>();
    private List<TerrainTile> terrains = new ArrayList<>();
    private Matrix4f projectionMatrix;

    private boolean wireframe = false;

    public MasterRenderer() {
        GL11.glEnable(GL11.GL_CULL_FACE);
        GL11.glCullFace(GL11.GL_BACK);
        createProjectionMatrix();
        renderer = new EntityRenderer(shader, projectionMatrix);
        terrainRenderer = new TerrainRenderer(terrainShader, projectionMatrix);

    }

    public void prepare() {
        GL11.glEnable(GL11.GL_DEPTH_TEST);
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
        GL11.glClearColor(0.49f, 89f, 0.98f, 1);
    }

    public void render(Light light, Camera camera) {
        prepare();
        shader.start();
        shader.loadLight(light);
        shader.loadViewMatrix(camera);
        renderer.render(entities);
        shader.stop();
        terrainShader.start();
        terrainShader.loadLight(light);
        terrainShader.loadViewMatrix(camera);
        terrainRenderer.render(terrains);
        terrainShader.stop();
        terrains.clear();
        entities.clear();
    }

    public void processTerrain(Terrain terrain) {
        terrains.addAll(terrain.getTerrainTiles());
    }

    public void processEntity(Entity entity) {
        TexturedModel entityModel = entity.getModel();
        List<Entity> batch = entities.get(entityModel);
        if(batch != null) {
            batch.add(entity);
        } else {
            List<Entity> newBatch = new ArrayList<>();
            newBatch.add(entity);
            entities.put(entityModel, newBatch);
        }
    }

    public void cleanUp() {
        shader.cleanUp();
        terrainShader.cleanUp();
    }

    private void createProjectionMatrix() {
        float aspectRatio = (float) Engine.WINDOW_WIDTH / (float) Engine.WINDOW_HEIGHT;
        float y_scale = (float) ((1f / Math.tan(Math.toRadians(FOV / 2f))) * aspectRatio);
        float x_scale = y_scale / aspectRatio;
        float frustum_length = FAR_PLANE - NEAR_PLEANE;

        projectionMatrix = new Matrix4f();
        projectionMatrix.m00 = x_scale;
        projectionMatrix.m11 = y_scale;
        projectionMatrix.m22 = -((FAR_PLANE + NEAR_PLEANE) / frustum_length);
        projectionMatrix.m23 = -1;
        projectionMatrix.m32 = -((2 * NEAR_PLEANE * FAR_PLANE) / frustum_length);
        projectionMatrix.m33 = 0;

    }

    public void switchWireframe() {
        this.wireframe = !this.wireframe;

        if(this.wireframe) {
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        } else {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        }
    }

    public Matrix4f getProjectionMatrix() {
        return projectionMatrix;
    }
}
