package net.minecraft.network.protocol.game;

import net.minecraft.network.protocol.Packet;
import net.minecraft.world.level.saveddata.maps.MapIcon;
import net.minecraft.world.level.saveddata.maps.WorldMap;
import net.minecraft.world.level.saveddata.maps.WorldMap$PatchData;

import com.bergerkiller.generated.net.minecraft.world.level.saveddata.maps.MapIconHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutMapHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutMapHandle.Builder;

class PacketPlayOutMap extends Packet {

    <code>
    import com.bergerkiller.generated.net.minecraft.world.level.saveddata.maps.MapIconHandle;
    import com.bergerkiller.mountiplex.conversion.type.DuplexConverter;
    import com.bergerkiller.mountiplex.conversion.util.ConvertingList;

    public static class Builder implements Cloneable {
        private int mapId = 0;
        private byte scale = (byte) 1;
        private boolean locked = false;
        private java.util.Optional<java.util.List<MapIconHandle>> mapIcons = java.util.Optional.empty();
        private boolean hasData = false;
        private int startX, startY;
        private int width, height;
        private byte[] colors = null;

        private static final DuplexConverter<MapIconHandle, MapCursor> mapCursorHandleConversion = new DuplexConverter<MapIconHandle, MapCursor>(MapIconHandle.class, MapCursor.class) {
            @Override
            public MapCursor convertInput(MapIconHandle mapIconHandle) {
                return mapIconHandle.toCursor();
            }

            @Override
            public MapIconHandle convertOutput(MapCursor mapCursor) {
                return MapIconHandle.fromCursor(mapCursor);
            }
        };

        private Builder() {
        }

        public int get_mapId() {
            return this.mapId;
        }

        public byte get_scale() {
            return scale;
        }

        public boolean get_locked() {
            return locked;
        }

        public java.util.Optional<java.util.List<org.bukkit.map.MapCursor>> get_cursors() {
            return mapIcons.map(list -> new ConvertingList<>(list, mapCursorHandleConversion));
        }

        public java.util.Optional<java.util.List<MapIconHandle>> get_cursors_nms() {
            return mapIcons;
        }

        public boolean has_data() {
            return hasData;
        }

        public int get_data_startX() {
            return startX;
        }

        public int get_data_startY() {
            return startY;
        }

        public int get_data_width() {
            return width;
        }

        public int get_data_height() {
            return height;
        }

        public byte[] get_data_colors() {
            return colors;
        }

        public Builder mapId(int mapId) {
            this.mapId = mapId;
            return this;
        }

        public Builder scale(byte scale) {
            this.scale = scale;
            return this;
        }

        public Builder locked(boolean locked) {
            this.locked = locked;
            return this;
        }

        private List<MapIconHandle> convertCursors(java.util.List<org.bukkit.map.MapCursor> cursors) {
            return new ConvertingList<>(cursors, mapCursorHandleConversion.reverse());
        }

        public Builder no_cursors() {
            return this.cursors_nms(java.util.Collections.emptyList());
        }

        public Builder cursors(java.util.List<org.bukkit.map.MapCursor> cursors) {
            return this.cursors_nms(convertCursors(cursors));
        }

        public Builder cursors_nms(java.util.List<MapIconHandle> rawCursors) {
            this.mapIcons = java.util.Optional.ofNullable(rawCursors);
            return this;
        }

        public Builder add_cursors(java.util.List<org.bukkit.map.MapCursor> cursors) {
            return add_cursors_nms(convertCursors(cursors));
        }

        public Builder add_cursors_nms(java.util.List<MapIconHandle> rawCursors) {
            if (!this.mapIcons.isPresent()) {
                this.cursors_nms(rawCursors);
            } else if (rawCursors != null && !rawCursors.isEmpty()) {
                java.util.List<MapIconHandle> existing = this.mapIcons.get();
                java.util.List<MapIconHandle> newList = new java.util.ArrayList<>(existing.size() + rawCursors.size());
                newList.addAll(existing);
                newList.addAll(rawCursors);
                this.mapIcons = java.util.Optional.of(newList);
            }
            return this;
        }

        public Builder data(int startX, int startY, int width, int height, byte[] colors) {
            this.startX = startX;
            this.startY = startY;
            this.width = width;
            this.height = height;
            this.colors = colors;
            this.hasData = true;
            return this;
        }

        @Override
        public Builder clone() {
            Builder copy = new Builder();
            copy.mapId = this.mapId;
            copy.scale = this.scale;
            copy.locked = this.locked;
            copy.mapIcons = this.mapIcons;
            copy.startX = this.startX;
            copy.startY = this.startY;
            copy.width = this.width;
            copy.height = this.height;
            copy.colors = this.colors;
            copy.hasData = this.hasData;
            return copy;
        }

        public PacketPlayOutMapHandle create() {
            return createNew(this);
        }
    }

    public static Builder build() {
        return new Builder();
    }

    public Builder mutable() {
        Builder b = build()
            .mapId(getMapId())
            .scale(getScale())
            .locked(isLocked());
        if (hasCursors()) {
            b.cursors(getCursors());
        }
        if (hasPixels()) {
            b.data(getStartX(), getStartY(), getWidth(), getHeight(), getPixels());
        }
        return b;
    }
    </code>

#if version >= 1.20.5
    public int getMapId() {
        return instance.mapId().id();
    }
    public byte getScale:scale();
    public boolean isLocked:locked();
#elseif version >= 1.18
    public int getMapId();
    public byte getScale();
    public boolean isLocked();
#elseif version >= 1.17
    public int getMapId:b();
    public byte getScale:c();
    public boolean isLocked:d();
#else
    public int getMapId() {
        #require net.minecraft.network.protocol.game.PacketPlayOutMap private int mapId:a;
        return instance#mapId;
    }
    public byte getScale() {
        #require net.minecraft.network.protocol.game.PacketPlayOutMap private byte scale:b;
        return instance#scale;
    }
    public boolean isLocked() {
  #if version >= 1.14
        #require net.minecraft.network.protocol.game.PacketPlayOutMap private boolean locked:d;
        return instance#locked;
  #else
        return false;
  #endif
    }
#endif

#if version >= 1.20.5
    #require net.minecraft.network.protocol.game.PacketPlayOutMap public WorldMap$PatchData getColorPatch() {
        return (WorldMap$PatchData) instance.colorPatch().orElse(null);
    }

    public boolean hasCursors() {
        return instance.decorations().isPresent();
    }
    public (java.util.List<org.bukkit.map.MapCursor>) java.util.List<MapIcon> getCursors() {
        return (java.util.List) instance.decorations().orElse(null);
    }
    public int getStartX() {
        WorldMap$PatchData data = instance#getColorPatch();
        return (data == null) ? 0 : data.startX();
    }
    public int getStartY() {
        WorldMap$PatchData data = instance#getColorPatch();
        return (data == null) ? 0 : data.startY();
    }
    public int getWidth() {
        WorldMap$PatchData data = instance#getColorPatch();
        return (data == null) ? 0 : data.width();
    }
    public int getHeight() {
        WorldMap$PatchData data = instance#getColorPatch();
        return (data == null) ? 0 : data.height();
    }
    public boolean hasPixels() {
        return instance.colorPatch().isPresent();
    }
    public byte[] getPixels() {
        WorldMap$PatchData data = instance#getColorPatch();
        return (data == null) ? (byte[]) null : data.mapColors();
    }
#elseif version >= 1.17
    // Since 1.17 the region coordinates and pixel data are in a separate 'Patch' class
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly final List<MapIcon> cursors:decorations;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private final net.minecraft.world.level.saveddata.maps.WorldMap$PatchData colorPatch;

    public boolean hasCursors() {
        java.util.List cursors = instance#cursors;
        return cursors != null;
    }
    public (java.util.List<org.bukkit.map.MapCursor>) java.util.List<MapIcon> getCursors() {
        return instance#cursors;
    }
    public int getStartX() {
        #require net.minecraft.world.level.saveddata.maps.WorldMap$PatchData public final readonly int startX;
        WorldMap$PatchData colorPatch = instance#colorPatch;
        return (colorPatch == null) ? 0 : colorPatch#startX;
    }
    public int getStartY() {
        #require net.minecraft.world.level.saveddata.maps.WorldMap$PatchData public final readonly int startY;
        WorldMap$PatchData colorPatch = instance#colorPatch;
        return (colorPatch == null) ? 0 : colorPatch#startY;
    }
    public int getWidth() {
        #require net.minecraft.world.level.saveddata.maps.WorldMap$PatchData public final readonly int width;
        WorldMap$PatchData colorPatch = instance#colorPatch;
        return (colorPatch == null) ? 0 : colorPatch#width;
    }
    public int getHeight() {
        #require net.minecraft.world.level.saveddata.maps.WorldMap$PatchData public final readonly int height;
        WorldMap$PatchData colorPatch = instance#colorPatch;
        return (colorPatch == null) ? 0 : colorPatch#height;
    }
    public boolean hasPixels() {
        WorldMap$PatchData colorPatch = instance#colorPatch;
        return colorPatch != null;
    }
    public byte[] getPixels() {
        #require net.minecraft.world.level.saveddata.maps.WorldMap$PatchData public final readonly byte[] mapColors;
        WorldMap$PatchData colorPatch = instance#colorPatch;
        return (colorPatch == null) ? (byte[]) null : colorPatch#mapColors;
    }
#else
  #if version >= 1.14
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly MapIcon[] cursors:e;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int startx:f;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int starty:g;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int width:h;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int height:i;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly byte[] pixels:j;
  #elseif version >= 1.9
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly MapIcon[] cursors:d;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int startx:e;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int starty:f;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int width:g;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int height:h;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly byte[] pixels:i;
  #else
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly MapIcon[] cursors:c;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int startx:d;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int starty:e;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int width:f;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly int height:g;
    #require net.minecraft.network.protocol.game.PacketPlayOutMap private readonly byte[] pixels:h;
  #endif

    public boolean hasCursors() {
        MapIcon[] cursors = instance#cursors;
        return cursors != null;
    }
    public (java.util.List<org.bukkit.map.MapCursor>) java.util.List<MapIcon> getCursors() {
        MapIcon[] cursors = instance#cursors;
        return (cursors == null) ? java.util.Collections.emptyList() : java.util.Arrays.asList(cursors);
    }
    public int getStartX() {
        return instance#startx;
    }
    public int getStartY() {
        return instance#starty;
    }
    public int getWidth() {
        return instance#width;
    }
    public int getHeight() {
        return instance#height;
    }
    public boolean hasPixels() {
        byte[] pixels = instance#pixels;
        return pixels != null;
    }
    public byte[] getPixels() {
        return instance#pixels;
    }
#endif

    public static (PacketPlayOutMapHandle) PacketPlayOutMap createNew(PacketPlayOutMapHandle.Builder builder) {
#if version >= 1.17
        // Convert Bukkit MapCursor list -> MapIcon list
        java.util.List mapIconsList;
        if (builder.get_cursors_nms().isPresent()) {
            java.util.List mapIconHandleList = (java.util.List) builder.get_cursors_nms().get();
            if (mapIconHandleList.isEmpty()) {
                mapIconsList = java.util.Collections.emptyList();
            } else {
                mapIconsList = new java.util.ArrayList(mapIconHandleList.size());
                for (int i = 0; i < mapIconHandleList.size(); i++) {
                    MapIconHandle iconHandle = (MapIconHandle) mapIconHandleList.get(i);
                    mapIconsList.add(iconHandle.getRaw());
                }
            }
        } else {
            mapIconsList = null;
        }

        // Pack the pixel data into the PatchData class
        WorldMap$PatchData patchData;
        if (builder.has_data()) {
            patchData = new WorldMap$PatchData(builder.get_data_startX(), builder.get_data_startY(),
                                               builder.get_data_width(), builder.get_data_height(),
                                               builder.get_data_colors());
        } else {
            patchData = null;
        }
#else
        // Convert Bukkit MapCursor list -> MapIcon array
        MapIcon[] mapIconsArray;
        if (builder.get_cursors_nms().isPresent()) {
            java.util.List mapIconHandleList = (java.util.List) builder.get_cursors_nms().get();
            mapIconsArray = new MapIcon[mapIconHandleList.size()];
            for (int i = 0; i < mapIconsArray.length; i++) {
                MapIconHandle iconHandle = (MapIconHandle) mapIconHandleList.get(i);
                mapIconsArray[i] = (MapIcon) iconHandle.getRaw();
            }
        } else {
            mapIconsArray = new MapIcon[0];
        }
#endif

#if version >= 1.20.5
        java.util.Optional mapIconsOpt = builder.get_cursors().isPresent() ?
                java.util.Optional.of(mapIconsList) : java.util.Optional.empty();
        java.util.Optional patchDataOpt = java.util.Optional.ofNullable(patchData);
        return new PacketPlayOutMap(new net.minecraft.world.level.saveddata.maps.MapId(builder.get_mapId()),
                builder.get_scale(), builder.get_locked(), mapIconsOpt, patchDataOpt);
#elseif version >= 1.17
        return new PacketPlayOutMap(builder.get_mapId(), builder.get_scale(), builder.get_locked(), mapIconsList, patchData);
#else
        PacketPlayOutMap packet = new PacketPlayOutMap();

        #require net.minecraft.network.protocol.game.PacketPlayOutMap private int mapId:a;
        packet#mapId = builder.get_mapId();

        #require net.minecraft.network.protocol.game.PacketPlayOutMap private byte scale:b;
        packet#scale = builder.get_scale();

  #if version >= 1.14
        #require net.minecraft.network.protocol.game.PacketPlayOutMap private boolean locked:d;
        packet#locked = builder.get_locked();
  #endif

        packet#cursors = mapIconsArray;
        packet#startx = builder.get_data_startX();
        packet#starty = builder.get_data_startY();
        packet#width = builder.get_data_width();
        packet#height = builder.get_data_height();
        packet#pixels = builder.get_data_colors();
        return packet;
#endif
    }
}