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

package com.hypixel.hytale.server.core.modules.blockhealth;

import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.core.util.io.ByteBufUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.Iterator;
import com.hypixel.hytale.protocol.packets.world.UpdateBlockDamage;
import com.hypixel.hytale.protocol.BlockPosition;
import com.hypixel.hytale.protocol.Packet;
import java.util.List;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import java.util.function.Predicate;
import java.util.Objects;
import com.hypixel.hytale.server.core.universe.world.World;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.time.Instant;
import com.hypixel.hytale.math.vector.Vector3i;
import java.util.Map;
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 BlockHealthChunk implements Component<ChunkStore>
{
    private static final byte SERIALIZATION_VERSION = 2;
    public static final BuilderCodec<BlockHealthChunk> CODEC;
    private final Map<Vector3i, BlockHealth> blockHealthMap;
    private final Map<Vector3i, FragileBlock> blockFragilityMap;
    private Instant lastRepairGameTime;
    
    public BlockHealthChunk() {
        this.blockHealthMap = new Object2ObjectOpenHashMap<Vector3i, BlockHealth>(0);
        this.blockFragilityMap = new Object2ObjectOpenHashMap<Vector3i, FragileBlock>(0);
    }
    
    public Instant getLastRepairGameTime() {
        return this.lastRepairGameTime;
    }
    
    public void setLastRepairGameTime(final Instant lastRepairGameTime) {
        this.lastRepairGameTime = lastRepairGameTime;
    }
    
    @Nonnull
    public Map<Vector3i, BlockHealth> getBlockHealthMap() {
        return this.blockHealthMap;
    }
    
    @Nonnull
    public Map<Vector3i, FragileBlock> getBlockFragilityMap() {
        return this.blockFragilityMap;
    }
    
    @Nonnull
    public BlockHealth damageBlock(final Instant currentUptime, @Nonnull final World world, @Nonnull final Vector3i block, final float health) {
        final BlockHealth blockHealth = this.blockHealthMap.compute(block, (key, value) -> {
            if (value == null) {
                value = new BlockHealth();
            }
            value.setHealth(value.getHealth() - health);
            value.setLastDamageGameTime(currentUptime);
            return (value.getHealth() < 1.0) ? value : null;
        });
        if (blockHealth != null && !blockHealth.isDestroyed()) {
            final Predicate<PlayerRef> filter = player -> true;
            world.getNotificationHandler().updateBlockDamage(block.getX(), block.getY(), block.getZ(), blockHealth.getHealth(), -health, filter);
        }
        return Objects.requireNonNullElse(blockHealth, BlockHealth.NO_DAMAGE_INSTANCE);
    }
    
    @Nonnull
    public BlockHealth repairBlock(@Nonnull final World world, @Nonnull final Vector3i block, final float progress) {
        final BlockHealth blockHealth = Objects.requireNonNullElse(this.blockHealthMap.computeIfPresent(block, (key, value) -> {
            value.setHealth(value.getHealth() + progress);
            return (value.getHealth() > 1.0) ? value : null;
        }), BlockHealth.NO_DAMAGE_INSTANCE);
        world.getNotificationHandler().updateBlockDamage(block.getX(), block.getY(), block.getZ(), blockHealth.getHealth(), progress);
        return blockHealth;
    }
    
    public void removeBlock(@Nonnull final World world, @Nonnull final Vector3i block) {
        if (this.blockHealthMap.remove(block) != null) {
            world.getNotificationHandler().updateBlockDamage(block.getX(), block.getY(), block.getZ(), BlockHealth.NO_DAMAGE_INSTANCE.getHealth(), 0.0f);
        }
    }
    
    public void makeBlockFragile(final Vector3i blockLocation, final float fragileDuration) {
        this.blockFragilityMap.compute(blockLocation, (key, value) -> {
            if (value == null) {
                value = new FragileBlock(fragileDuration);
            }
            value.setDurationSeconds(fragileDuration);
            return (value.getDurationSeconds() <= 0.0) ? null : value;
        });
    }
    
    public boolean isBlockFragile(final Vector3i block) {
        return this.blockFragilityMap.get(block) != null;
    }
    
    public float getBlockHealth(final Vector3i block) {
        return this.blockHealthMap.getOrDefault(block, BlockHealth.NO_DAMAGE_INSTANCE).getHealth();
    }
    
    public void createBlockDamagePackets(@Nonnull final List<Packet> list) {
        for (final Map.Entry<Vector3i, BlockHealth> entry : this.blockHealthMap.entrySet()) {
            final Vector3i block = entry.getKey();
            final BlockPosition blockPosition = new BlockPosition(block.getX(), block.getY(), block.getZ());
            list.add(new UpdateBlockDamage(blockPosition, entry.getValue().getHealth(), 0.0f));
        }
    }
    
    @Nonnull
    @Override
    public BlockHealthChunk clone() {
        final BlockHealthChunk copy = new BlockHealthChunk();
        copy.lastRepairGameTime = this.lastRepairGameTime;
        for (final Map.Entry<Vector3i, BlockHealth> entry : this.blockHealthMap.entrySet()) {
            copy.blockHealthMap.put(entry.getKey(), entry.getValue().clone());
        }
        for (final Map.Entry<Vector3i, FragileBlock> entry2 : this.blockFragilityMap.entrySet()) {
            copy.blockFragilityMap.put(entry2.getKey(), entry2.getValue().clone());
        }
        return copy;
    }
    
    public void deserialize(@Nonnull final byte[] data) {
        this.blockHealthMap.clear();
        final ByteBuf buf = Unpooled.wrappedBuffer(data);
        final byte version = buf.readByte();
        for (int healthEntries = buf.readInt(), i = 0; i < healthEntries; ++i) {
            final int x = buf.readInt();
            final int y = buf.readInt();
            final int z = buf.readInt();
            final BlockHealth bh = new BlockHealth();
            bh.deserialize(buf, version);
            this.blockHealthMap.put(new Vector3i(x, y, z), bh);
        }
        if (version > 1) {
            for (int fragilityEntries = buf.readInt(), j = 0; j < fragilityEntries; ++j) {
                final int x2 = buf.readInt();
                final int y2 = buf.readInt();
                final int z2 = buf.readInt();
                final FragileBlock fragileBlock = new FragileBlock();
                fragileBlock.deserialize(buf, version);
                this.blockFragilityMap.put(new Vector3i(x2, y2, z2), fragileBlock);
            }
        }
    }
    
    public byte[] serialize() {
        final ByteBuf buf = Unpooled.buffer();
        buf.writeByte(2);
        buf.writeInt(this.blockHealthMap.size());
        for (final Map.Entry<Vector3i, BlockHealth> entry : this.blockHealthMap.entrySet()) {
            final Vector3i vec = entry.getKey();
            buf.writeInt(vec.x);
            buf.writeInt(vec.y);
            buf.writeInt(vec.z);
            final BlockHealth bh = entry.getValue();
            bh.serialize(buf);
        }
        buf.writeInt(this.blockFragilityMap.size());
        for (final Map.Entry<Vector3i, FragileBlock> entry2 : this.blockFragilityMap.entrySet()) {
            final Vector3i vec = entry2.getKey();
            buf.writeInt(vec.x);
            buf.writeInt(vec.y);
            buf.writeInt(vec.z);
            entry2.getValue().serialize(buf);
        }
        return ByteBufUtil.getBytesRelease(buf);
    }
    
    static {
        CODEC = BuilderCodec.builder(BlockHealthChunk.class, BlockHealthChunk::new).append(new KeyedCodec<byte[]>("Data", Codec.BYTE_ARRAY), BlockHealthChunk::deserialize, BlockHealthChunk::serialize).documentation("Binary data representing the state of this BlockHealthChunk").add().append(new KeyedCodec("LastRepairGameTime", Codec.INSTANT), (o, l) -> o.lastRepairGameTime = l, o -> o.lastRepairGameTime).documentation("The last tick of the world this BlockHealthChunk processed.").add().build();
    }
}
