package net.minecraft.world.level.saveddata.maps;

import java.util.AbstractList;
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
import net.minecraft.world.item.MapItem;
import org.apache.logging.log4j.LogManager;
import net.minecraft.network.protocol.Packet;
import net.minecraft.world.level.BlockGetter;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.item.ItemStack;
import java.util.Iterator;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.LevelAccessor;
import java.util.function.Consumer;
import net.minecraft.world.level.dimension.DimensionType;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.Dynamic;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import com.google.common.collect.Maps;
import com.google.common.collect.Lists;
import net.minecraft.world.entity.player.Player;
import java.util.Map;
import java.util.List;
import net.minecraft.world.level.Level;
import net.minecraft.resources.ResourceKey;
import org.apache.logging.log4j.Logger;
import net.minecraft.world.level.saveddata.SavedData;

public class MapItemSavedData extends SavedData {
    private static final Logger LOGGER;
    public int x;
    public int z;
    public ResourceKey<Level> dimension;
    public boolean trackingPosition;
    public boolean unlimitedTracking;
    public byte scale;
    public byte[] colors;
    public boolean locked;
    public final List<HoldingPlayer> carriedBy;
    private final Map<Player, HoldingPlayer> carriedByPlayers;
    private final Map<String, MapBanner> bannerMarkers;
    public final Map<String, MapDecoration> decorations;
    private final Map<String, MapFrame> frameMarkers;
    
    public MapItemSavedData(final String string) {
        super(string);
        this.colors = new byte[16384];
        this.carriedBy = Lists.newArrayList();
        this.carriedByPlayers = Maps.newHashMap();
        this.bannerMarkers = Maps.newHashMap();
        this.decorations = Maps.newLinkedHashMap();
        this.frameMarkers = Maps.newHashMap();
    }
    
    public void setProperties(final int integer1, final int integer2, final int integer3, final boolean boolean4, final boolean boolean5, final ResourceKey<Level> ug) {
        this.scale = (byte)integer3;
        this.setOrigin(integer1, integer2, this.scale);
        this.dimension = ug;
        this.trackingPosition = boolean4;
        this.unlimitedTracking = boolean5;
        this.setDirty();
    }
    
    public void setOrigin(final double double1, final double double2, final int integer) {
        final int integer2 = 128 * (1 << integer);
        final int integer3 = Mth.floor((double1 + 64.0) / integer2);
        final int integer4 = Mth.floor((double2 + 64.0) / integer2);
        this.x = integer3 * integer2 + integer2 / 2 - 64;
        this.z = integer4 * integer2 + integer2 / 2 - 64;
    }
    
    @Override
    public void load(final CompoundTag le) {
        final IllegalArgumentException ex;
        this.dimension = DimensionType.parseLegacy(new Dynamic((DynamicOps)NbtOps.INSTANCE, le.get("dimension"))).resultOrPartial((Consumer)MapItemSavedData.LOGGER::error).<Throwable>orElseThrow(() -> {
            new IllegalArgumentException("Invalid map dimension: " + le.get("dimension"));
            return ex;
        });
        this.x = le.getInt("xCenter");
        this.z = le.getInt("zCenter");
        this.scale = (byte)Mth.clamp(le.getByte("scale"), 0, 4);
        this.trackingPosition = (!le.contains("trackingPosition", 1) || le.getBoolean("trackingPosition"));
        this.unlimitedTracking = le.getBoolean("unlimitedTracking");
        this.locked = le.getBoolean("locked");
        this.colors = le.getByteArray("colors");
        if (this.colors.length != 16384) {
            this.colors = new byte[16384];
        }
        final ListTag lk3 = le.getList("banners", 10);
        for (int integer4 = 0; integer4 < lk3.size(); ++integer4) {
            final MapBanner czr5 = MapBanner.load(lk3.getCompound(integer4));
            this.bannerMarkers.put(czr5.getId(), czr5);
            this.addDecoration(czr5.getDecoration(), null, czr5.getId(), czr5.getPos().getX(), czr5.getPos().getZ(), 180.0, czr5.getName());
        }
        final ListTag lk4 = le.getList("frames", 10);
        for (int integer5 = 0; integer5 < lk4.size(); ++integer5) {
            final MapFrame czt6 = MapFrame.load(lk4.getCompound(integer5));
            this.frameMarkers.put(czt6.getId(), czt6);
            this.addDecoration(MapDecoration.Type.FRAME, null, "frame-" + czt6.getEntityId(), czt6.getPos().getX(), czt6.getPos().getZ(), czt6.getRotation(), null);
        }
    }
    
    @Override
    public CompoundTag save(final CompoundTag le) {
        ResourceLocation.CODEC.encodeStart((DynamicOps)NbtOps.INSTANCE, this.dimension.location()).resultOrPartial((Consumer)MapItemSavedData.LOGGER::error).ifPresent(lu -> le.put("dimension", lu));
        le.putInt("xCenter", this.x);
        le.putInt("zCenter", this.z);
        le.putByte("scale", this.scale);
        le.putByteArray("colors", this.colors);
        le.putBoolean("trackingPosition", this.trackingPosition);
        le.putBoolean("unlimitedTracking", this.unlimitedTracking);
        le.putBoolean("locked", this.locked);
        final ListTag lk3 = new ListTag();
        for (final MapBanner czr5 : this.bannerMarkers.values()) {
            ((AbstractList<CompoundTag>)lk3).add(czr5.save());
        }
        le.put("banners", lk3);
        final ListTag lk4 = new ListTag();
        for (final MapFrame czt6 : this.frameMarkers.values()) {
            ((AbstractList<CompoundTag>)lk4).add(czt6.save());
        }
        le.put("frames", lk4);
        return le;
    }
    
    public void lockData(final MapItemSavedData czv) {
        this.locked = true;
        this.x = czv.x;
        this.z = czv.z;
        this.bannerMarkers.putAll(czv.bannerMarkers);
        this.decorations.putAll(czv.decorations);
        System.arraycopy(czv.colors, 0, this.colors, 0, czv.colors.length);
        this.setDirty();
    }
    
    public void tickCarriedBy(final Player bec, final ItemStack bki) {
        if (!this.carriedByPlayers.containsKey(bec)) {
            final HoldingPlayer a4 = new HoldingPlayer(bec);
            this.carriedByPlayers.put(bec, a4);
            this.carriedBy.add(a4);
        }
        if (!bec.inventory.contains(bki)) {
            this.decorations.remove(bec.getName().getString());
        }
        for (int integer4 = 0; integer4 < this.carriedBy.size(); ++integer4) {
            final HoldingPlayer a5 = this.carriedBy.get(integer4);
            final String string6 = a5.player.getName().getString();
            if (a5.player.removed || (!a5.player.inventory.contains(bki) && !bki.isFramed())) {
                this.carriedByPlayers.remove(a5.player);
                this.carriedBy.remove(a5);
                this.decorations.remove(string6);
            }
            else if (!bki.isFramed() && a5.player.level.dimension() == this.dimension && this.trackingPosition) {
                this.addDecoration(MapDecoration.Type.PLAYER, a5.player.level, string6, a5.player.getX(), a5.player.getZ(), a5.player.yRot, null);
            }
        }
        if (bki.isFramed() && this.trackingPosition) {
            final ItemFrame bba4 = bki.getFrame();
            final BlockPos fu5 = bba4.getPos();
            final MapFrame czt6 = this.frameMarkers.get(MapFrame.frameId(fu5));
            if (czt6 != null && bba4.getId() != czt6.getEntityId() && this.frameMarkers.containsKey(czt6.getId())) {
                this.decorations.remove("frame-" + czt6.getEntityId());
            }
            final MapFrame czt7 = new MapFrame(fu5, bba4.getDirection().get2DDataValue() * 90, bba4.getId());
            this.addDecoration(MapDecoration.Type.FRAME, bec.level, "frame-" + bba4.getId(), fu5.getX(), fu5.getZ(), bba4.getDirection().get2DDataValue() * 90, null);
            this.frameMarkers.put(czt7.getId(), czt7);
        }
        final CompoundTag le4 = bki.getTag();
        if (le4 != null && le4.contains("Decorations", 9)) {
            final ListTag lk5 = le4.getList("Decorations", 10);
            for (int integer5 = 0; integer5 < lk5.size(); ++integer5) {
                final CompoundTag le5 = lk5.getCompound(integer5);
                if (!this.decorations.containsKey(le5.getString("id"))) {
                    this.addDecoration(MapDecoration.Type.byIcon(le5.getByte("type")), bec.level, le5.getString("id"), le5.getDouble("x"), le5.getDouble("z"), le5.getDouble("rot"), null);
                }
            }
        }
    }
    
    public static void addTargetDecoration(final ItemStack bki, final BlockPos fu, final String string, final MapDecoration.Type a) {
        ListTag lk5;
        if (bki.hasTag() && bki.getTag().contains("Decorations", 9)) {
            lk5 = bki.getTag().getList("Decorations", 10);
        }
        else {
            lk5 = new ListTag();
            bki.addTagElement("Decorations", lk5);
        }
        final CompoundTag le6 = new CompoundTag();
        le6.putByte("type", a.getIcon());
        le6.putString("id", string);
        le6.putDouble("x", fu.getX());
        le6.putDouble("z", fu.getZ());
        le6.putDouble("rot", 180.0);
        ((AbstractList<CompoundTag>)lk5).add(le6);
        if (a.hasMapColor()) {
            final CompoundTag le7 = bki.getOrCreateTagElement("display");
            le7.putInt("MapColor", a.getMapColor());
        }
    }
    
    private void addDecoration(MapDecoration.Type a, @Nullable final LevelAccessor bqc, final String string, final double double4, final double double5, double double6, @Nullable final Component mr) {
        final int integer12 = 1 << this.scale;
        final float float13 = (float)(double4 - this.x) / integer12;
        final float float14 = (float)(double5 - this.z) / integer12;
        byte byte15 = (byte)(float13 * 2.0f + 0.5);
        byte byte16 = (byte)(float14 * 2.0f + 0.5);
        final int integer13 = 63;
        byte byte17;
        if (float13 >= -63.0f && float14 >= -63.0f && float13 <= 63.0f && float14 <= 63.0f) {
            double6 += ((double6 < 0.0) ? -8.0 : 8.0);
            byte17 = (byte)(double6 * 16.0 / 360.0);
            if (this.dimension == Level.NETHER && bqc != null) {
                final int integer14 = (int)(bqc.getLevelData().getDayTime() / 10L);
                byte17 = (byte)(integer14 * integer14 * 34187121 + integer14 * 121 >> 15 & 0xF);
            }
        }
        else {
            if (a != MapDecoration.Type.PLAYER) {
                this.decorations.remove(string);
                return;
            }
            final int integer14 = 320;
            if (Math.abs(float13) < 320.0f && Math.abs(float14) < 320.0f) {
                a = MapDecoration.Type.PLAYER_OFF_MAP;
            }
            else {
                if (!this.unlimitedTracking) {
                    this.decorations.remove(string);
                    return;
                }
                a = MapDecoration.Type.PLAYER_OFF_LIMITS;
            }
            byte17 = 0;
            if (float13 <= -63.0f) {
                byte15 = -128;
            }
            if (float14 <= -63.0f) {
                byte16 = -128;
            }
            if (float13 >= 63.0f) {
                byte15 = 127;
            }
            if (float14 >= 63.0f) {
                byte16 = 127;
            }
        }
        this.decorations.put(string, new MapDecoration(a, byte15, byte16, byte17, mr));
    }
    
    @Nullable
    public Packet<?> getUpdatePacket(final ItemStack bki, final BlockGetter bpg, final Player bec) {
        final HoldingPlayer a5 = this.carriedByPlayers.get(bec);
        if (a5 == null) {
            return null;
        }
        return a5.nextUpdatePacket(bki);
    }
    
    public void setDirty(final int integer1, final int integer2) {
        this.setDirty();
        for (final HoldingPlayer a5 : this.carriedBy) {
            a5.markDirty(integer1, integer2);
        }
    }
    
    public HoldingPlayer getHoldingPlayer(final Player bec) {
        HoldingPlayer a3 = this.carriedByPlayers.get(bec);
        if (a3 == null) {
            a3 = new HoldingPlayer(bec);
            this.carriedByPlayers.put(bec, a3);
            this.carriedBy.add(a3);
        }
        return a3;
    }
    
    public void toggleBanner(final LevelAccessor bqc, final BlockPos fu) {
        final double double4 = fu.getX() + 0.5;
        final double double5 = fu.getZ() + 0.5;
        final int integer8 = 1 << this.scale;
        final double double6 = (double4 - this.x) / integer8;
        final double double7 = (double5 - this.z) / integer8;
        final int integer9 = 63;
        boolean boolean14 = false;
        if (double6 >= -63.0 && double7 >= -63.0 && double6 <= 63.0 && double7 <= 63.0) {
            final MapBanner czr15 = MapBanner.fromWorld(bqc, fu);
            if (czr15 == null) {
                return;
            }
            boolean boolean15 = true;
            if (this.bannerMarkers.containsKey(czr15.getId()) && this.bannerMarkers.get(czr15.getId()).equals(czr15)) {
                this.bannerMarkers.remove(czr15.getId());
                this.decorations.remove(czr15.getId());
                boolean15 = false;
                boolean14 = true;
            }
            if (boolean15) {
                this.bannerMarkers.put(czr15.getId(), czr15);
                this.addDecoration(czr15.getDecoration(), bqc, czr15.getId(), double4, double5, 180.0, czr15.getName());
                boolean14 = true;
            }
            if (boolean14) {
                this.setDirty();
            }
        }
    }
    
    public void checkBanners(final BlockGetter bpg, final int integer2, final int integer3) {
        final Iterator<MapBanner> iterator5 = this.bannerMarkers.values().iterator();
        while (iterator5.hasNext()) {
            final MapBanner czr6 = iterator5.next();
            if (czr6.getPos().getX() == integer2 && czr6.getPos().getZ() == integer3) {
                final MapBanner czr7 = MapBanner.fromWorld(bpg, czr6.getPos());
                if (czr6.equals(czr7)) {
                    continue;
                }
                iterator5.remove();
                this.decorations.remove(czr6.getId());
            }
        }
    }
    
    public void removedFromFrame(final BlockPos fu, final int integer) {
        this.decorations.remove("frame-" + integer);
        this.frameMarkers.remove(MapFrame.frameId(fu));
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    public class HoldingPlayer {
        public final Player player;
        private boolean dirtyData;
        private int minDirtyX;
        private int minDirtyY;
        private int maxDirtyX;
        private int maxDirtyY;
        private int tick;
        public int step;
        
        public HoldingPlayer(final Player bec) {
            this.dirtyData = true;
            this.maxDirtyX = 127;
            this.maxDirtyY = 127;
            this.player = bec;
        }
        
        @Nullable
        public Packet<?> nextUpdatePacket(final ItemStack bki) {
            if (this.dirtyData) {
                this.dirtyData = false;
                return new ClientboundMapItemDataPacket(MapItem.getMapId(bki), MapItemSavedData.this.scale, MapItemSavedData.this.trackingPosition, MapItemSavedData.this.locked, MapItemSavedData.this.decorations.values(), MapItemSavedData.this.colors, this.minDirtyX, this.minDirtyY, this.maxDirtyX + 1 - this.minDirtyX, this.maxDirtyY + 1 - this.minDirtyY);
            }
            if (this.tick++ % 5 == 0) {
                return new ClientboundMapItemDataPacket(MapItem.getMapId(bki), MapItemSavedData.this.scale, MapItemSavedData.this.trackingPosition, MapItemSavedData.this.locked, MapItemSavedData.this.decorations.values(), MapItemSavedData.this.colors, 0, 0, 0, 0);
            }
            return null;
        }
        
        public void markDirty(final int integer1, final int integer2) {
            if (this.dirtyData) {
                this.minDirtyX = Math.min(this.minDirtyX, integer1);
                this.minDirtyY = Math.min(this.minDirtyY, integer2);
                this.maxDirtyX = Math.max(this.maxDirtyX, integer1);
                this.maxDirtyY = Math.max(this.maxDirtyY, integer2);
            }
            else {
                this.dirtyData = true;
                this.minDirtyX = integer1;
                this.minDirtyY = integer2;
                this.maxDirtyX = integer1;
                this.maxDirtyY = integer2;
            }
        }
    }
}
