package gregtech.common.render;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.potion.Potion;
import net.minecraft.util.MathHelper;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.World;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.event.world.WorldEvent;

import org.lwjgl.opengl.GL11;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import gregtech.GT_Mod;
import gregtech.common.entities.GT_EntityFXPollution;
import gregtech.common.misc.GT_ClientPollutionMap;

@SideOnly(Side.CLIENT)
public class GT_PollutionRenderer {

    private static GT_ClientPollutionMap pollutionMap;
    private static int playerPollution = 0;

    private static final boolean DEBUG = false;

    // PARTICLES_POLLUTION_START + PARTICLES_POLLUTION_END -> Max Particles
    private static final int PARTICLES_MAX_NUM = 100;
    private static final int PARTICLES_POLLUTION_START = 400000;
    private static final int PARTICLES_POLLUTION_END = 3500000;

    private static final int FOG_START_AT_POLLUTION = 400000;
    private static final int FOG_MAX_AT_POLLUTION = 7000000;
    // jump from linear to exponential fog. x*FOG_MAX_AT_POLLUTION+FOG_START_AT_POLLUTION
    private static final double FOG_START_EXP_RATIO = 0.02D;

    private static final float[] fogColor = { 0.3f, 0.25f, 0.1f };
    private static final short[] grassColor = { 230, 180, 40 };
    private static final short[] leavesColor = { 160, 80, 15 };
    private static final short[] liquidColor = { 160, 200, 10 };
    private static final short[] foliageColor = { 160, 80, 15 };

    // TODO need to soft update some blocks, grass and leaves does more often than liquid it looks like.

    public GT_PollutionRenderer() {
        pollutionMap = new GT_ClientPollutionMap();
    }

    public void preLoad() {
        net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(this);
        FMLCommonHandler.instance()
            .bus()
            .register(this);
    }

    public void processPacket(ChunkCoordIntPair chunk, int pollution) {
        pollutionMap.addChunkPollution(chunk.chunkXPos, chunk.chunkZPos, pollution);
    }

    @SubscribeEvent(priority = EventPriority.HIGH)
    public void enteredWorld(WorldEvent.Load event) {
        EntityClientPlayerMP p = Minecraft.getMinecraft().thePlayer;
        if (!event.world.isRemote || p == null) return;
        pollutionMap.reset();
    }

    private static int color(int color, int pollution, int low, float high, short[] colors) {
        if (pollution < low) return color;

        int r = (color >> 16) & 0xFF;
        int g = (color >> 8) & 0xFF;
        int b = color & 0xFF;
        float p = (pollution - low) / high;
        if (p > 1) p = 1;
        float pi = 1 - p;

        r = ((int) (r * pi + p * colors[0])) & 0xFF;
        g = ((int) (g * pi + p * colors[1])) & 0xFF;
        b = ((int) (b * pi + p * colors[2])) & 0xFF;

        return (r & 0xFF) << 16 | (g & 0xFF) << 8 | b & 0xFF;
    }

    // Methods for hodgepodge to color grass / foliage blocks etc.
    public static int colorGrass(int oColor, int x, int z) {
        return color(oColor, pollutionMap.getPollution(x, z) / 1000, 350, 600, grassColor);
    }

    public static int colorLeaves(int oColor, int x, int z) {
        return color(oColor, pollutionMap.getPollution(x, z) / 1000, 300, 500, leavesColor);
    }

    public static int colorLiquid(int oColor, int x, int z) {
        return color(oColor, pollutionMap.getPollution(x, z) / 1000, 300, 500, liquidColor);
    }

    public static int colorFoliage(int oColor, int x, int z) {
        return color(oColor, pollutionMap.getPollution(x, z) / 1000, 300, 500, foliageColor);
    }

    public static int getKnownPollution(int x, int z) {
        return pollutionMap.getPollution(x, z);
    }

    @SubscribeEvent(priority = EventPriority.LOW)
    public void manipulateColor(EntityViewRenderEvent.FogColors event) {
        if (!DEBUG && Minecraft.getMinecraft().thePlayer.capabilities.isCreativeMode) return;

        if (event.block.getMaterial() == Material.water || event.block.getMaterial() == Material.lava) return;

        float x = fogIntensityLastTick > 1 ? 1F : (float) fogIntensityLastTick;
        float xi = 1 - x;

        event.red = xi * event.red + x * fogColor[0];
        event.green = xi * event.green + x * fogColor[1];
        event.blue = xi * event.blue + x * fogColor[2];
    }

    private static final int END_MAX_DISTANCE = 192 - 1;
    private static double fogIntensityLastTick = 0;

    @SubscribeEvent(priority = EventPriority.LOWEST)
    public void renderGTPollutionFog(EntityViewRenderEvent.RenderFogEvent event) {
        if (!GT_Mod.gregtechproxy.mRenderPollutionFog) return;

        if ((!DEBUG && Minecraft.getMinecraft().thePlayer.capabilities.isCreativeMode)
            || (fogIntensityLastTick < FOG_START_EXP_RATIO)) return;

        if (event.fogMode == 0) {
            double v = 1 - fogIntensityLastTick / FOG_START_EXP_RATIO;
            // trying to smooth out jump from linear to exponential
            GL11.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR);
            GL11.glFogf(GL11.GL_FOG_START, (float) ((END_MAX_DISTANCE - 20) * 0.75F * v + 20));
            GL11.glFogf(GL11.GL_FOG_END, (float) (END_MAX_DISTANCE * (0.75F + v * 0.25F)));
        }
        // else if ( event.fogMode < 0) { }
    }

    @SubscribeEvent(priority = EventPriority.LOWEST)
    public void renderGTPollutionFog(EntityViewRenderEvent.FogDensity event) {
        if (!GT_Mod.gregtechproxy.mRenderPollutionFog) return;

        if (!DEBUG && Minecraft.getMinecraft().thePlayer.capabilities.isCreativeMode) return;

        if (event.entity.isPotionActive(Potion.blindness) || (fogIntensityLastTick < FOG_START_EXP_RATIO)
            || event.block.getMaterial() == Material.water
            || event.block.getMaterial() == Material.lava) return;

        GL11.glFogi(GL11.GL_FOG_MODE, GL11.GL_EXP2);
        event.density = (float) Math.pow(fogIntensityLastTick - FOG_START_EXP_RATIO, .75F) / 5 + 0.01F;
        event.setCanceled(true);
    }

    private double lastUpdate = 0;

    @SubscribeEvent(priority = EventPriority.HIGHEST)
    public void onRenderTick(TickEvent.RenderTickEvent event) {
        Minecraft mc = Minecraft.getMinecraft();
        if (mc == null) return;
        EntityClientPlayerMP player = mc.thePlayer;
        if (player == null) return;

        if (event.phase == TickEvent.Phase.START) {
            if (event.renderTickTime < lastUpdate) lastUpdate = lastUpdate - 1;
            float step = (float) ((event.renderTickTime - lastUpdate) / 50);
            lastUpdate = event.renderTickTime;

            float fogIntensity = (playerPollution - FOG_START_AT_POLLUTION) / (float) FOG_MAX_AT_POLLUTION;
            if (fogIntensity > 1) fogIntensity = 1;
            else if (fogIntensity < 0) fogIntensity = 0;

            double e = fogIntensity - fogIntensityLastTick;
            if (e != 0) {
                if (e > 0.2) e = 0.2D;
                else if (e < -0.5) e = -0.5D;

                if (e > 0.001D || e < -0.001D) fogIntensityLastTick += step * e;
                else fogIntensityLastTick = fogIntensity;
            }
        } else if (DEBUG) {
            drawPollution("Intensity: " + (fogIntensityLastTick * 10000), 0);
            drawPollution(
                "Pollution: " + pollutionMap.getPollution(
                    Minecraft.getMinecraft().thePlayer.lastTickPosX,
                    Minecraft.getMinecraft().thePlayer.lastTickPosZ),
                20);
            drawPollution(
                "Density:   "
                    + ((float) (Math.pow(fogIntensityLastTick - FOG_START_EXP_RATIO, .75F) / 5 + 0.01F) * 10000),
                40);
        }
    }

    // Adding dirt particles in the air
    @SubscribeEvent(priority = EventPriority.HIGHEST)
    public void onClientTick(TickEvent.ClientTickEvent event) {
        if (!GT_Mod.gregtechproxy.mRenderDirtParticles) return;
        Minecraft mc = Minecraft.getMinecraft();
        if (mc == null) return;
        EntityClientPlayerMP player = mc.thePlayer;
        if (player == null || (player.capabilities.isCreativeMode && !DEBUG)) return;

        World w = player.worldObj;
        playerPollution = pollutionMap.getPollution(player.lastTickPosX, player.lastTickPosZ);

        float intensity = ((float) playerPollution - PARTICLES_POLLUTION_START) / PARTICLES_POLLUTION_END;
        if (intensity < 0) return;
        else if (intensity > 1) intensity = 1;
        else intensity *= intensity;

        int x = MathHelper.floor_double(player.posX);
        int y = MathHelper.floor_double(player.posY);
        int z = MathHelper.floor_double(player.posZ);

        int numParticles = Math.round(intensity * PARTICLES_MAX_NUM);

        for (int l = 0; l < numParticles; ++l) {
            int i1 = x + w.rand.nextInt(16) - w.rand.nextInt(16);
            int j1 = y + w.rand.nextInt(16) - w.rand.nextInt(16);
            int k1 = z + w.rand.nextInt(16) - w.rand.nextInt(16);
            Block block = w.getBlock(i1, j1, k1);

            if (block.getMaterial() == Material.air) {
                EntityFX fx = new GT_EntityFXPollution(
                    w,
                    (float) i1 + w.rand.nextFloat(),
                    (float) j1 + w.rand.nextFloat(),
                    (float) k1 + w.rand.nextFloat());
                mc.effectRenderer.addEffect(fx);
            }
        }
    }

    private void drawPollution(String text, int off) {
        GL11.glPushMatrix();
        GL11.glEnable(GL11.GL_BLEND);
        OpenGlHelper.glBlendFunc(770, 771, 1, 0);
        Minecraft.getMinecraft().fontRenderer.drawStringWithShadow(text, 0, off, 0xFFFFFFFF);
        GL11.glDisable(GL11.GL_BLEND);
        GL11.glPopMatrix();
    }
}