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

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.KeyboardInputEvent;
import dev.crucifixbeta.api.events.impl.MoveEvent;
import dev.crucifixbeta.api.events.impl.MovedEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.UpdateEvent;
import dev.crucifixbeta.api.utils.math.Timer;
import dev.crucifixbeta.api.utils.path.BaritoneUtil;
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.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.EnumSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.ExplosionS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;

@Environment(EnvType.CLIENT)
public class Speed extends Module {
   public static Speed INSTANCE;
   private final EnumSetting<Speed.Mode> mode = this.add(new EnumSetting<>("Mode", Speed.Mode.Strafe));
   public final SliderSetting collideSpeed = this.add(
      new SliderSetting("CollideSpeed", 0.08, 0.0, 0.08, 0.01, () -> this.mode.is(Speed.Mode.Grim))
   );
   private final BooleanSetting strict = this.add(new BooleanSetting("Strict", true, () -> this.mode.is(Speed.Mode.Grim)));
   private final BooleanSetting boat = this.add(new BooleanSetting("BoatLongJump", true, () -> this.mode.is(Speed.Mode.Grim)));
   public final SliderSetting boatExpand = this.add(new SliderSetting("BoatExpand", 0.2, 0.0, 1.0, 0.01, () -> this.mode.is(Speed.Mode.Grim)));
   public final SliderSetting boatSpeed = this.add(new SliderSetting("BoatSpeed", 0.2, -2.0, 2.0, 0.01, () -> this.mode.is(Speed.Mode.Grim)));
   public final SliderSetting boatJump = this.add(new SliderSetting("BoatJump", 0.2, 0.0, 2.0, 0.01, () -> this.mode.is(Speed.Mode.Grim)));
   private final BooleanSetting inWater = this.add(new BooleanSetting("InWater", false, () -> !this.mode.is(Speed.Mode.Grim)));
   private final BooleanSetting inBlock = this.add(new BooleanSetting("InBlock", false, () -> !this.mode.is(Speed.Mode.Grim)));
   private final BooleanSetting airStop = this.add(new BooleanSetting("AirStop", false, () -> !this.mode.is(Speed.Mode.Grim)));
   private final SliderSetting lagTime = this.add(new SliderSetting("LagTime", 500.0, 0.0, 1000.0, 1.0, () -> !this.mode.is(Speed.Mode.Grim)));
   private final BooleanSetting jump = this.add(new BooleanSetting("Jump", true, () -> this.mode.is(Speed.Mode.Strafe)));
   private final SliderSetting strafeSpeed = this.add(
      new SliderSetting("Speed", 0.2873, 0.0, 1.0, 1.0E-4, () -> this.mode.is(Speed.Mode.Strafe))
   );
   private final BooleanSetting explosions = this.add(new BooleanSetting("ExplosionsBoost", false, () -> this.mode.is(Speed.Mode.Strafe)));
   private final BooleanSetting velocity = this.add(new BooleanSetting("VelocityBoost", true, () -> this.mode.is(Speed.Mode.Strafe)));
   private final SliderSetting multiplier = this.add(new SliderSetting("H-Factor", 1.0, 0.0, 5.0, 0.01, () -> this.mode.is(Speed.Mode.Strafe)));
   private final SliderSetting vertical = this.add(new SliderSetting("V-Factor", 1.0, 0.0, 5.0, 0.01, () -> this.mode.is(Speed.Mode.Strafe)));
   private final SliderSetting coolDown = this.add(
      new SliderSetting("CoolDown", 1000.0, 0.0, 5000.0, 1.0, () -> this.mode.is(Speed.Mode.Strafe))
   );
   private final BooleanSetting slow = this.add(new BooleanSetting("Slowness", false, () -> this.mode.is(Speed.Mode.Strafe)));
   private final BooleanSetting fastSwim = this.add(new BooleanSetting("FastSwim", false).setParent());
   private final SliderSetting swimSpeed = this.add(new SliderSetting("SwimSpeed", 0.2, 0.0, 1.0, 0.01, this.fastSwim::isOpen));
   private final SliderSetting swimDownFactor = this.add(new SliderSetting("SwimDownFactor", 0.0, 0.0, 1.0, 1.0E-6, this.fastSwim::isOpen));
   private final SliderSetting swimDownSpeed = this.add(new SliderSetting("SwimDownSpeed", 0.2, 0.0, 1.0, 0.01, this.fastSwim::isOpen));
   private final SliderSetting swimUpSpeed = this.add(new SliderSetting("SwimUpSpeed", 0.2, 0.0, 1.0, 0.01, this.fastSwim::isOpen));
   private final Timer expTimer = new Timer();
   private final Timer lagTimer = new Timer();
   private boolean stop;
   private double speed;
   private double getDistance;
   private int strictTicks;
   private int strafe = 4;
   private int stage;
   private double lastExp;
   private boolean boost;

   public Speed() {
      super("Speed", Module.Category.Movement);
      this.setChinese("\u52a0\u901f");
      INSTANCE = this;
   }

   @Override
   public String getInfo() {
      return this.mode.getValue().name();
   }

   @Override
   public void onEnable() {
      if (mc.player != null) {
         this.speed = MovementUtil.getSpeed(false);
         this.getDistance = MovementUtil.getDistance2D();
      }

      this.stage = 4;
   }

   @EventListener(
      priority = 100
   )
   public void invoke(PacketEvent.Receive event) {
      if (!BaritoneUtil.isActive()) {
         if (this.mode.is(Speed.Mode.Strafe)) {
            if (event.getPacket() instanceof EntityVelocityUpdateS2CPacket var3) {
               if (mc.player != null && var3.getEntityId() == mc.player.getId() && this.velocity.getValue()) {
                  double var4 = Math.sqrt(var3.getVelocityX() * var3.getVelocityX() + var3.getVelocityZ() * var3.getVelocityZ());
                  double var6 = this.lastExp = this.expTimer.passed((long)this.coolDown.getValueInt()) ? var4 : var4 - this.lastExp;
                  if (this.lastExp > 0.0) {
                     this.expTimer.reset();
                     this.speed = this.speed + this.lastExp * this.multiplier.getValue();
                     this.getDistance = this.getDistance + this.lastExp * this.multiplier.getValue();
                     if (MovementUtil.getMotionY() > 0.0 && this.vertical.getValue() != 0.0) {
                        MovementUtil.setMotionY(MovementUtil.getMotionY() * this.vertical.getValue());
                     }
                  }
               }
            } else if (event.getPacket() instanceof ExplosionS2CPacket var8 && this.explosions.getValue()) {
               Vec3d var9 = new Vec3d(var8.getX(), var8.getY(), var8.getZ());
               if (mc.player.getPos().distanceTo(var9) < 15.0) {
                  double var13 = Math.sqrt(
                     (double)(var8.getPlayerVelocityX() * var8.getPlayerVelocityX() + var8.getPlayerVelocityZ() * var8.getPlayerVelocityZ())
                  );
                  double var10 = this.lastExp = this.expTimer.passed((long)this.coolDown.getValueInt()) ? var13 : var13 - this.lastExp;
                  if (this.lastExp > 0.0) {
                     this.expTimer.reset();
                     this.speed = this.speed + this.lastExp * this.multiplier.getValue();
                     this.getDistance = this.getDistance + this.lastExp * this.multiplier.getValue();
                     if (MovementUtil.getMotionY() > 0.0) {
                        MovementUtil.setMotionY(MovementUtil.getMotionY() * this.vertical.getValue());
                     }
                  }
               }
            }
         }

         if (event.getPacket() instanceof PlayerPositionLookS2CPacket) {
            this.lagTimer.reset();
            this.resetStrafe();
         }
      }
   }

   @EventListener
   public void onMove(MovedEvent event) {
      if (!nullCheck()) {
         double var2 = mc.player.getX() - mc.player.prevX;
         double var4 = mc.player.getZ() - mc.player.prevZ;
         this.getDistance = Math.sqrt(var2 * var2 + var4 * var4);
      }
   }

   @EventListener
   public void onUpdate(UpdateEvent event) {
      if (this.mode.is(Speed.Mode.Grim)) {
         if (!MovementUtil.isMoving()) {
            return;
         }

         int var2 = 0;
         Box var3 = this.strict.getValue() ? mc.player.getBoundingBox() : mc.player.getBoundingBox().expand(1.0);

         for (Entity var5 : CrucifixBeta.THREAD.getEntities()) {
            Box var6 = var5.getBoundingBox();
            if (this.boat.getValue() && mc.player.isOnGround() && var5 instanceof BoatEntity && var3.intersects(var6.expand(this.boatExpand.getValue()))) {
               double var7 = Math.toRadians((double)Sprint.getSprintYaw(mc.player.getYaw()));
               double var9 = this.boatSpeed.getValue();
               mc.player.setVelocity(-Math.sin(var7) * var9, this.boatJump.getValue(), Math.cos(var7) * var9);
               return;
            }

            if (var3.intersects(var6) && this.canCauseSpeed(var5)) {
               var2++;
            }
         }

         double var11 = Math.toRadians((double)Sprint.getSprintYaw(mc.player.getYaw()));
         double var13 = this.collideSpeed.getValue() * (double)var2;
         mc.player.addVelocity(-Math.sin(var11) * var13, 0.0, Math.cos(var11) * var13);
      }
   }

   @EventListener
   public void onKeyboardInput(KeyboardInputEvent event) {
      if (this.fastSwim.getValue() && mc.player != null && mc.player.isInFluid()) {
         mc.player.input.sneaking = false;
      }
   }

   private boolean canCauseSpeed(Entity entity) {
      return entity != mc.player && entity instanceof LivingEntity && !(entity instanceof ArmorStandEntity);
   }

   @EventListener
   public void invoke(MoveEvent event) {
      if (this.fastSwim.getValue() && mc.player.isInFluid()) {
         this.handleFastSwim(event);
      } else {
         if (!MovementUtil.isMoving() && this.airStop.getValue() && !this.mode.is(Speed.Mode.Grim)) {
            MovementUtil.setMotionX(0.0);
            MovementUtil.setMotionZ(0.0);
         }

         if ((this.inWater.getValue() || !mc.player.isSubmergedInWater() && !mc.player.isTouchingWater() && !mc.player.isInLava())
            && !mc.player.isRiding()
            && !mc.player.isHoldingOntoLadder()
            && (this.inBlock.getValue() || !EntityUtil.isInsideBlock())
            && !mc.player.getAbilities().flying
            && !mc.player.isFallFlying()
            && MovementUtil.isMoving()) {
            if (!this.mode.is(Speed.Mode.Strafe)) {
               double var18 = 1.0;
               double var19 = 1.0;
               if (mc.player.hasStatusEffect(StatusEffects.SPEED)) {
                  double var8 = (double)mc.player.getStatusEffect(StatusEffects.SPEED).getAmplifier();
                  var18 = 1.0 + 0.2 * (var8 + 1.0);
               }

               if (mc.player.hasStatusEffect(StatusEffects.SLOWNESS)) {
                  double var21 = (double)mc.player.getStatusEffect(StatusEffects.SLOWNESS).getAmplifier();
                  var19 = 1.0 + 0.2 * (var21 + 1.0);
               }

               double var20 = 0.2873F * var18 / var19;
               float var10 = 0.0F;
               if (mc.player.hasStatusEffect(StatusEffects.JUMP_BOOST)) {
                  var10 += (float)(mc.player.getStatusEffect(StatusEffects.JUMP_BOOST).getAmplifier() + 1) * 0.1F;
               }

               if (this.mode.getValue() == Speed.Mode.StrafeStrict) {
                  if (!this.lagTimer.passed((long)this.lagTime.getValueInt())) {
                     return;
                  }

                  if (this.strafe == 1) {
                     this.speed = 1.35F * var20 - 0.01F;
                  } else if (this.strafe == 2) {
                     if (mc.player.input.jumping || !mc.player.isOnGround()) {
                        return;
                     }

                     float var11 = 0.39999995F + var10;
                     event.setY((double)var11);
                     MovementUtil.setMotionY((double)var11);
                     this.speed *= 2.149;
                  } else if (this.strafe == 3) {
                     double var12 = 0.66 * (this.getDistance - var20);
                     this.speed = this.getDistance - var12;
                  } else {
                     if ((
                           !mc.world.isSpaceEmpty(mc.player, mc.player.getBoundingBox().offset(0.0, mc.player.getVelocity().getY(), 0.0))
                              || mc.player.verticalCollision
                        )
                        && this.strafe > 0) {
                        this.strafe = 1;
                     }

                     this.speed = this.getDistance - this.getDistance / 159.0;
                  }

                  this.strictTicks++;
                  this.speed = Math.max(this.speed, var20);
                  double var22 = 0.465 * var18 / var19;
                  double var14 = 0.44 * var18 / var19;
                  this.speed = Math.min(this.speed, this.strictTicks > 25 ? var22 : var14);
                  if (this.strictTicks > 50) {
                     this.strictTicks = 0;
                  }

                  Vec2f var16 = this.handleStrafeMotion((float)this.speed);
                  event.setX((double)var16.x);
                  event.setZ((double)var16.y);
                  this.strafe++;
               }
            } else if (this.stop) {
               this.stop = false;
            } else if (this.lagTimer.passed((long)this.lagTime.getValueInt())) {
               if (this.stage == 1) {
                  this.speed = 1.35 * MovementUtil.getSpeed(this.slow.getValue(), this.strafeSpeed.getValue()) - 0.01;
               } else if (this.stage != 2 || !mc.player.isOnGround() || !mc.options.jumpKey.isPressed() && !this.jump.getValue()) {
                  if (this.stage == 3) {
                     this.speed = this.getDistance - 0.66 * (this.getDistance - MovementUtil.getSpeed(this.slow.getValue(), this.strafeSpeed.getValue()));
                     this.boost = !this.boost;
                  } else {
                     if ((BlockUtil.canCollide(null, mc.player.getBoundingBox().offset(0.0, MovementUtil.getMotionY(), 0.0)) || mc.player.collidedSoftly)
                        && this.stage > 0) {
                        this.stage = 1;
                     }

                     this.speed = this.getDistance - this.getDistance / 159.0;
                  }
               } else {
                  double var2 = 0.3999 + MovementUtil.getJumpSpeed();
                  MovementUtil.setMotionY(var2);
                  event.setY(var2);
                  this.speed = this.speed * (this.boost ? 1.6835 : 1.395);
               }

               this.speed = Math.min(this.speed, 10.0);
               this.speed = Math.max(this.speed, MovementUtil.getSpeed(this.slow.getValue(), this.strafeSpeed.getValue()));
               double var17 = (double)mc.player.input.movementForward;
               double var4 = (double)mc.player.input.movementSideways;
               double var6 = (double)mc.player.getYaw();
               if (var17 == 0.0 && var4 == 0.0) {
                  event.setX(0.0);
                  event.setZ(0.0);
               } else if (var17 != 0.0 && var4 != 0.0) {
                  var17 *= Math.sin(Math.PI / 4);
                  var4 *= Math.cos(Math.PI / 4);
               }

               event.setX((var17 * this.speed * -Math.sin(Math.toRadians(var6)) + var4 * this.speed * Math.cos(Math.toRadians(var6))) * 0.99);
               event.setZ((var17 * this.speed * Math.cos(Math.toRadians(var6)) - var4 * this.speed * -Math.sin(Math.toRadians(var6))) * 0.99);
               this.stage++;
            }
         } else {
            this.resetStrafe();
            this.stop = true;
         }
      }
   }

   private void handleFastSwim(MoveEvent event) {
      if (mc.options.sneakKey.isPressed() && mc.player.input.jumping) {
         event.setY(0.0);
         MovementUtil.setMotionY(0.0);
      } else if (mc.options.sneakKey.isPressed()) {
         event.setY(-this.swimDownSpeed.getValue());
         MovementUtil.setMotionY(-this.swimDownSpeed.getValue());
      } else if (mc.player.input.jumping) {
         event.setY(this.swimUpSpeed.getValue());
         MovementUtil.setMotionY(this.swimUpSpeed.getValue());
      } else {
         event.setY(-this.swimDownFactor.getValue());
         MovementUtil.setMotionY(-this.swimDownFactor.getValue());
      }

      double[] var2 = MovementUtil.directionSpeed(this.swimSpeed.getValue());
      event.setX(var2[0]);
      event.setZ(var2[1]);
      MovementUtil.setMotionX(var2[0]);
      MovementUtil.setMotionZ(var2[1]);
   }

   public Vec2f handleStrafeMotion(float speed) {
      float var2 = mc.player.input.movementForward;
      float var3 = mc.player.input.movementSideways;
      float var4 = mc.player.prevYaw + (mc.player.getYaw() - mc.player.prevYaw) * mc.getRenderTickCounter().getTickDelta(true);
      if (var2 == 0.0F && var3 == 0.0F) {
         return Vec2f.ZERO;
      } else {
         if (var2 != 0.0F) {
            if (var3 >= 1.0F) {
               var4 += var2 > 0.0F ? -45.0F : 45.0F;
               var3 = 0.0F;
            } else if (var3 <= -1.0F) {
               var4 += var2 > 0.0F ? 45.0F : -45.0F;
               var3 = 0.0F;
            }

            if (var2 > 0.0F) {
               var2 = 1.0F;
            } else if (var2 < 0.0F) {
               var2 = -1.0F;
            }
         }

         float var5 = (float)Math.cos(Math.toRadians((double)var4));
         float var6 = (float)(-Math.sin(Math.toRadians((double)var4)));
         return new Vec2f(var2 * speed * var6 + var3 * speed * var5, var2 * speed * var5 - var3 * speed * var6);
      }
   }

   public void resetStrafe() {
      this.strafe = 4;
      this.strictTicks = 0;
      this.speed = 0.0;
      this.getDistance = 0.0;
   }
@Environment(EnvType.CLIENT)
   public static enum Mode {
      Strafe,
      StrafeStrict,
      Grim;

}
}
