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

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

import javax.annotation.Nullable;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import com.hypixel.hytale.math.util.ChunkUtil;
import javax.annotation.Nonnull;
import io.netty.buffer.ByteBuf;
import java.util.BitSet;

public class ChunkLightDataBuilder extends ChunkLightData
{
    static boolean DEBUG;
    protected BitSet currentSegments;
    
    public ChunkLightDataBuilder(final short changeId) {
        super(null, changeId);
    }
    
    public ChunkLightDataBuilder(@Nonnull final ChunkLightData lightData, final short changeId) {
        super((lightData.light != null) ? lightData.light.copy() : null, changeId);
        if (lightData instanceof ChunkLightDataBuilder) {
            throw new IllegalArgumentException("ChunkLightDataBuilder light data isn't compacted so we can't read this cleanly atm");
        }
        if (this.light != null) {
            (this.currentSegments = new BitSet()).set(0);
            findSegments(this.light, 0, this.currentSegments);
        }
    }
    
    protected static void findSegments(@Nonnull final ByteBuf light, final int position, @Nonnull final BitSet currentSegments) {
        final int mask = light.getByte(position * 17);
        for (int i = 0; i < 8; ++i) {
            final int val = light.getUnsignedShort(position * 17 + i * 2 + 1);
            if ((mask >> i & 0x1) == 0x1) {
                currentSegments.set(val);
                findSegments(light, val, currentSegments);
            }
        }
    }
    
    public void setBlockLight(final int x, final int y, final int z, final byte red, final byte green, final byte blue) {
        this.setBlockLight(ChunkUtil.indexBlock(x, y, z), red, green, blue);
    }
    
    public void setBlockLight(final int index, final byte red, final byte green, final byte blue) {
        final byte sky = this.getLight(index, 3);
        this.setLightRaw(index, ChunkLightData.combineLightValues(red, green, blue, sky));
    }
    
    public void setSkyLight(final int x, final int y, final int z, final byte light) {
        this.setSkyLight(ChunkUtil.indexBlock(x, y, z), light);
    }
    
    public void setSkyLight(final int index, final byte light) {
        this.setLight(index, 3, light);
    }
    
    public void setLight(final int index, final int channel, final byte value) {
        if (channel < 0 || channel >= 4) {
            throw new IllegalArgumentException();
        }
        int current = this.getLightRaw(index);
        current &= ~(15 << channel * 4);
        current |= (value & 0xF) << channel * 4;
        this.setLightRaw(index, (short)current);
    }
    
    public void setLightRaw(final int index, final short value) {
        if (index < 0 || index >= 32768) {
            throw new IllegalArgumentException("Index " + index + " is outside of the bounds!");
        }
        if (this.light == null) {
            this.light = Unpooled.buffer(2176);
        }
        if (this.currentSegments == null) {
            (this.currentSegments = new BitSet()).set(0);
        }
        setTraverse(this.light, this.currentSegments, index, 0, 0, value);
    }
    
    @Nonnull
    public ChunkLightData build() {
        if (this.light == null) {
            return new ChunkLightData(null, this.changeId);
        }
        final ByteBuf buffer = Unpooled.buffer(this.currentSegments.cardinality() * 17);
        buffer.writerIndex(17);
        this.serializeOctree(buffer, 0, 0);
        return new ChunkLightData(buffer, this.changeId);
    }
    
    private int serializeOctree(@Nonnull final ByteBuf to, final int position, int segmentIndex) {
        final int toPosition = segmentIndex;
        final int mask = this.light.getByte(position * 17);
        to.setByte(toPosition * 17, mask);
        for (int i = 0; i < 8; ++i) {
            int val = this.light.getUnsignedShort(position * 17 + i * 2 + 1);
            if ((mask >> i & 0x1) == 0x1) {
                to.ensureWritable(17);
                final int nextSegmentIndex = ++segmentIndex;
                to.writerIndex((nextSegmentIndex + 1) * 17);
                final int from = val;
                val = nextSegmentIndex;
                segmentIndex = this.serializeOctree(to, from, nextSegmentIndex);
            }
            to.setShort(toPosition * 17 + i * 2 + 1, val);
        }
        return segmentIndex;
    }
    
    @Nullable
    private static Res setTraverse(@Nonnull final ByteBuf local, @Nonnull final BitSet currentSegments, final int index, final int pointer, final int depth, final short value) {
        final int headerLocation = pointer * 17;
        byte i = local.getByte(headerLocation);
        final int innerIndex = index >> 12 - depth & 0x7;
        final int position = innerIndex * 2 + headerLocation + 1;
        final short currentValue = local.getShort(position);
        try {
            if ((i >> innerIndex & 0x1) == 0x1) {
                final int currentValueMasked = currentValue & 0xFFFF;
                if (depth == 12) {
                    throw new RuntimeException("Discovered branch at deepest point in octree! Mask " + i + " innerIndex " + innerIndex + " depth " + depth + " setValue " + value + " currentValue " + currentValue + " at index " + index + " pointer " + pointer);
                }
                if (setTraverse(local, currentSegments, index, currentValueMasked, depth + 3, value) != null) {
                    currentSegments.clear(currentValueMasked);
                    local.setShort(position, value);
                    final int mask = ~(1 << innerIndex);
                    i &= (byte)mask;
                    local.setByte(headerLocation, i);
                    if (i == 0) {
                        for (int j = 0; j < 8; ++j) {
                            final short s = local.getShort(j * 2 + headerLocation + 1);
                            if (s != value) {
                                return null;
                            }
                        }
                        return Res.INSTANCE;
                    }
                }
            }
            else if (value != currentValue) {
                if (depth > 12) {
                    throw new IllegalStateException("Somehow have invalid octree state: " + octreeToString(local) + " when setTraverse(" + index + ", " + pointer + ", " + depth + ", " + value + ");");
                }
                if (depth == 12) {
                    byte[] bytes = null;
                    if (ChunkLightDataBuilder.DEBUG) {
                        bytes = new byte[17];
                        local.getBytes(headerLocation, bytes, 0, bytes.length);
                    }
                    local.setShort(position, value);
                    for (int k = 0; k < 8; ++k) {
                        final short s2 = local.getShort(k * 2 + headerLocation + 1);
                        if (s2 != value) {
                            return null;
                        }
                    }
                    return ChunkLightDataBuilder.DEBUG ? new Res(ByteBufUtil.hexDump(bytes)) : Res.INSTANCE;
                }
                i |= (byte)(1 << innerIndex);
                local.setByte(headerLocation, i);
                final int newSegmentIndex = growSegment(local, currentSegments, currentValue);
                local.setShort(position, newSegmentIndex);
                final Res out = setTraverse(local, currentSegments, index, newSegmentIndex, depth + 3, value);
                if (out != null) {
                    throw new RuntimeException("Created new segment that instantly collapsed with (" + index + ", " + pointer + ", " + depth + ", " + value + "): with currentValue mismatch " + currentValue + " res " + String.valueOf(out));
                }
                return null;
            }
        }
        catch (final Throwable t) {
            throw new RuntimeException("Failed to setTraverse(" + index + ", " + pointer + ", " + depth + ", " + value + ") with i " + i + ", innerIndex " + innerIndex + ", position " + position + ", currentValue " + currentValue, t);
        }
        return null;
    }
    
    protected static int growSegment(@Nonnull final ByteBuf local, @Nonnull final BitSet currentSegments, final short val) {
        final int newSegmentIndex = currentSegments.nextClearBit(0);
        currentSegments.set(newSegmentIndex);
        final int currentCapacity = local.capacity();
        if (currentCapacity <= (newSegmentIndex + 1) * 17) {
            final int newCap = currentCapacity + 1088;
            local.capacity(newCap);
        }
        local.setByte(newSegmentIndex * 17, 0);
        for (int j = 0; j < 8; ++j) {
            local.setShort(newSegmentIndex * 17 + j * 2 + 1, val);
        }
        return newSegmentIndex;
    }
    
    @Nonnull
    public String toStringOctree() {
        if (this.light == null) {
            return "NULL";
        }
        return octreeToString(this.light);
    }
    
    @Nonnull
    public static String octreeToString(@Nonnull final ByteBuf buffer) {
        final StringBuffer out = new StringBuffer();
        try {
            octreeToString(buffer, 0, out, 0);
        }
        catch (final Throwable t) {
            throw new RuntimeException("Failed at " + String.valueOf(out), t);
        }
        return out.toString();
    }
    
    public static void octreeToString(@Nonnull final ByteBuf buffer, final int pointer, @Nonnull final StringBuffer out, final int recursion) {
        final int i = buffer.getByte(pointer * 17);
        for (int j = 0; j < 8; ++j) {
            final int loc = pointer * 17 + j * 2 + 1;
            final int s = buffer.getUnsignedShort(loc);
            out.append("\t".repeat(Math.max(0, recursion)));
            if ((i & 1 << j) != 0x0) {
                out.append("SUBTREE AT ").append(j).append('\n');
                octreeToString(buffer, s, out, recursion + 1);
            }
            else {
                out.append("INDEX ").append(j).append(" VALUE: ").append(s);
            }
            if (j != 7) {
                out.append('\n');
            }
        }
    }
    
    static {
        ChunkLightDataBuilder.DEBUG = false;
    }
    
    private static class Res
    {
        public static final Res INSTANCE;
        private final String segment;
        
        private Res(final String segment) {
            this.segment = segment;
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "Res{segment='" + this.segment + "'}";
        }
        
        static {
            INSTANCE = new Res(null);
        }
    }
}
