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

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

import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.math.util.ChunkUtil;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import io.netty.buffer.ByteBuf;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.server.core.util.io.ByteBufUtil;
import com.hypixel.hytale.server.core.asset.type.environment.config.Environment;
import io.netty.buffer.ByteBufAllocator;
import javax.annotation.Nonnull;
import java.util.Map;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.ComponentType;
import it.unimi.dsi.fastutil.ints.Int2LongMap;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Component;

public class EnvironmentChunk implements Component<ChunkStore>
{
    public static final BuilderCodec<EnvironmentChunk> CODEC;
    private final EnvironmentColumn[] columns;
    private final Int2LongMap counts;
    
    public static ComponentType<ChunkStore, EnvironmentChunk> getComponentType() {
        return LegacyModule.get().getEnvironmentChunkComponentType();
    }
    
    public EnvironmentChunk() {
        this(0);
    }
    
    public EnvironmentChunk(final int defaultId) {
        this.columns = new EnvironmentColumn[1024];
        this.counts = new Int2LongOpenHashMap();
        for (int i = 0; i < this.columns.length; ++i) {
            this.columns[i] = new EnvironmentColumn(defaultId);
        }
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> clone() {
        final EnvironmentChunk chunk = new EnvironmentChunk();
        for (int i = 0; i < this.columns.length; ++i) {
            chunk.columns[i].copyFrom(this.columns[i]);
        }
        chunk.counts.putAll(this.counts);
        return chunk;
    }
    
    public int get(final int x, final int y, final int z) {
        return this.columns[idx(x, z)].get(y);
    }
    
    public EnvironmentColumn get(final int x, final int z) {
        return this.columns[idx(x, z)];
    }
    
    public void setColumn(final int x, final int z, final int environmentId) {
        final EnvironmentColumn column = this.columns[idx(x, z)];
        column.set(environmentId);
        final int minY = Integer.MIN_VALUE;
        int maxY;
        do {
            final int id = column.get(minY);
            maxY = column.getMax(minY);
            final int count = maxY - minY + 1;
            this.decrementBlockCount(id, count);
        } while (maxY < Integer.MAX_VALUE);
        this.createIfNotExist(environmentId);
        this.incrementBlockCount(environmentId, Integer.MAX_VALUE);
        column.set(environmentId);
    }
    
    public boolean set(final int x, final int y, final int z, final int environmentId) {
        final EnvironmentColumn column = this.columns[idx(x, z)];
        final int oldInternalId = column.get(y);
        if (environmentId != oldInternalId) {
            this.decrementBlockCount(oldInternalId, 1L);
            this.createIfNotExist(environmentId);
            this.incrementBlockCount(environmentId);
            column.set(y, environmentId);
            return true;
        }
        return false;
    }
    
    public boolean contains(final int environmentId) {
        return this.counts.containsKey(environmentId);
    }
    
    private void createIfNotExist(final int environmentId) {
        if (!this.counts.containsKey(environmentId)) {
            this.counts.put(environmentId, 0L);
        }
    }
    
    private void incrementBlockCount(final int internalId) {
        this.counts.mergeLong(internalId, 1L, Long::sum);
    }
    
    private void incrementBlockCount(final int internalId, final int count) {
        final long oldCount = this.counts.get(internalId);
        this.counts.put(internalId, oldCount + count);
    }
    
    private boolean decrementBlockCount(final int environmentId, final long count) {
        final long oldCount = this.counts.get(environmentId);
        if (oldCount <= count) {
            this.counts.remove(environmentId);
            return true;
        }
        this.counts.put(environmentId, oldCount - count);
        return false;
    }
    
    private byte[] serialize() {
        final ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        try {
            buf.writeInt(this.counts.size());
            int environmentId = 0;
            for (final Int2LongMap.Entry entry : this.counts.int2LongEntrySet()) {
                environmentId = entry.getIntKey();
                final Environment environment = Environment.getAssetMap().getAsset(environmentId);
                final String key = (environment != null) ? environment.getId() : Environment.UNKNOWN.getId();
                buf.writeInt(environmentId);
                ByteBufUtil.writeUTF(buf, key);
            }
            for (int i = 0; i < this.columns.length; ++i) {
                this.columns[i].serialize(buf, (environmentId, buf0) -> buf0.writeInt(environmentId));
            }
            return ByteBufUtil.getBytesRelease(buf);
        }
        catch (final Throwable t) {
            buf.release();
            throw SneakyThrow.sneakyThrow(t);
        }
    }
    
    private void deserialize(@Nonnull final byte[] bytes) {
        final ByteBuf buf = Unpooled.wrappedBuffer(bytes);
        this.counts.clear();
        final int mappingCount = buf.readInt();
        final Int2IntMap idMapping = new Int2IntOpenHashMap(mappingCount);
        for (int i = 0; i < mappingCount; ++i) {
            final int serialId = buf.readInt();
            final String key = ByteBufUtil.readUTF(buf);
            final int environmentId = Environment.getIndexOrUnknown(key, "Failed to find environment '%s' when deserializing environment chunk", key);
            idMapping.put(serialId, environmentId);
            this.counts.put(environmentId, 0L);
        }
        for (int i = 0; i < this.columns.length; ++i) {
            final EnvironmentColumn column = this.columns[i];
            column.deserialize(buf, buf0 -> idMapping.get(buf0.readInt()));
            for (int x = 0; x < column.size(); ++x) {
                this.counts.mergeLong(column.getValue(x), 1L, Long::sum);
            }
        }
    }
    
    public byte[] serializeProtocol() {
        final ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        for (int i = 0; i < this.columns.length; ++i) {
            this.columns[i].serializeProtocol(buf);
        }
        return ByteBufUtil.getBytesRelease(buf);
    }
    
    public void trim() {
        for (int i = 0; i < this.columns.length; ++i) {
            this.columns[i].trim();
        }
    }
    
    private static int idx(final int x, final int z) {
        return ChunkUtil.indexColumn(x, z);
    }
    
    static {
        CODEC = BuilderCodec.builder(EnvironmentChunk.class, EnvironmentChunk::new).addField(new KeyedCodec<byte[]>("Data", Codec.BYTE_ARRAY), EnvironmentChunk::deserialize, EnvironmentChunk::serialize).build();
    }
}
