package org.valkyrienskies.mod.client.render;

import java.util.HashMap;
import java.util.Map;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraft.init.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

// TODO: Upon further inspection this class does the exact opposite of what its name implies 
// and takes a stupid slow approach to rendering simple geometries. Remove this and create a 
// vertex buffer based solution soon!
public class FastBlockModelRenderer {

    public static final Map<IBlockState, BufferBuilder.State> blockstateToVertexData = new HashMap<IBlockState, BufferBuilder.State>();
    // Maps IBlockState to a map that maps brightness to VertexBuffer that are already uploaded to gpu memory.
    public static final Map<IBlockState, Map<Integer, VertexBuffer>> blockstateBrightnessToVertexBuffer = new HashMap<IBlockState, Map<Integer, VertexBuffer>>();

    // Be careful to only use this on the main thread. Using it on other threads will break stuff.
    public static final BufferBuilder VERTEX_BUILDER = new BufferBuilder(500000);
    // Used to make sure that when we simulate rendering models they're not affected by light from other blocks.
    private static final BlockPos offsetPos = new BlockPos(0, 512, 0);

    public static void renderBlockModel(Tessellator tessellator, World world,
        IBlockState blockstateToRender, int brightness) {
        GL11.glPushMatrix();
        GL11.glTranslated(-offsetPos.getX(), -offsetPos.getY(), -offsetPos.getZ());
        renderBlockModelHighQualityHighRam(tessellator, world, blockstateToRender, brightness);
        GL11.glPopMatrix();
    }

    private static void renderBlockModelHighQualityHighRam(Tessellator tessellator, World world,
        IBlockState blockstateToRender, int brightness) {
        if (!blockstateToVertexData.containsKey(blockstateToRender)) {
            generateRenderDataFor(world, blockstateToRender);
        }

        // We're using the VBO, check if a compiled VertexBuffer already exists. If
        // there isn't one we will create it, then render.
        if (!blockstateBrightnessToVertexBuffer.containsKey(blockstateToRender)) {
            blockstateBrightnessToVertexBuffer
                .put(blockstateToRender, new HashMap<Integer, VertexBuffer>());
        }
        if (!blockstateBrightnessToVertexBuffer.get(blockstateToRender).containsKey(brightness)) {
            // We have to create the VertexBuffer
            BufferBuilder.State bufferBuilderState = blockstateToVertexData.get(blockstateToRender);

            VERTEX_BUILDER.setTranslation(0, 0, 0);
            VERTEX_BUILDER.begin(7, DefaultVertexFormats.BLOCK);
            VERTEX_BUILDER.setVertexState(bufferBuilderState);

            // This code adjusts the brightness of the model rendered.
            int j = VERTEX_BUILDER.vertexFormat.getSize() >> 2;
            int cont = VERTEX_BUILDER.getVertexCount();
            int offsetUV = VERTEX_BUILDER.vertexFormat.getUvOffsetById(1) / 4;
            int bufferNextSize = VERTEX_BUILDER.vertexFormat.getIntegerSize();

            for (int contont = 0; contont < cont; contont += 4) {
                try {
                    int i = (contont) * bufferNextSize + offsetUV;
                    VERTEX_BUILDER.rawIntBuffer.put(i, brightness);
                    VERTEX_BUILDER.rawIntBuffer.put(i + j, brightness);
                    VERTEX_BUILDER.rawIntBuffer.put(i + j * 2, brightness);
                    VERTEX_BUILDER.rawIntBuffer.put(i + j * 3, brightness);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            VertexBuffer blockVertexBuffer = new VertexBuffer(DefaultVertexFormats.BLOCK);
            // Now that the VERTEX_BUILDER has been filled with all the render data, we must
            // upload it to the gpu.
            // The VERTEX_UPLOADER copies the state of the VERTEX_BUILDER to
            // blockVertexBuffer, and then uploads it to the gpu.
            VERTEX_BUILDER.finishDrawing();
            VERTEX_BUILDER.reset();
            blockVertexBuffer.bufferData(VERTEX_BUILDER.getByteBuffer());
            // Put the VertexBuffer for that data into the Map for future rendering.
            blockstateBrightnessToVertexBuffer.get(blockstateToRender)
                .put(brightness, blockVertexBuffer);
        }

        // Just to test the look of the State in case I ever need to.
        if (false) {
            BufferBuilder.State bufferBuilderState = blockstateToVertexData.get(blockstateToRender);
            tessellator.getBuffer().begin(7, DefaultVertexFormats.BLOCK);
            tessellator.getBuffer().setVertexState(bufferBuilderState);
            tessellator.getBuffer().finishDrawing();
            tessellator.draw();
        }

        renderVertexBuffer(
            blockstateBrightnessToVertexBuffer.get(blockstateToRender).get(brightness));
    }

    public static void renderVertexBuffer(VertexBuffer vertexBuffer) {
        // Check if optifine shaders are currently loaded.
        final boolean areOptifineShadersEnabled = GibsModelRegistry.isOptifineShadersEnabled();

        GlStateManager.pushMatrix();
        GlStateManager.resetColor();

        GlStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
        OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit);
        GlStateManager.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
        OpenGlHelper.setClientActiveTexture(OpenGlHelper.lightmapTexUnit);
        GlStateManager.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
        OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit);
        GlStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);

        // Extra OpenGL states that must be enabled when shaders are enabled.
        if (areOptifineShadersEnabled) {
            GL11.glEnableClientState(32885);
            GL20.glEnableVertexAttribArray(11);
            GL20.glEnableVertexAttribArray(12);
            GL20.glEnableVertexAttribArray(10);
        }

        GlStateManager.pushMatrix();
        vertexBuffer.bindBuffer();

        // Even more OpenGL states that must be enabled when shaders are enabled.
        if (areOptifineShadersEnabled) {
            int vertexSizeI = 14;
            GL11.glVertexPointer(3, 5126, 56, 0L);
            GL11.glColorPointer(4, 5121, 56, 12L);
            GL11.glTexCoordPointer(2, 5126, 56, 16L);
            OpenGlHelper.setClientActiveTexture(OpenGlHelper.lightmapTexUnit);
            GL11.glTexCoordPointer(2, 5122, 56, 24L);
            OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit);
            GL11.glNormalPointer(5120, 56, 28L);
            GL20.glVertexAttribPointer(11, 2, 5126, false, 56, 32L);
            GL20.glVertexAttribPointer(12, 4, 5122, false, 56, 40L);
            GL20.glVertexAttribPointer(10, 3, 5122, false, 56, 48L);
        } else {

            GlStateManager.glVertexPointer(3, 5126, 28, 0);
            GlStateManager.glColorPointer(4, 5121, 28, 12);
            GlStateManager.glTexCoordPointer(2, 5126, 28, 16);
            OpenGlHelper.setClientActiveTexture(OpenGlHelper.lightmapTexUnit);
            GlStateManager.glTexCoordPointer(2, 5122, 28, 24);
            OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit);
        }

        vertexBuffer.drawArrays(7);
        GlStateManager.popMatrix();
        vertexBuffer.unbindBuffer();
        GlStateManager.resetColor();

        for (VertexFormatElement vertexformatelement : DefaultVertexFormats.BLOCK.getElements()) {
            VertexFormatElement.EnumUsage vertexformatelement$enumusage = vertexformatelement
                .getUsage();
            int i = vertexformatelement.getIndex();

            switch (vertexformatelement$enumusage) {
                case POSITION:
                    GlStateManager.glDisableClientState(32884);
                    break;
                case UV:
                    OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit + i);
                    GlStateManager.glDisableClientState(32888);
                    OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit);
                    break;
                case COLOR:
                    GlStateManager.glDisableClientState(32886);
                    GlStateManager.resetColor();
            }
        }

        OpenGlHelper.glBindBuffer(OpenGlHelper.GL_ARRAY_BUFFER, 0);

        // Finally disable some of those extra OpenGL states that were be enabled due to shaders.
        if (areOptifineShadersEnabled) {
            GL11.glDisableClientState(32885);
            GL20.glDisableVertexAttribArray(11);
            GL20.glDisableVertexAttribArray(12);
            GL20.glDisableVertexAttribArray(10);
        }

        GlStateManager.resetColor();
        GlStateManager.popMatrix();
    }

    private static void renderBlockModelHighQuality(Tessellator tessellator, World world,
        IBlockState blockstateToRender, int brightness) {
        BufferBuilder.State vertexData = blockstateToVertexData.get(blockstateToRender);

        if (vertexData == null) {
            generateRenderDataFor(world, blockstateToRender);
            vertexData = blockstateToVertexData.get(blockstateToRender);
        }
        renderVertexState(vertexData, tessellator, brightness);
    }

    private static void renderVertexState(BufferBuilder.State data, Tessellator tessellator,
        int brightness) {
        GL11.glPushMatrix();
        tessellator.getBuffer().begin(7, DefaultVertexFormats.BLOCK);

        tessellator.getBuffer().setVertexState(data);
        int j = tessellator.getBuffer().vertexFormat.getSize() >> 2;
        int cont = tessellator.getBuffer().getVertexCount();
        int offsetUV = tessellator.getBuffer().vertexFormat.getUvOffsetById(1) / 4;
        int bufferNextSize = tessellator.getBuffer().vertexFormat.getIntegerSize();

        for (int contont = 0; contont < cont; contont += 4) {
            try {
                int i = (contont) * bufferNextSize + offsetUV;

                tessellator.getBuffer().rawIntBuffer.put(i, brightness);
                tessellator.getBuffer().rawIntBuffer.put(i + j, brightness);
                tessellator.getBuffer().rawIntBuffer.put(i + j * 2, brightness);
                tessellator.getBuffer().rawIntBuffer.put(i + j * 3, brightness);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        tessellator.draw();

        GL11.glPopMatrix();
    }

    private static void generateRenderDataFor(World world, IBlockState state) {
        VERTEX_BUILDER.begin(7, DefaultVertexFormats.BLOCK);
        BlockRendererDispatcher blockrendererdispatcher = Minecraft.getMinecraft()
            .getBlockRendererDispatcher();
        IBakedModel modelFromState = blockrendererdispatcher.getModelForState(state);
        blockrendererdispatcher.getBlockModelRenderer()
            .renderModel(Minecraft.getMinecraft().world, modelFromState,
                Blocks.AIR.getDefaultState(), offsetPos, VERTEX_BUILDER, false, 0);
        BufferBuilder.State toReturn = VERTEX_BUILDER.getVertexState();
        VERTEX_BUILDER.finishDrawing();
        VERTEX_BUILDER.reset();
        blockstateToVertexData.put(state, toReturn);
    }

}