package net.minecraft.world.level.levelgen;

import net.minecraft.core.BlockPosition;
import net.minecraft.core.BlockPosition$MutableBlockPosition;
import net.minecraft.util.DataBits;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.chunk.Chunk;
import net.minecraft.world.level.chunk.IChunkAccess;

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

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

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

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

    public (ChunkHandle) Chunk getChunk() {
#if version >= 1.13
        return (Chunk) instance#chunk;
#else
        return (Chunk) 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 DataBits databits:data;
    #else
        #require net.minecraft.world.level.levelgen.HeightMap private final DataBits databits:c;
    #endif
        DataBits databits = instance#databits;
  #endif

        IChunkAccess ichunkaccess = instance#chunk;
        int databits_index = 0;
  #if version >= 1.18
        org.bukkit.World world = ((Chunk) ichunkaccess).getLevel().getWorld();
        int startX = ((Chunk) ichunkaccess).getPos().x << 4;
        int startY = ichunkaccess.getHighestSectionPosition() + 15;
        int startZ = ((Chunk) ichunkaccess).getPos().z << 4;
  #else
        org.bukkit.World world = ((Chunk) ichunkaccess).getWorld().getWorld();
        int startX = ((Chunk) ichunkaccess).getPos().x << 4;
        int startY = ichunkaccess.b() + 15;
        int startZ = ((Chunk) ichunkaccess).getPos().z << 4;
  #endif
        BlockPosition$MutableBlockPosition blockposition = new BlockPosition$MutableBlockPosition();
        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);
                    IBlockData iblockdata = ichunkaccess.getBlockState((BlockPosition) blockposition);
  #else
                    blockposition.d(x, y, z);
                    IBlockData iblockdata = ichunkaccess.getType((BlockPosition) 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 = ((Chunk) 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);

        ChunkHandle 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
}