package gregtech.common.misc;

import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.util.MathHelper;

public class GT_ClientPollutionMap {

    private static final byte RADIUS = 24;
    private static final byte DISTANCE_RELOAD_MAP = 5; // When player moved x chunks, shift the map to new center.
    private static final byte SIZE = RADIUS * 2 + 1; // Area to keep stored.

    private int x0, z0;
    private int dim;

    private boolean initialized = false;

    private static short[][] chunkMatrix; // short because reasons.

    public GT_ClientPollutionMap() {}

    public void reset() {
        initialized = false;
    }

    private void initialize(int playerChunkX, int playerChunkZ, int dimension) {
        initialized = true;
        chunkMatrix = new short[SIZE][SIZE];
        x0 = playerChunkX;
        z0 = playerChunkZ;
        dim = dimension;
    }

    public void addChunkPollution(int chunkX, int chunkZ, int pollution) {
        EntityClientPlayerMP player = Minecraft.getMinecraft().thePlayer;
        if (player == null || player.worldObj == null) return;

        int playerXChunk = MathHelper.floor_double(player.posX) >> 4;
        int playerZChunk = MathHelper.floor_double(player.posZ) >> 4; // posX/Z seems to be always loaded,

        if (!initialized) {
            initialize(playerXChunk, playerZChunk, player.dimension);
        }

        if (dim != player.dimension) {
            initialize(playerXChunk, playerZChunk, player.dimension);
        }

        if (Math.abs(x0 - playerXChunk) > DISTANCE_RELOAD_MAP || Math.abs(z0 - playerZChunk) > DISTANCE_RELOAD_MAP)
            shiftCenter(playerXChunk, playerZChunk);

        int relX = chunkX - x0 + RADIUS;
        if (relX >= SIZE || relX < 0) // out of bounds
            return;
        int relZ = chunkZ - z0 + RADIUS;
        if (relZ >= SIZE || relZ < 0) // out of bounds
            return;

        pollution = pollution / 225;
        if (pollution > Short.MAX_VALUE) // Sanity
            chunkMatrix[relX][relZ] = Short.MAX_VALUE; // Max pollution = 7,3mill
        else if (pollution < 0) chunkMatrix[relX][relZ] = 0;
        else chunkMatrix[relX][relZ] = (short) (pollution);
    }

    // xy interpolation, between 4 chunks as corners, unknown treated as 0.
    public int getPollution(double fx, double fz) {
        if (!initialized) return 0;
        int x = MathHelper.floor_double(fx);
        int z = MathHelper.floor_double(fz);
        int xDiff = ((x - 8) >> 4) - x0;
        int zDiff = ((z - 8) >> 4) - z0;

        if (xDiff < -RADIUS || zDiff < -RADIUS || xDiff >= RADIUS || zDiff >= RADIUS) return 0;

        // coordinates in shifted chunk.
        x = (x - 8) % 16;
        z = (z - 8) % 16;
        if (x < 0) x = 16 + x;
        if (z < 0) z = 16 + z;

        int xi = 15 - x;
        int zi = 15 - z;

        // read pollution in 4 corner chunks
        int offsetX = RADIUS + xDiff;
        int offsetZ = RADIUS + zDiff;

        int c00 = chunkMatrix[offsetX][offsetZ];
        int c10 = chunkMatrix[offsetX + 1][offsetZ];
        int c01 = chunkMatrix[offsetX][offsetZ + 1];
        int c11 = chunkMatrix[offsetX + 1][offsetZ + 1];

        // Is divided by 15*15 but is handled when storing chunk data.
        return c00 * xi * zi + c10 * x * zi + c01 * xi * z + c11 * x * z;
    }

    // shift the matrix to fit new center
    private void shiftCenter(int chunkX, int chunkZ) {
        int xDiff = chunkX - x0;
        int zDiff = chunkZ - z0;
        boolean[] allEmpty = new boolean[SIZE]; // skip check z row if its empty.
        if (xDiff > 0) for (byte x = 0; x < SIZE; x++) {
            int xOff = x + xDiff;
            if (xOff < SIZE) {
                chunkMatrix[x] = chunkMatrix[xOff].clone();
            } else {
                chunkMatrix[x] = new short[SIZE];
                allEmpty[x] = true;
            }
        }
        else if (xDiff < 0) for (byte x = SIZE - 1; x >= 0; x--) {
            int xOff = x + xDiff;
            if (xOff > 0) {
                chunkMatrix[x] = chunkMatrix[xOff].clone();
            } else {
                chunkMatrix[x] = new short[SIZE];
                allEmpty[x] = true;
            }
        }

        if (zDiff > 0) for (byte x = 0; x < SIZE; x++) {
            if (allEmpty[x]) continue;
            for (int z = 0; z < SIZE; z++) {
                int zOff = z + zDiff;
                chunkMatrix[x][z] = (zOff < SIZE) ? chunkMatrix[x][zOff] : 0;
            }
        }
        else if (zDiff < 0) for (byte x = 0; x < SIZE; x++) {
            if (allEmpty[x]) continue;
            for (int z = SIZE - 1; z >= 0; z--) {
                int zOff = z + zDiff;
                chunkMatrix[x][z] = (zOff > 0) ? chunkMatrix[x][zOff] : 0;
            }
        }

        x0 = chunkX;
        z0 = chunkZ;
    }
}