// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.universe.world.chunk.section;

import io.netty.buffer.Unpooled;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.util.ChunkUtil;
import io.netty.buffer.ByteBuf;

public class ChunkLightData
{
    public static final ChunkLightData EMPTY;
    public static final int TREE_SIZE = 8;
    public static final int TREE_MASK = 7;
    public static final int DEPTH_MAGIC = 12;
    public static final int SIZE_MAGIC = 17;
    public static final int INITIAL_CAPACITY = 128;
    public static final byte MAX_VALUE = 15;
    public static final int CHANNEL_COUNT = 4;
    public static final int BITS_PER_CHANNEL = 4;
    public static final int CHANNEL_MASK = 15;
    public static final int RED_CHANNEL = 0;
    public static final int GREEN_CHANNEL = 1;
    public static final int BLUE_CHANNEL = 2;
    public static final int SKY_CHANNEL = 3;
    public static final int RED_CHANNEL_BIT = 0;
    public static final int GREEN_CHANNEL_BIT = 4;
    public static final int BLUE_CHANNEL_BIT = 8;
    public static final int SKY_CHANNEL_BIT = 12;
    public static final int RGB_MASK = -61441;
    protected final short changeId;
    ByteBuf light;
    
    public ChunkLightData(final ByteBuf light, final short changeId) {
        this.light = light;
        this.changeId = changeId;
    }
    
    public short getChangeId() {
        return this.changeId;
    }
    
    public byte getRedBlockLight(final int x, final int y, final int z) {
        return this.getRedBlockLight(ChunkUtil.indexBlock(x, y, z));
    }
    
    public byte getRedBlockLight(final int index) {
        if (this.light == null) {
            return 0;
        }
        return this.getLight(index, 0);
    }
    
    public byte getGreenBlockLight(final int x, final int y, final int z) {
        return this.getGreenBlockLight(ChunkUtil.indexBlock(x, y, z));
    }
    
    public byte getGreenBlockLight(final int index) {
        if (this.light == null) {
            return 0;
        }
        return this.getLight(index, 1);
    }
    
    public byte getBlueBlockLight(final int x, final int y, final int z) {
        return this.getBlueBlockLight(ChunkUtil.indexBlock(x, y, z));
    }
    
    public byte getBlueBlockLight(final int index) {
        if (this.light == null) {
            return 0;
        }
        return this.getLight(index, 2);
    }
    
    public byte getBlockLightIntensity(final int x, final int y, final int z) {
        return this.getBlockLightIntensity(ChunkUtil.indexBlock(x, y, z));
    }
    
    public byte getBlockLightIntensity(final int index) {
        if (this.light == null) {
            return 0;
        }
        final byte r = this.getLight(index, 0);
        final byte g = this.getLight(index, 1);
        final byte b = this.getLight(index, 2);
        return (byte)(MathUtil.maxValue(b, g, r) & 0xF);
    }
    
    public short getBlockLight(final int x, final int y, final int z) {
        return this.getBlockLight(ChunkUtil.indexBlock(x, y, z));
    }
    
    public short getBlockLight(final int index) {
        if (this.light == null) {
            return 0;
        }
        return (short)(this.getLightRaw(index) & 0xFFFF0FFF);
    }
    
    public byte getSkyLight(final int x, final int y, final int z) {
        return this.getSkyLight(ChunkUtil.indexBlock(x, y, z));
    }
    
    public byte getSkyLight(final int index) {
        if (this.light == null) {
            return 0;
        }
        return this.getLight(index, 3);
    }
    
    public byte getLight(final int index, final int channel) {
        if (channel < 0 || channel >= 4) {
            throw new IllegalArgumentException();
        }
        if (this.light == null) {
            return 0;
        }
        final short value = this.getLightRaw(index);
        return (byte)(value >> channel * 4 & 0xF);
    }
    
    public short getLightRaw(final int x, final int y, final int z) {
        return this.getLightRaw(ChunkUtil.indexBlock(x, y, z));
    }
    
    public short getLightRaw(final int index) {
        if (this.light == null) {
            return 0;
        }
        if (index < 0 || index >= 32768) {
            throw new IllegalArgumentException("Index " + index + " is outside of the bounds!");
        }
        return getTraverse(this.light, index, 0, 0);
    }
    
    protected static short getTraverse(@Nonnull final ByteBuf local, final int index, final int pointer, final int depth) {
        int loc = -1;
        int result = -1;
        try {
            final int position = pointer * 17;
            final byte mask = local.getByte(position);
            final int innerIndex = index >> 12 - depth & 0x7;
            loc = innerIndex * 2 + position + 1;
            result = local.getUnsignedShort(loc);
            if ((mask >> innerIndex & 0x1) == 0x1) {
                return getTraverse(local, index, result, depth + 3);
            }
            return (short)result;
        }
        catch (final Throwable t) {
            throw new RuntimeException("Failed with " + index + ", " + pointer + ", " + depth + ". Result: " + result + " from " + loc, t);
        }
    }
    
    public void serialize(@Nonnull final ByteBuf buf) {
        buf.writeShort(this.changeId);
        final boolean hasLight = this.light != null;
        buf.writeBoolean(hasLight);
        if (hasLight) {
            buf.ensureWritable(this.light.readableBytes());
            final int before = buf.writerIndex();
            buf.writeInt(0);
            this.serializeOctree(buf, 0);
            final int after = buf.writerIndex();
            buf.writerIndex(before);
            buf.writeInt(after - before - 4);
            buf.writerIndex(after);
        }
    }
    
    private void serializeOctree(@Nonnull final ByteBuf buf, final int position) {
        final int mask = this.light.getByte(position * 17);
        buf.writeByte(mask);
        for (int i = 0; i < 8; ++i) {
            final int val = this.light.getUnsignedShort(position * 17 + i * 2 + 1);
            if ((mask >> i & 0x1) == 0x1) {
                this.serializeOctree(buf, val);
            }
            else {
                buf.writeShort(val);
            }
        }
    }
    
    public void serializeForPacket(@Nonnull final ByteBuf buf) {
        final boolean hasLight = this.light != null;
        buf.writeBoolean(hasLight);
        if (hasLight) {
            buf.ensureWritable(this.light.readableBytes());
            this.serializeOctreeForPacket(buf, 0);
        }
    }
    
    private void serializeOctreeForPacket(@Nonnull final ByteBuf buf, final int position) {
        final int mask = this.light.getByte(position * 17);
        buf.writeByte(mask);
        for (int i = 0; i < 8; ++i) {
            final int val = this.light.getUnsignedShort(position * 17 + i * 2 + 1);
            if ((mask >> i & 0x1) == 0x1) {
                this.serializeOctreeForPacket(buf, val);
            }
            else {
                buf.writeShortLE(val);
            }
        }
    }
    
    @Nonnull
    public static ChunkLightData deserialize(@Nonnull final ByteBuf buf, final int version) {
        final short changeId = buf.readShort();
        final boolean hasLight = buf.readBoolean();
        ChunkLightData chunkLightData;
        if (hasLight) {
            final int length = buf.readInt();
            final ByteBuf from = buf.readSlice(length);
            final int estSize = length * 23 / 20;
            final ByteBuf buffer = Unpooled.buffer(estSize);
            buffer.writerIndex(17);
            deserializeOctree(from, buffer, 0, 0);
            chunkLightData = new ChunkLightData(buffer.copy(), changeId);
        }
        else {
            chunkLightData = new ChunkLightData(null, changeId);
        }
        return chunkLightData;
    }
    
    private static int deserializeOctree(@Nonnull final ByteBuf from, @Nonnull final ByteBuf to, final int position, int segmentIndex) {
        final int mask = from.readByte();
        to.setByte(position * 17, mask);
        for (int i = 0; i < 8; ++i) {
            int val;
            if ((mask >> i & 0x1) == 0x1) {
                final int nextSegmentIndex = ++segmentIndex;
                to.writerIndex((nextSegmentIndex + 1) * 17);
                val = nextSegmentIndex;
                segmentIndex = deserializeOctree(from, to, val, nextSegmentIndex);
            }
            else {
                val = from.readShort();
            }
            to.setShort(position * 17 + i * 2 + 1, val);
        }
        return segmentIndex;
    }
    
    @Nonnull
    public String octreeToString() {
        if (this.light == null) {
            return "NULL";
        }
        return ChunkLightDataBuilder.octreeToString(this.light);
    }
    
    public static short combineLightValues(final byte red, final byte green, final byte blue, final byte sky) {
        return (short)(sky << 12 | blue << 8 | green << 4 | red << 0);
    }
    
    public static short combineLightValues(final byte red, final byte green, final byte blue) {
        return (short)(blue << 8 | green << 4 | red << 0);
    }
    
    public static byte getLightValue(final short value, final int channel) {
        return (byte)(value >> channel * 4 & 0xF);
    }
    
    static {
        EMPTY = new ChunkLightData(null, (short)0);
    }
}
