package com.gmail.nossr50.util.blockmeta;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class PrimitiveExChunkletStore implements ChunkletStore, Externalizable {
    private static final long serialVersionUID = 8603603827094383873L;

    /** X, Z, Y */
    public boolean[][][] store = new boolean[16][16][64];

    @Override
    public boolean isTrue(int x, int y, int z) {
        return store[x][z][y];
    }

    @Override
    public void setTrue(int x, int y, int z) {
        store[x][z][y] = true;
    }

    @Override
    public void setFalse(int x, int y, int z) {
        store[x][z][y] = false;
    }

    @Override
    public boolean isEmpty() {
        for (int x = 0; x < 16; x++) {
            for (int z = 0; z < 16; z++) {
                for (int y = 0; y < 64; y++) {
                    if (store[x][z][y]) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    @Override
    public void copyFrom(ChunkletStore otherStore) {
        for (int x = 0; x < 16; x++) {
            for (int z = 0; z < 16; z++) {
                for (int y = 0; y < 64; y++) {
                    store[x][z][y] = otherStore.isTrue(x, y, z);
                }
            }
        }
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        byte[] buffer = new byte[2304]; // 2304 is 16*16*9
        int bufferIndex = 0;

        for (int x = 0; x < 16; x++) {
            for (int z = 0; z < 16; z++) {
                for (int y = 0; y < 64; y++) {
                    if (store[x][z][y]) {
                        byte[] temp = constructColumn(x, z);

                        for (int i = 0; i < 9; i++) {
                            buffer[bufferIndex] = temp[i];
                            bufferIndex++;
                        }

                        break;
                    }
                }
            }
        }

        out.write(buffer, 0, bufferIndex);
        out.flush();
    }

    // For this we assume that store has been initialized to be all false by now
    @Override
    public void readExternal(ObjectInput in) throws IOException {
        byte[] temp = new byte[9];

        // Could probably reorganize this loop to print nasty things if it does not equal 9 or -1
        while (in.read(temp, 0, 9) == 9) {
            int x = addressByteX(temp[0]);
            int z = addressByteZ(temp[0]);
            boolean[] yColumn = new boolean[64];

            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    yColumn[j + (i * 8)] = (temp[i + 1] & (1 << j)) != 0;
                }
            }

            store[x][z] = yColumn;
        }
    }

    /*
     * The column: An array of 9 bytes which represent all y values for a given (x,z) Chunklet-coordinate
     *
     * The first byte is an address byte, this provides the x and z values.
     * The next 8 bytes are all y values from 0 to 63, with each byte containing 8 bits of true/false data
     *
     * Each of these 8 bytes address to a y value from right to left
     *
     * Examples:
     * 00000001 represents that the lowest y value in this byte is true, all others are off
     * 10000000 represents that the highest y value in this byte is true, all others are off
     * 10000001 represents that the lowest and highest y values in this byte are true, all others are off
     *
     * Full columns:
     * See comment on Address byte for information on how to use that byte
     *
     * Example:
     * ADDRESS_BYTE 10000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000
     *  - x, z from ADDRESS_BYTE
     *  - The next byte contains data from 0 to 7
     *    - 1 is set in the highest bit position, this is 7 in y coordinate
     *  - The next byte contains data from 8 to 15
     *    - 1 is set in the lowest bit position, this is 8 in the y coordinate
     *  Therefore, for this column: There are true values at (x, 7, z) and (x, 8, z)
     */
    private byte[] constructColumn(int x, int z) {
        byte[] column = new byte[9];
        int index = 1;

        column[0] = makeAddressByte(x, z);

        for (int i = 0; i < 8; i++) {
            byte yCompressed = 0x0;
            int subColumnIndex = 8 * i;
            int subColumnEnd = subColumnIndex + 8;

            for (int y = subColumnIndex; y < subColumnEnd; y++) {
                if (store[x][z][y]) {
                    yCompressed |= 1 << (y % 8);
                }
            }

            column[index] = yCompressed;
            index++;
        }

        return column;
    }

    /*
     * The address byte: A single byte which contains x and z values which correspond to the x and z Chunklet-coordinates
     *
     * In Chunklet-coordinates, the only valid values are 0-15, so we can fit both into a single byte.
     *
     * The top 4 bits of the address byte are for the x value
     * The bottom 4 bits of the address byte are for the z value
     *
     * Examples:
     * An address byte with a value 00000001 would be split like so:
     *  - x = 0000 = 0
     *  - z = 0001 = 1
     *  => Chunklet coordinates (0, 1)
     *
     * 01011111
     *  - x = 0101 = 5
     *  - z = 1111 = 15
     *  => Chunklet coordinates (5, 15)
     */
    protected static byte makeAddressByte(int x, int z) {
        return (byte) ((x << 4) + z);
    }

    protected static int addressByteX(byte address) {
        return (address & 0xF0) >>> 4;
    }

    protected static int addressByteZ(byte address) {
        return address & 0x0F;
    }
}
