package dev.crucifixbeta.mod.modules.impl.movement;

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.EntityVelocityUpdateEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.TickEvent;
import dev.crucifixbeta.api.events.impl.UpdateRotateEvent;
import dev.crucifixbeta.api.utils.math.Timer;
import dev.crucifixbeta.api.utils.player.EntityUtil;
import dev.crucifixbeta.api.utils.player.MovementUtil;
import dev.crucifixbeta.api.utils.world.BlockUtil;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.impl.exploit.Blink;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.EnumSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.text.DecimalFormat;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.FishingBobberEntity;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full;
import net.minecraft.network.packet.s2c.play.EntityStatusS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.AxisDirection;

@Environment(EnvType.CLIENT)
public class Velocity extends Module {
   public static Velocity INSTANCE;
   private final EnumSetting<Velocity.Mode> mode = this.add(new EnumSetting<>("Mode", Velocity.Mode.Plain));
   private final SliderSetting lagPause = this.add(
      new SliderSetting(
         "LagPause",
         50,
         0,
         500,
         () -> this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3) || this.mode.is(Velocity.Mode.Wall)
      )
   );
   private final BooleanSetting ignorePearlLag = this.add(
      new BooleanSetting(
            "IgnorePearlLag", true, () -> this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3) || this.mode.is(Velocity.Mode.Wall)
         )
         .setParent()
   );
   private final SliderSetting phaseTime = this.add(
      new SliderSetting(
         "PhaseTime",
         250,
         0,
         1000,
         () -> (this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3) || this.mode.is(Velocity.Mode.Wall)) && this.ignorePearlLag.isOpen()
      )
   );
   public final BooleanSetting noRotation = this.add(
      new BooleanSetting(
         "NoRotation", false, () -> this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3) || this.mode.is(Velocity.Mode.Wall)
      )
   );
   public final BooleanSetting flagInWall = this.add(
      new BooleanSetting(
            "FlagInWall", false, () -> this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3) || this.mode.is(Velocity.Mode.Wall)
         )
         .setParent()
   );
   public final BooleanSetting whenPushOutOfBlocks = this.add(
      new BooleanSetting(
         "WhilePushOut",
         false,
         () -> (this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3) || this.mode.is(Velocity.Mode.Wall)) && this.flagInWall.isOpen()
      )
   );
   private final BooleanSetting cancelAll = this.add(new BooleanSetting("CancelAll", false, () -> this.mode.getValue() != Velocity.Mode.None));
   private final SliderSetting horizontal = this.add(
      new SliderSetting("Horizontal", 0.0, 0.0, 100.0, 1.0, () -> !this.cancelAll.getValue() && this.mode.getValue() != Velocity.Mode.None)
   );
   private final SliderSetting vertical = this.add(
      new SliderSetting("Vertical", 0.0, 0.0, 100.0, 1.0, () -> !this.cancelAll.getValue() && this.mode.getValue() != Velocity.Mode.None)
   );
   private final BooleanSetting wallsGroundOnly = this.add(new BooleanSetting("GroundOnly", false, () -> this.mode.is(Velocity.Mode.Wall)));
   private final BooleanSetting wallsTrapped = this.add(new BooleanSetting("Trapped", false, () -> this.mode.is(Velocity.Mode.Wall)));
   public final BooleanSetting staticSetting = this.add(
      new BooleanSetting("Static", false, () -> this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3))
   );
   public final BooleanSetting whileLiquid = this.add(new BooleanSetting("WhileLiquid", false));
   public final BooleanSetting whileElytra = this.add(new BooleanSetting("FallFlying", false));
   public final BooleanSetting entityPush = this.add(new BooleanSetting("NoEntityPush", true));
   public final BooleanSetting blockPush = this.add(new BooleanSetting("NoBlockPush", true));
   public final BooleanSetting fishBob = this.add(new BooleanSetting("NoFishBob", true));
   public final BooleanSetting waterPush = this.add(new BooleanSetting("NoWaterPush", false));
   public final BooleanSetting noClimb = this.add(new BooleanSetting("NoClimb", false));
   private boolean shouldSkipNextZeroVelocity;
   private boolean flag;
   private static boolean pushOutOfBlocks;
   private final Timer lagBackTimer = new Timer();
   public final Timer pearlTimer = new Timer();

   public Velocity() {
      super("Velocity", Module.Category.Movement);
      this.setChinese("\u53cd\u51fb\u9000");
      INSTANCE = this;
   }

   @Override
   public String getInfo() {
      if (this.mode.getValue() == Velocity.Mode.None) {
         return null;
      } else if (this.cancelAll.getValue()) {
         return this.mode.getValue().name() + ", Cancel";
      } else {
         DecimalFormat var1 = new DecimalFormat("0.0");
         return this.mode.getValue().name() + ", H:" + var1.format(this.horizontal.getValue()) + "%, V:" + var1.format(this.vertical.getValue()) + "%";
      }
   }

   @Override
   public void onEnable() {
      this.shouldSkipNextZeroVelocity = false;
      this.flag = false;
   }

   @Override
   public void onDisable() {
      this.shouldSkipNextZeroVelocity = false;
      this.flag = false;
      pushOutOfBlocks = false;
   }

   @EventListener
   public void onRotate(UpdateRotateEvent event) {
      if (this.noRotation()) {
         event.setRotation(CrucifixBeta.ROTATION.rotationYaw, 89.0F);
      }
   }

   public boolean noRotation() {
      return this.isOn() && this.isGrimLike() && EntityUtil.isInsideBlock() && this.noRotation.getValue();
   }

   @EventListener
   public void onVelocity(EntityVelocityUpdateEvent event) {
      if (!nullCheck() && event.getEntity() == mc.player && this.mode.getValue() != Velocity.Mode.None) {
         if (!mc.player.isInFluid() || this.whileLiquid.getValue()) {
            if (!mc.player.isFallFlying() || this.whileElytra.getValue()) {
               if (this.cancelAll.getValue() && this.shouldSkipNextZeroVelocity && event.getX() == 0.0 && event.getY() == 0.0 && event.getZ() == 0.0) {
                  event.cancel();
                  this.shouldSkipNextZeroVelocity = false;
               } else {
                  switch ((Velocity.Mode)this.mode.getValue()) {
                     case Plain:
                     case None:
                     default:
                        break;
                     case Grim:
                     case GrimV3:
                        if (!this.lagBackTimer.passedMs(this.lagPause.getValue())) {
                           return;
                        }

                        if (!EntityUtil.isInsideBlock() && this.getPos() == null && (!this.staticSetting.getValue() || !MovementUtil.isStatic())) {
                           return;
                        }

                        if (event.getX() != 0.0 || event.getZ() != 0.0) {
                           this.flag = true;
                        }
                        break;
                     case Wall:
                        if (!this.lagBackTimer.passedMs(this.lagPause.getValue())) {
                           return;
                        }

                        if (!EntityUtil.isInsideBlock() && (!this.wallsTrapped.getValue() || !this.isWallsTrapped())) {
                           return;
                        }

                        if (this.wallsGroundOnly.getValue() && !mc.player.isOnGround()) {
                           return;
                        }

                        if (event.getX() != 0.0 || event.getZ() != 0.0) {
                           this.flag = true;
                        }
                  }

                  if (this.cancelAll.getValue()) {
                     event.cancel();
                  } else {
                     this.applyScale(event);
                  }
               }
            }
         }
      }
   }

   @EventListener
   public void onReceivePacket(PacketEvent.Receive event) {
      if (event.getPacket() instanceof PlayerPositionLookS2CPacket) {
         if (!this.ignorePearlLag.getValue() || this.pearlTimer.passed((long)this.phaseTime.getValueInt())) {
            this.lagBackTimer.reset();
         }

         if (this.cancelAll.getValue()) {
            this.shouldSkipNextZeroVelocity = true;
         }
      }

      if (!nullCheck() && (!mc.player.isInFluid() || this.whileLiquid.getValue())) {
         if (this.fishBob.getValue()
            && event.getPacket() instanceof EntityStatusS2CPacket var3
            && var3.getStatus() == 31
            && var3.getEntity(mc.world) instanceof FishingBobberEntity var4
            && var4.getHookedEntity() == mc.player) {
            event.setCancelled(true);
         }
      }
   }

   @EventListener
   public void onUpdate(TickEvent event) {
      if (!nullCheck() && !event.isPost()) {
         if (!mc.player.isInFluid() || this.whileLiquid.getValue()) {
            if (this.flagInWall.getValue()) {
               pushOutOfBlocks = false;
               double var2 = (double)mc.player.getWidth() * 0.35;
               pushOutOfBlocks(mc.player.getX() - var2, mc.player.getZ() + var2);
               pushOutOfBlocks(mc.player.getX() - var2, mc.player.getZ() - var2);
               pushOutOfBlocks(mc.player.getX() + var2, mc.player.getZ() - var2);
               pushOutOfBlocks(mc.player.getX() + var2, mc.player.getZ() + var2);
            }

            if (Blink.INSTANCE == null || !Blink.INSTANCE.isOn() || Blink.INSTANCE.pauseModule == null || !Blink.INSTANCE.pauseModule.getValue()) {
               if (this.flag) {
                  if (this.lagBackTimer.passedMs(this.lagPause.getValue())
                     && (this.flagInWall.getValue() && (!pushOutOfBlocks || this.whenPushOutOfBlocks.getValue()) || !EntityUtil.isInsideBlock())) {
                     mc.getNetworkHandler()
                        .sendPacket(
                           new Full(
                              mc.player.getX(),
                              mc.player.getY(),
                              mc.player.getZ(),
                              CrucifixBeta.ROTATION.rotationYaw,
                              CrucifixBeta.ROTATION.rotationPitch,
                              mc.player.isOnGround()
                           )
                        );
                     BlockPos var4 = this.getPos();
                     if (var4 != null) {
                        mc.getNetworkHandler()
                           .sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, var4, mc.player.getHorizontalFacing().getOpposite()));
                     }
                  }

                  this.flag = false;
               }
            }
         }
      }
   }

   @EventListener
   public void onPushEntity(Object event) {
      try {
         Entity var2 = (Entity)event.getClass().getMethod("getEntity").invoke(event);
         if (this.entityPush.getValue() && var2 == mc.player) {
            event.getClass().getMethod("setCancelled", boolean.class).invoke(event, true);
         }
      } catch (Exception var3) {
      }
   }

   @EventListener
   public void onPushOutOfBlocks(Object event) {
      if (this.blockPush.getValue()) {
         try {
            event.getClass().getMethod("setCancelled", boolean.class).invoke(event, true);
         } catch (Exception var3) {
         }
      }
   }

   private boolean isGrimLike() {
      return this.mode.is(Velocity.Mode.Grim) || this.mode.is(Velocity.Mode.GrimV3) || this.mode.is(Velocity.Mode.Wall);
   }

   private boolean isWallsTrapped() {
      BlockPos var1 = mc.player.getBlockPos().up(mc.player.isCrawling() ? 1 : 2);
      return !mc.world.getBlockState(var1).isReplaceable();
   }

   private void applyScale(EntityVelocityUpdateEvent event) {
      double var2 = this.horizontal.getValue() / 100.0;
      double var4 = this.vertical.getValue() / 100.0;
      event.setX(event.getX() * var2);
      event.setZ(event.getZ() * var2);
      event.setY(event.getY() * var4);
   }

   private BlockPos getPos() {
      BlockPos var1 = mc.player.getBlockPos().down();
      return mc.world.getBlockState(var1).getBlock() == Blocks.OBSIDIAN ? var1 : null;
   }

   private static void pushOutOfBlocks(double x, double z) {
      BlockPos var4 = BlockPos.ofFloored(x, mc.player.getY(), z);
      if (wouldCollideAt(var4)) {
         double var5 = x - (double)var4.getX();
         double var7 = z - (double)var4.getZ();
         Direction var9 = null;
         double var10 = Double.MAX_VALUE;
         Direction[] var12 = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH};

         for (Direction var16 : var12) {
            double var17 = var16.getAxis().choose(var5, 0.0, var7);
            double var19 = var16.getDirection() == AxisDirection.POSITIVE ? 1.0 - var17 : var17;
            if (var19 < var10 && !wouldCollideAt(var4.offset(var16))) {
               var10 = var19;
               var9 = var16;
            }
         }

         if (var9 != null) {
            pushOutOfBlocks = true;
         }
      }
   }

   private static boolean wouldCollideAt(BlockPos pos) {
      Box var1 = mc.player.getBoundingBox();
      Box var2 = new Box((double)pos.getX(), var1.minY, (double)pos.getZ(), (double)pos.getX() + 1.0, var1.maxY, (double)pos.getZ() + 1.0).contract(1.0E-7);
      return BlockUtil.canCollide(mc.player, var2);
   }
@Environment(EnvType.CLIENT)
   public static enum Mode {
      Plain,
      Grim,
      Wall,
      GrimV3,
      None;

}
}
