package net.minecraft.world.level.levelgen;

import net.minecraft.core.BlockPos;
import net.minecraft.core.BlockPos$MutableBlockPos;
import net.minecraft.util.BitStorage;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ChunkAccess;

import io.github.opencubicchunks.cubicchunks.api.world.ICubicWorld;

import com.bergerkiller.generated.net.minecraft.world.level.chunk.LevelChunkHandle;

class Heightmap {
    #bootstrap com.bergerkiller.bukkit.common.internal.CommonBootstrap.initServer();

#if version >= 1.17
    #require net.minecraft.world.level.levelgen.Heightmap private final ChunkAccess chunk;
#elseif version >= 1.14
    #require net.minecraft.world.level.levelgen.Heightmap private final ChunkAccess chunk:e;
#elseif version >= 1.13
    #require net.minecraft.world.level.levelgen.Heightmap private final ChunkAccess chunk:c;
#endif

    public (LevelChunkHandle) LevelChunk getChunk() {
#if version >= 1.13
        return (LevelChunk) instance#chunk;
#else
        return (LevelChunk) instance.chunk;
#endif
    }

#if version >= 1.18
    public int getHeight:getFirstAvailable(int x, int z);
    private void setHeight(int x, int z, int height);
#elseif version >= 1.13
    public int getHeight:a(int x, int z);
    private void setHeight:a(int x, int z, int height);
#else
    public int getHeight(int x, int z);
    public void setHeight(int x, int z, int height);
#endif

#if version >= 1.14
    // Since version 1.14 the heightmap is no longer used for light calculations
    // We have to recalculate one ourselves, with our own rules
    public void initialize() {
  #if exists net.minecraft.world.level.levelgen.Heightmap private final char[] heightmap;
        // Tuinity
        #require net.minecraft.world.level.levelgen.Heightmap private final char[] databits:heightmap;
        char[] databits = instance#databits;
  #else
        // Standard spigot
    #if version >= 1.17
        #require net.minecraft.world.level.levelgen.Heightmap private final BitStorage databits:data;
    #else
        #require net.minecraft.world.level.levelgen.Heightmap private final BitStorage databits:c;
    #endif
        BitStorage databits = instance#databits;
  #endif

        ChunkAccess ichunkaccess = instance#chunk;
        int databits_index = 0;
  #if version >= 26.1
        int chunkX = ((LevelChunk) ichunkaccess).getPos().x();
        int chunkZ = ((LevelChunk) ichunkaccess).getPos().z();
  #else
        int chunkX = ((LevelChunk) ichunkaccess).getPos().x;
        int chunkZ = ((LevelChunk) ichunkaccess).getPos().z;
  #endif
  #if version >= 1.18
        org.bukkit.World world = ((LevelChunk) ichunkaccess).getLevel().getWorld();
        int startX = chunkX << 4;
        int startY = ichunkaccess.getHighestSectionPosition() + 15;
        int startZ = chunkZ << 4;
  #else
        org.bukkit.World world = ((LevelChunk) ichunkaccess).getWorld().getWorld();
        int startX = chunkX << 4;
        int startY = ichunkaccess.b() + 15;
        int startZ = chunkZ << 4;
  #endif
        BlockPos$MutableBlockPos blockposition = new BlockPos$MutableBlockPos();
        for (int z = 0; z < 16; ++z) {
            for (int x = 0; x < 16; ++x) {
                int y = startY;
                while (y > 0) {
  #if version >= 1.18
                    blockposition.set(x, y, z);
                    BlockState iblockdata = ichunkaccess.getBlockState((BlockPos) blockposition);
  #else
                    blockposition.d(x, y, z);
                    BlockState iblockdata = ichunkaccess.getType((BlockPos) blockposition);
  #endif

                    // Optimization for air blocks
                    Block block = iblockdata.getBlock();
                    if (block == Blocks.AIR) {
                        --y;
                        continue;
                    }

                    // Turn into BKCommonLib BlockData to read material properties we need
                    com.bergerkiller.bukkit.common.wrappers.BlockData bkc_blockdata;
                    bkc_blockdata = com.bergerkiller.bukkit.common.wrappers.BlockDataRegistry.fromBlockData((Object) iblockdata);

                    // If opacity is nonzero, stop
                    if (bkc_blockdata.getOpacity(world, startX + x, y, startZ + z) > 0) {
                        break;
                    }

                    // If top blockface is opaque, don't progress y and stop
                    com.bergerkiller.bukkit.common.collections.BlockFaceSet bkc_opaquefaces;
                    bkc_opaquefaces = bkc_blockdata.getOpaqueFaces(world, startX + x, y, startZ + z);
                    if (bkc_opaquefaces.up()) {
                        break;
                    }

                    // Next block
                    --y;

                    // If bottom blockface was opaque, stop (but allow block itself to be lit)
                    if (bkc_opaquefaces.down()) {
                        break;
                    }
                }

  #if version >= 1.21.2
                // Adjust for world height offset
                y -= ichunkaccess.getMinY();
  #elseif version >= 1.17
                // Adjust for world height offset
                y -= ichunkaccess.getMinBuildHeight();
  #endif

                // Store the value
  #if exists net.minecraft.world.level.levelgen.Heightmap private final char[] heightmap;
                // Tuinity
                databits[databits_index++] = (char) (y+1);
  #elseif version >= 1.18
                // Standard spigot (with mojangmap method names)
                databits.set(databits_index++, y+1);
  #else
                // Standard spigot
                databits.a(databits_index++, y+1);
  #endif
            }
        }
    }
#elseif version == 1.13
    public void initialize:a();
#elseif version == 1.13.1
    public void initialize:a();
#elseif version == 1.13.2
    public void initialize:a();
#else
    // On versions before 1.12.2 use our own implementation
    // The server offers no method on these versions to do so.
    public void initialize() {
        // Do not do this when this is a CubicChunks world (!!!)
  #if exists io.github.opencubicchunks.cubicchunks.api.world.ICube
        {
            World world = ((LevelChunk) instance.chunk).getWorld();
            if (world instanceof ICubicWorld && ((ICubicWorld) world).isCubicWorld()) {
                return;
            }
        }
  #endif

        com.bergerkiller.generated.net.minecraft.world.level.levelgen.HeightmapHandle handle;
        handle = com.bergerkiller.generated.net.minecraft.world.level.levelgen.HeightmapHandle.createHandle(instance);

        LevelChunkHandle chunkHandle = handle.getChunk();
        org.bukkit.World world = chunkHandle.getBukkitChunk().getWorld();
        int baseX = chunkHandle.getLocX() << 4;
        int baseZ = chunkHandle.getLocZ() << 4;
        int highestY = chunkHandle.getTopSliceY() + 16;
        for (int x = 0; x < 16; ++x) {
            for (int z = 0; z < 16; ++z) {
                int y = highestY;
                while (y > 0) {
                    com.bergerkiller.bukkit.common.wrappers.BlockData blockData = chunkHandle.getBlockDataAtCoord(x, y - 1, z);
                    if (blockData == com.bergerkiller.bukkit.common.wrappers.BlockData.AIR || blockData.getOpacity(world, baseX+x, y, baseZ+z) == 0) {
                        --y;
                        continue;
                    }
                    break;
                }
                handle.setHeight(x, z, y);
            }
        }
    }
#endif
}