package net.minecraft.world.level.block.state;

import net.minecraft.core.BlockPosition;
import net.minecraft.core.EnumDirection;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BlockCarpet;
import net.minecraft.world.level.block.state.properties.IBlockState;
import net.minecraft.world.level.IBlockAccess;

import com.google.common.collect.ImmutableMap;

import com.bergerkiller.bukkit.common.collections.BlockFaceSet;

import com.bergerkiller.generated.net.minecraft.world.level.block.BlockHandle;
import com.bergerkiller.generated.net.minecraft.world.level.block.state.IBlockDataHandle;
import com.bergerkiller.generated.net.minecraft.world.level.block.state.properties.IBlockStateHandle;

interface IBlockData {
    public abstract (BlockHandle) Block getBlock();

    <code>
    public void logStates() {
        for (java.util.Map.Entry<IBlockStateHandle, Comparable<?>> entry : getStates().entrySet()) {
            com.bergerkiller.bukkit.common.Logging.LOGGER.info(entry.getKey() + " = " + entry.getValue());
        }
    }

    public IBlockStateHandle findState(String key) {
        for (IBlockStateHandle blockState : getStates().keySet()) {
            if (blockState.getKeyToken().equals(key)) {
                return blockState;
            }
        }
        return null;
    }

    public IBlockDataHandle set(String key, Object value) {
        return set(findState(key), value);
    }

    public <T> T get(String key, Class<T> type) {
        return get(findState(key), type);
    }

    public <T> T get(IBlockStateHandle state, Class<T> type) {
        return com.bergerkiller.bukkit.common.conversion.Conversion.convert(get(state), type, null);
    }
    </code>

    // World access require check
#if version >= 1.18
    #require net.minecraft.world.level.block.Block public boolean isWorldAccessRequired:hasDynamicShape();
#elseif version >= 1.16
    #require net.minecraft.world.level.block.Block public boolean isWorldAccessRequired:o();
#elseif version >= 1.15
    #require net.minecraft.world.level.block.Block public boolean isWorldAccessRequired:q();
#elseif version >= 1.14
    #require net.minecraft.world.level.block.Block public boolean isWorldAccessRequired:p();
#endif

    // Returns null when the opaque faces aren't cached
    public BlockFaceSet getCachedOpaqueFaces() {
        Block block = instance.getBlock();

#if version >= 1.14
        // This flag is set to true by default for all blocks
        // Blocks that pass light through at all times, have it set to false
        // Examples are: doors, trapdoors
  #if version >= 1.18
        if (!instance.canOcclude()) {
  #elseif version >= 1.16
        if (!instance.l()) {
  #else
        if (!instance.o()) {
  #endif
            return com.bergerkiller.bukkit.common.collections.BlockFaceSet.NONE;
        }

        // Some blocks must be overrided to allow light to pass through
        // There doesn't appear to be a property we can use to query this :(
        if (block instanceof BlockCarpet) {
            return com.bergerkiller.bukkit.common.collections.BlockFaceSet.NONE;
        }

        // Check world access isn't required (see: Block)
        boolean worldAccessRequired = block#isWorldAccessRequired();
        if (worldAccessRequired) {
            return null;
        }

        // Query all 6 faces and produce a mask
        int mask = 0;
        IBlockAccess emptyBlockAccess = net.minecraft.world.level.BlockAccessAir.INSTANCE;
  #if version >= 1.18
        if (instance.isFaceSturdy(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.NORTH)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_NORTH;
        }
        if (instance.isFaceSturdy(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.EAST)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_EAST;
        }
        if (instance.isFaceSturdy(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.SOUTH)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_SOUTH;
        }
        if (instance.isFaceSturdy(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.WEST)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_WEST;
        }
        if (instance.isFaceSturdy(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.UP)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_UP;
        }
        if (instance.isFaceSturdy(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.DOWN)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_DOWN;
        }
  #elseif version >= 1.14.4
        if (instance.d(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.NORTH)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_NORTH;
        }
        if (instance.d(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.EAST)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_EAST;
        }
        if (instance.d(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.SOUTH)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_SOUTH;
        }
        if (instance.d(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.WEST)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_WEST;
        }
        if (instance.d(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.UP)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_UP;
        }
        if (instance.d(emptyBlockAccess, BlockPosition.ZERO, EnumDirection.DOWN)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_DOWN;
        }
  #else
        // Awkward! It's not cached yet. Hopefully this works, though.
        if (Block.d(instance, emptyBlockAccess, BlockPosition.ZERO, EnumDirection.NORTH)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_NORTH;
        }
        if (Block.d(instance, emptyBlockAccess, BlockPosition.ZERO, EnumDirection.EAST)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_EAST;
        }
        if (Block.d(instance, emptyBlockAccess, BlockPosition.ZERO, EnumDirection.SOUTH)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_SOUTH;
        }
        if (Block.d(instance, emptyBlockAccess, BlockPosition.ZERO, EnumDirection.WEST)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_WEST;
        }
        if (Block.d(instance, emptyBlockAccess, BlockPosition.ZERO, EnumDirection.UP)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_UP;
        }
        if (Block.d(instance, emptyBlockAccess, BlockPosition.ZERO, EnumDirection.DOWN)) {
            mask |= com.bergerkiller.bukkit.common.collections.BlockFaceSet.MASK_DOWN;
        }
  #endif

        // Bugfix: sometimes faces is 'all' while opacity < 15
        // When this happens, it would make transparent blocks block all light
        // We obviously don't want that!
        if (mask == com.bergerkiller.bukkit.common.collections.BlockFaceSet.ALL.mask()) {
  #if version >= 1.18
            int opacity = instance.getLightBlock(emptyBlockAccess, BlockPosition.ZERO);
  #else
            int opacity = instance.b(emptyBlockAccess, BlockPosition.ZERO);
  #endif
            if (opacity < 15) {
                mask = 0;
            }
        }

        return com.bergerkiller.bukkit.common.collections.BlockFaceSet.byMask(mask);

#elseif version >= 1.9
        return block.isOccluding(instance) ? com.bergerkiller.bukkit.common.collections.BlockFaceSet.ALL
                                           : com.bergerkiller.bukkit.common.collections.BlockFaceSet.NONE;
#else
        return block.isOccluding() ? com.bergerkiller.bukkit.common.collections.BlockFaceSet.ALL
                                   : com.bergerkiller.bukkit.common.collections.BlockFaceSet.NONE;
#endif
    }

    // Returns -1 if world access is required to figure it out
#if version >= 1.14
    public int getCachedOpacity() {
        // Check world access isn't required (see: Block)
        Block block = instance.getBlock();
        boolean worldAccessRequired = block#isWorldAccessRequired();
        if (worldAccessRequired) {
            return -1;
        } else {
  #if version >= 1.18
            return instance.getLightBlock((IBlockAccess) net.minecraft.world.level.BlockAccessAir.INSTANCE, BlockPosition.ZERO);
  #else
            return instance.b((IBlockAccess) net.minecraft.world.level.BlockAccessAir.INSTANCE, BlockPosition.ZERO);
  #endif
        }
    }
#elseif version >= 1.13
    public int getCachedOpacity() {
        return -1;
    }
#elseif version >= 1.9
    public int getCachedOpacity:c();
#else
    // IBlockData has no properties, delegate to Block
    public int getCachedOpacity() {
  #if version >= 1.8.3
        return instance.getBlock().p();
  #else
        return instance.getBlock().n();
  #endif
    }
#endif

    public boolean isSolid() {
#if version >= 1.18
        return instance.getMaterial().isSolid();
#elseif version >= 1.9
        return instance.getMaterial().isBuildable();
#else
        return instance.getBlock().getMaterial().isBuildable();
#endif
    }

    public Object get((IBlockStateHandle) IBlockState state) {
        if (state != null) {
#if version >= 1.18
            return instance.getValue(state);
#else
            return instance.get(state);
#endif
        } else {
            return null;
        }
    }

    public (IBlockDataHandle) IBlockData set((IBlockStateHandle) IBlockState state, Object value) {
        if (state != null) {
#if version >= 1.18
            Class type = state.getValueClass();
#elseif version >= 1.16
            Class type = state.getType();
#else
            Class type = state.b();
#endif
            Object converted = com.bergerkiller.bukkit.common.conversion.Conversion.convert(value, type, null);
            if (converted != null) {
#if version >= 1.18
                return (IBlockData) instance.setValue(state, (Comparable) converted);
#else
                return (IBlockData) instance.set(state, (Comparable) converted);
#endif
            }
        }
        return instance;
    }

#select version >=
#case 1.18: public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:getValues();
#case 1.17: public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:getStateMap();
#case 1.13.2
  #if methodexists net.minecraft.world.level.block.state.IBlockDataHolder public abstract com.google.common.collect.ImmutableMap<IBlockState<?>, Comparable<?>> getStateMap();
      public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:getStateMap();
  #else
      public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:b();
  #endif
#case 1.13: public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:b();
#case 1.12: public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:t();
#case 1.11: public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:u();
#case 1.9:  public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState<?>, Comparable<?>> getStates:s();
#case else: public abstract (Map<IBlockStateHandle, Comparable<?>>) ImmutableMap<IBlockState, Comparable> getStates:b();
#endselect

}