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

import dev.crucifixbeta.api.events.eventbus.EventListener;
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.player.EntityUtil;
import dev.crucifixbeta.api.utils.player.MovementUtil;
import dev.crucifixbeta.api.utils.world.BlockPosX;
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.block.Blocks;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.PositionAndOnGround;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;

@Environment(EnvType.CLIENT)
public class Clip extends Module {
   public static Clip INSTANCE;
   private final EnumSetting<Clip.Mode> mode = this.add(new EnumSetting<>("Mode", Clip.Mode.Clip));
   private final SliderSetting delay = this.add(new SliderSetting("Delay", 5, 1, 10, () -> this.mode.is(Clip.Mode.Clip)));
   private final SliderSetting rotationDelay = this.add(new SliderSetting("RotationDelay", 100, 0, 500, () -> this.mode.is(Clip.Mode.NoPacket)));
   private final BooleanSetting obsidian = this.add(new BooleanSetting("Obsidian", false, () -> this.mode.is(Clip.Mode.NoPacket)));
   private final BooleanSetting move = this.add(new BooleanSetting("Move", true, () -> this.mode.is(Clip.Mode.NoPacket)));
   private final BooleanSetting moveIn = this.add(
      new BooleanSetting("MoveIn", true, () -> this.mode.is(Clip.Mode.NoPacket) && this.move.isOpen())
   );
   private final BooleanSetting invalid = this.add(new BooleanSetting("InvalidPacket", true, () -> this.mode.is(Clip.Mode.NoPacket)));
   private final Timer timer = new Timer();
   private boolean cancel = true;
   private int packets;
   private static final double MAGIC_OFFSET;

   public Clip() {
      super("Clip", Module.Category.Exploit);
      this.setChinese("\u5361\u5899");
      INSTANCE = this;
   }

   @Override
   public void onDisable() {
      this.packets = 0;
   }

   @Override
   public String getInfo() {
      return this.mode.is(Clip.Mode.NoPacket) ? String.valueOf(this.timer.getMs()) : String.valueOf(this.packets);
   }

   @Override
   public void onEnable() {
      if (!nullCheck()) {
         if (this.mode.is(Clip.Mode.NoPacket) && this.move.getValue()) {
            this.cancel = false;
            if (this.moveIn.getValue()) {
               Direction var1 = mc.player.getHorizontalFacing();
               mc.player.setPosition(mc.player.getX() + (double)var1.getOffsetX() * 0.5, mc.player.getY(), mc.player.getZ() + (double)var1.getOffsetZ() * 0.5);
               mc.getNetworkHandler().sendPacket(new PositionAndOnGround(mc.player.getX(), mc.player.getY(), mc.player.getZ(), true));
            } else {
               mc.getNetworkHandler().sendPacket(new PositionAndOnGround(mc.player.getX(), mc.player.getY(), mc.player.getZ(), true));
               mc.player
                  .setPosition(
                     this.roundToClosest(mc.player.getX(), Math.floor(mc.player.getX()) + 0.23, Math.floor(mc.player.getX()) + 0.77),
                     mc.player.getY(),
                     this.roundToClosest(mc.player.getZ(), Math.floor(mc.player.getZ()) + 0.23, Math.floor(mc.player.getZ()) + 0.77)
                  );
               mc.getNetworkHandler()
                  .sendPacket(
                     new PositionAndOnGround(
                        this.roundToClosest(mc.player.getX(), Math.floor(mc.player.getX()) + 0.23, Math.floor(mc.player.getX()) + 0.77),
                        mc.player.getY(),
                        this.roundToClosest(mc.player.getZ(), Math.floor(mc.player.getZ()) + 0.23, Math.floor(mc.player.getZ()) + 0.77),
                        true
                     )
                  );
            }

            this.cancel = true;
         }
      }
   }

   @EventListener
   public void onUpdate(UpdateEvent event) {
      if (this.mode.is(Clip.Mode.NoPacket)) {
         if (!this.insideBlock() && this.move.getValue()) {
            this.disable();
         }
      } else if (this.mode.is(Clip.Mode.Clip)) {
         if (MovementUtil.isMoving()) {
            this.packets = 0;
            return;
         }

         if ((double)mc.player.age % this.delay.getValue() == 0.0) {
            mc.player
               .setPosition(
                  mc.player.getX()
                     + MathHelper.clamp(
                        this.roundToClosest(mc.player.getX(), Math.floor(mc.player.getX()) + 0.241, Math.floor(mc.player.getX()) + 0.759) - mc.player.getX(),
                        -0.03,
                        0.03
                     ),
                  mc.player.getY(),
                  mc.player.getZ()
                     + MathHelper.clamp(
                        this.roundToClosest(mc.player.getZ(), Math.floor(mc.player.getZ()) + 0.241, Math.floor(mc.player.getZ()) + 0.759) - mc.player.getZ(),
                        -0.03,
                        0.03
                     )
               );
            mc.getNetworkHandler().sendPacket(new PositionAndOnGround(mc.player.getX(), mc.player.getY(), mc.player.getZ(), true));
            mc.getNetworkHandler()
               .sendPacket(
                  new PositionAndOnGround(
                     this.roundToClosest(mc.player.getX(), Math.floor(mc.player.getX()) + 0.23, Math.floor(mc.player.getX()) + 0.77),
                     mc.player.getY(),
                     this.roundToClosest(mc.player.getZ(), Math.floor(mc.player.getZ()) + 0.23, Math.floor(mc.player.getZ()) + 0.77),
                     true
                  )
               );
            this.packets++;
         }
      } else if (this.mode.is(Clip.Mode.HitboxDesync)) {
         Direction var2 = mc.player.getHorizontalFacing();
         Box var3 = mc.player.getBoundingBox();
         Vec3d var4 = var3.getCenter();
         Vec3d var5 = new Vec3d(var2.getUnitVector());
         Vec3d var6 = this.merge(new BlockPosX(var4).toCenterPos().add(0.0, -0.5, 0.0).add(var5.multiply(0.20000996883537)), var2);
         mc.player.setPosition(var6.x == 0.0 ? mc.player.getX() : var6.x, mc.player.getY(), var6.z == 0.0 ? mc.player.getZ() : var6.z);
         this.disable();
      }
   }

   private Vec3d merge(Vec3d keyCodec, Direction facing) {
      return new Vec3d(
         keyCodec.x * (double)Math.abs(facing.getOffsetX()),
         keyCodec.y * (double)Math.abs(facing.getOffsetY()),
         keyCodec.z * (double)Math.abs(facing.getOffsetZ())
      );
   }

   @EventListener
   public void onPacket(PacketEvent.Send event) {
      if (!nullCheck() && this.mode.is(Clip.Mode.NoPacket)) {
         Packet var2;
         if (this.cancel && (var2 = event.getPacket()) instanceof PlayerMoveC2SPacket) {
            PlayerMoveC2SPacket var3 = (PlayerMoveC2SPacket)var2;
            if (!this.insideBlock()) {
               if (this.move.getValue()) {
                  this.disable();
               }

               return;
            }

            if (var3.changesLook() && this.timer.passedMs(this.rotationDelay.getValue())) {
               float var4 = var3.getYaw(0.0F);
               float var5 = var3.getPitch(0.0F);
               this.cancel = false;
               if (this.invalid.getValue()) {
                  mc.getNetworkHandler().sendPacket(new Full(mc.player.getX(), mc.player.getY() + 1337.0, mc.player.getZ(), var4, var5, false));
                  mc.getNetworkHandler().sendPacket(new Full(mc.player.getX(), mc.player.getY() - 1337.0, mc.player.getZ(), var4, var5, false));
               } else {
                  mc.getNetworkHandler().sendPacket(new Full(mc.player.getX(), mc.player.getY() + 2.0, mc.player.getZ(), var4, var5, false));
                  mc.getNetworkHandler().sendPacket(new Full(mc.player.getX(), mc.player.getY() - 2.0, mc.player.getZ(), var4, var5, false));
               }

               this.cancel = true;
               this.timer.reset();
            }

            event.cancel();
         }
      }
   }

   public boolean insideBlock() {
      BlockPos var1 = EntityUtil.getPlayerPos(true);

      for (int var2 = -1; var2 <= 1; var2++) {
         for (int var3 = -1; var3 <= 1; var3++) {
            for (int var4 = -1; var4 <= 1; var4++) {
               BlockPos var5 = var1.add(var2, var3, var4);
               if ((
                     mc.world.getBlockState(var5).getBlock() == Blocks.BEDROCK
                        || mc.world.getBlockState(var5).getBlock() == Blocks.OBSIDIAN && this.obsidian.getValue()
                  )
                  && mc.player.getBoundingBox().intersects(new Box(var5))) {
                  return true;
               }
            }
         }
      }

      return false;
   }

   private double roundToClosest(double num, double low, double high) {
      double var7 = high - num;
      double var9 = num - low;
      return var7 > var9 ? low : high;
   }

   static {

      MAGIC_OFFSET = 0.20000996883537;
   }
@Environment(EnvType.CLIENT)
   public static enum Mode {
      HitboxDesync,
      Clip,
      NoPacket;

}
}
