package me.earth.phobos.util;

import java.util.HashMap;
import java.util.Map;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.init.Blocks;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;

public class HoleUtil {
  private static final Minecraft mc = Minecraft.getMinecraft();
  
  public static BlockSafety isBlockSafe(Block block) {
    if (block == Blocks.BEDROCK)
      return BlockSafety.UNBREAKABLE; 
    if (block == Blocks.OBSIDIAN || block == Blocks.ENDER_CHEST || block == Blocks.ANVIL)
      return BlockSafety.RESISTANT; 
    return BlockSafety.BREAKABLE;
  }
  
  public static HoleInfo isHole(BlockPos centreBlock, boolean onlyOneWide, boolean ignoreDown) {
    HoleInfo output = new HoleInfo();
    HashMap<BlockOffset, BlockSafety> unsafeSides = getUnsafeSides(centreBlock);
    if (unsafeSides.containsKey(BlockOffset.DOWN) && 
      unsafeSides.remove(BlockOffset.DOWN, BlockSafety.BREAKABLE) && 
      !ignoreDown) {
      output.setSafety(BlockSafety.BREAKABLE);
      return output;
    } 
    int size = unsafeSides.size();
    unsafeSides.entrySet().removeIf(entry -> (entry.getValue() == BlockSafety.RESISTANT));
    if (unsafeSides.size() != size)
      output.setSafety(BlockSafety.RESISTANT); 
    size = unsafeSides.size();
    if (size == 0) {
      output.setType(HoleType.SINGLE);
      output.setCentre(new AxisAlignedBB(centreBlock));
      return output;
    } 
    if (size == 1 && !onlyOneWide)
      return isDoubleHole(output, centreBlock, unsafeSides.keySet().stream().findFirst().get()); 
    output.setSafety(BlockSafety.BREAKABLE);
    return output;
  }
  
  private static HoleInfo isDoubleHole(HoleInfo info, BlockPos centreBlock, BlockOffset weakSide) {
    BlockPos unsafePos = weakSide.offset(centreBlock);
    HashMap<BlockOffset, BlockSafety> unsafeSides = getUnsafeSides(unsafePos);
    int size = unsafeSides.size();
    unsafeSides.entrySet().removeIf(entry -> (entry.getValue() == BlockSafety.RESISTANT));
    if (unsafeSides.size() != size)
      info.setSafety(BlockSafety.RESISTANT); 
    if (unsafeSides.containsKey(BlockOffset.DOWN)) {
      info.setType(HoleType.CUSTOM);
      unsafeSides.remove(BlockOffset.DOWN);
    } 
    if (unsafeSides.size() > 1) {
      info.setType(HoleType.NONE);
      return info;
    } 
    double minX = Math.min(centreBlock.getX(), unsafePos.getX());
    double maxX = (Math.max(centreBlock.getX(), unsafePos.getX()) + 1);
    double minZ = Math.min(centreBlock.getZ(), unsafePos.getZ());
    double maxZ = (Math.max(centreBlock.getZ(), unsafePos.getZ()) + 1);
    info.setCentre(new AxisAlignedBB(minX, centreBlock.getY(), minZ, maxX, (centreBlock.getY() + 1), maxZ));
    if (info.getType() != HoleType.CUSTOM)
      info.setType(HoleType.DOUBLE); 
    return info;
  }
  
  public static HashMap<BlockOffset, BlockSafety> getUnsafeSides(BlockPos pos) {
    HashMap<BlockOffset, BlockSafety> output = new HashMap<>();
    BlockSafety temp = isBlockSafe(mc.world.getBlockState(BlockOffset.DOWN.offset(pos)).getBlock());
    if (temp != BlockSafety.UNBREAKABLE)
      output.put(BlockOffset.DOWN, temp); 
    temp = isBlockSafe(mc.world.getBlockState(BlockOffset.NORTH.offset(pos)).getBlock());
    if (temp != BlockSafety.UNBREAKABLE)
      output.put(BlockOffset.NORTH, temp); 
    temp = isBlockSafe(mc.world.getBlockState(BlockOffset.SOUTH.offset(pos)).getBlock());
    if (temp != BlockSafety.UNBREAKABLE)
      output.put(BlockOffset.SOUTH, temp); 
    temp = isBlockSafe(mc.world.getBlockState(BlockOffset.EAST.offset(pos)).getBlock());
    if (temp != BlockSafety.UNBREAKABLE)
      output.put(BlockOffset.EAST, temp); 
    temp = isBlockSafe(mc.world.getBlockState(BlockOffset.WEST.offset(pos)).getBlock());
    if (temp != BlockSafety.UNBREAKABLE)
      output.put(BlockOffset.WEST, temp); 
    return output;
  }
  
  public enum BlockSafety {
    UNBREAKABLE, RESISTANT, BREAKABLE;
  }
  
  public enum HoleType {
    SINGLE, DOUBLE, CUSTOM, NONE;
  }
  
  public static class HoleInfo {
    private HoleUtil.HoleType type;
    
    private HoleUtil.BlockSafety safety;
    
    private AxisAlignedBB centre;
    
    public HoleInfo() {
      this(HoleUtil.BlockSafety.UNBREAKABLE, HoleUtil.HoleType.NONE);
    }
    
    public HoleInfo(HoleUtil.BlockSafety safety, HoleUtil.HoleType type) {
      this.type = type;
      this.safety = safety;
    }
    
    public void setType(HoleUtil.HoleType type) {
      this.type = type;
    }
    
    public void setSafety(HoleUtil.BlockSafety safety) {
      this.safety = safety;
    }
    
    public void setCentre(AxisAlignedBB centre) {
      this.centre = centre;
    }
    
    public HoleUtil.HoleType getType() {
      return this.type;
    }
    
    public HoleUtil.BlockSafety getSafety() {
      return this.safety;
    }
    
    public AxisAlignedBB getCentre() {
      return this.centre;
    }
  }
  
  public enum BlockOffset {
    DOWN(0, -1, 0),
    UP(0, 1, 0),
    NORTH(0, 0, -1),
    EAST(1, 0, 0),
    SOUTH(0, 0, 1),
    WEST(-1, 0, 0);
    
    private final int x;
    
    private final int y;
    
    private final int z;
    
    BlockOffset(int x, int y, int z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
    
    public BlockPos offset(BlockPos pos) {
      return pos.add(this.x, this.y, this.z);
    }
    
    public BlockPos forward(BlockPos pos, int scale) {
      return pos.add(this.x * scale, 0, this.z * scale);
    }
    
    public BlockPos backward(BlockPos pos, int scale) {
      return pos.add(-this.x * scale, 0, -this.z * scale);
    }
    
    public BlockPos left(BlockPos pos, int scale) {
      return pos.add(this.z * scale, 0, -this.x * scale);
    }
    
    public BlockPos right(BlockPos pos, int scale) {
      return pos.add(-this.z * scale, 0, this.x * scale);
    }
  }
}
