package dev.crucifixbeta.api.utils.path;

import dev.crucifixbeta.api.utils.Wrapper;
import java.util.ArrayList;
import java.util.Comparator;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.CactusBlock;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.EnderChestBlock;
import net.minecraft.block.FenceBlock;
import net.minecraft.block.PaneBlock;
import net.minecraft.block.PistonBlock;
import net.minecraft.block.PistonExtensionBlock;
import net.minecraft.block.PistonHeadBlock;
import net.minecraft.block.SkullBlock;
import net.minecraft.block.SlabBlock;
import net.minecraft.block.StainedGlassBlock;
import net.minecraft.block.StairsBlock;
import net.minecraft.block.TrapdoorBlock;
import net.minecraft.block.WallBlock;
import net.minecraft.util.math.BlockPos;

@Environment(EnvType.CLIENT)
public class AStarCustomPathFinder implements Wrapper {
   private static final Vec3[] flatCardinalDirections = new Vec3[]{
      new Vec3(1.0, 0.0, 0.0), new Vec3(-1.0, 0.0, 0.0), new Vec3(0.0, 0.0, 1.0), new Vec3(0.0, 0.0, -1.0)
   };
   private final Vec3 startVec3;
   private final Vec3 endVec3;
   private final ArrayList<AStarCustomPathFinder.Hub> hubs = new ArrayList<>();
   private final ArrayList<AStarCustomPathFinder.Hub> hubsToWork = new ArrayList<>();
   private ArrayList<Vec3> path = new ArrayList<>();

   public AStarCustomPathFinder(Vec3 startVec3, Vec3 endVec3) {
      this.startVec3 = startVec3.addVector(0.0, 0.0, 0.0).floor();
      this.endVec3 = endVec3.addVector(0.0, 0.0, 0.0).floor();
   }

   public static boolean checkPositionValidity(Vec3 loc, boolean checkGround) {
      return checkPositionValidity((int)loc.x(), (int)loc.y(), (int)loc.z(), checkGround);
   }

   public static boolean checkPositionValidity(int x, int y, int z, boolean checkGround) {
      BlockPos var4 = new BlockPos(x, y, z);
      BlockPos var5 = new BlockPos(x, y + 1, z);
      BlockPos var6 = new BlockPos(x, y - 1, z);
      return !isBlockSolid(var4) && !isBlockSolid(var5) && (isBlockSolid(var6) || !checkGround) && isSafeToWalkOn(var6);
   }

   private static boolean isBlockSolid(BlockPos block) {
      return mc.world.getBlockState(block).shapeCache != null && mc.world.getBlockState(block).shapeCache.isFullCube
         || mc.world.getBlockState(block).getBlock() instanceof SlabBlock
         || mc.world.getBlockState(block).getBlock() instanceof StairsBlock
         || mc.world.getBlockState(block).getBlock() instanceof CactusBlock
         || mc.world.getBlockState(block).getBlock() instanceof ChestBlock
         || mc.world.getBlockState(block).getBlock() instanceof EnderChestBlock
         || mc.world.getBlockState(block).getBlock() instanceof SkullBlock
         || mc.world.getBlockState(block).getBlock() instanceof PaneBlock
         || mc.world.getBlockState(block).getBlock() instanceof FenceBlock
         || mc.world.getBlockState(block).getBlock() instanceof WallBlock
         || mc.world.getBlockState(block).getBlock() instanceof StainedGlassBlock
         || mc.world.getBlockState(block).getBlock() instanceof PistonBlock
         || mc.world.getBlockState(block).getBlock() instanceof PistonExtensionBlock
         || mc.world.getBlockState(block).getBlock() instanceof PistonHeadBlock
         || mc.world.getBlockState(block).getBlock() instanceof StainedGlassBlock
         || mc.world.getBlockState(block).getBlock() instanceof TrapdoorBlock;
   }

   private static boolean isSafeToWalkOn(BlockPos block) {
      return !(mc.world.getBlockState(block).getBlock() instanceof FenceBlock) && !(mc.world.getBlockState(block).getBlock() instanceof WallBlock);
   }

   public ArrayList<Vec3> getPath() {
      return this.path;
   }

   public void compute() {
      this.compute(1000, 4);
   }

   public void compute(int loops, int depth) {
      this.path.clear();
      this.hubsToWork.clear();
      ArrayList var3 = new ArrayList();
      var3.add(this.startVec3);
      this.hubsToWork.add(new AStarCustomPathFinder.Hub(this.startVec3, null, var3, this.startVec3.squareDistanceTo(this.endVec3), 0.0, 0.0));

      label52:
      for (int var4 = 0; var4 < loops; var4++) {
         this.hubsToWork.sort(new AStarCustomPathFinder.CompareHub());
         int var5 = 0;
         if (this.hubsToWork.isEmpty()) {
            break;
         }

         for (AStarCustomPathFinder.Hub var7 : new ArrayList<>(this.hubsToWork)) {
            if (++var5 <= depth) {
               this.hubsToWork.remove(var7);
               this.hubs.add(var7);

               for (Vec3 var11 : flatCardinalDirections) {
                  Vec3 var12 = var7.getLoc().add(var11).floor();
                  if (checkPositionValidity(var12, false) && this.addHub(var7, var12, 0.0)) {
                     break label52;
                  }
               }

               Vec3 var14 = var7.getLoc().addVector(0.0, 1.0, 0.0).floor();
               Vec3 var13;
               if (checkPositionValidity(var14, false) && this.addHub(var7, var14, 0.0)
                  || checkPositionValidity(var13 = var7.getLoc().addVector(0.0, -1.0, 0.0).floor(), false) && this.addHub(var7, var13, 0.0)) {
                  break label52;
               }
            }
         }
      }

      this.hubs.sort(new AStarCustomPathFinder.CompareHub());
      this.path = this.hubs.getFirst().getPath();
   }

   public AStarCustomPathFinder.Hub isHubExisting(Vec3 loc) {
      for (AStarCustomPathFinder.Hub var3 : this.hubs) {
         if (var3.getLoc().x() == loc.x() && var3.getLoc().y() == loc.y() && var3.getLoc().z() == loc.z()) {
            return var3;
         }
      }

      for (AStarCustomPathFinder.Hub var5 : this.hubsToWork) {
         if (var5.getLoc().x() == loc.x() && var5.getLoc().y() == loc.y() && var5.getLoc().z() == loc.z()) {
            return var5;
         }
      }

      return null;
   }

   public boolean addHub(AStarCustomPathFinder.Hub parent, Vec3 loc, double cost) {
      AStarCustomPathFinder.Hub var5 = this.isHubExisting(loc);
      double var6 = cost;
      if (parent != null) {
         var6 = cost + parent.getTotalCost();
      }

      if (var5 == null) {
         double var8 = 9.0;
         if (loc.x() == this.endVec3.x() && loc.y() == this.endVec3.y() && loc.z() == this.endVec3.z() || loc.squareDistanceTo(this.endVec3) <= var8) {
            this.path.clear();
            this.path = parent.getPath();
            this.path.add(loc);
            return true;
         }

         ArrayList var10 = new ArrayList<>(parent.getPath());
         var10.add(loc);
         this.hubsToWork.add(new AStarCustomPathFinder.Hub(loc, parent, var10, loc.squareDistanceTo(this.endVec3), cost, var6));
      } else if (var5.getCost() > cost) {
         ArrayList var11 = new ArrayList<>(parent.getPath());
         var11.add(loc);
         var5.setLoc(loc);
         var5.setParent(parent);
         var5.setPath(var11);
         var5.setSquareDistanceToFromTarget(loc.squareDistanceTo(this.endVec3));
         var5.setCost(cost);
         var5.setTotalCost(var6);
      }

      return false;
   }

   @Environment(EnvType.CLIENT)
   public static class CompareHub implements Comparator<AStarCustomPathFinder.Hub> {
      public int compare(AStarCustomPathFinder.Hub o1, AStarCustomPathFinder.Hub o2) {
         return (int)(o1.getSquareDistanceToFromTarget() + o1.getTotalCost() - (o2.getSquareDistanceToFromTarget() + o2.getTotalCost()));
      }
   }

   @Environment(EnvType.CLIENT)
   public static class Hub {
      private Vec3 loc;
      private AStarCustomPathFinder.Hub parent;
      private ArrayList<Vec3> path;
      private double squareDistanceToFromTarget;
      private double cost;
      private double totalCost;

      public Hub(Vec3 loc, AStarCustomPathFinder.Hub parent, ArrayList<Vec3> path, double squareDistanceToFromTarget, double cost, double totalCost) {
         this.loc = loc;
         this.parent = parent;
         this.path = path;
         this.squareDistanceToFromTarget = squareDistanceToFromTarget;
         this.cost = cost;
         this.totalCost = totalCost;
      }

      public Vec3 getLoc() {
         return this.loc;
      }

      public void setLoc(Vec3 loc) {
         this.loc = loc;
      }

      public AStarCustomPathFinder.Hub getParent() {
         return this.parent;
      }

      public void setParent(AStarCustomPathFinder.Hub parent) {
         this.parent = parent;
      }

      public ArrayList<Vec3> getPath() {
         return this.path;
      }

      public void setPath(ArrayList<Vec3> path) {
         this.path = path;
      }

      public double getSquareDistanceToFromTarget() {
         return this.squareDistanceToFromTarget;
      }

      public void setSquareDistanceToFromTarget(double squareDistanceToFromTarget) {
         this.squareDistanceToFromTarget = squareDistanceToFromTarget;
      }

      public double getCost() {
         return this.cost;
      }

      public void setCost(double cost) {
         this.cost = cost;
      }

      public double getTotalCost() {
         return this.totalCost;
      }

      public void setTotalCost(double totalCost) {
         this.totalCost = totalCost;
      }
   }
}
