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

import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.MoveEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.TickEvent;
import dev.crucifixbeta.api.events.impl.UpdateEvent;
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 io.netty.util.internal.ConcurrentSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.network.packet.c2s.play.TeleportConfirmC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.LookAndOnGround;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.PositionAndOnGround;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.util.math.Vec3d;

@Environment(EnvType.CLIENT)
public class PacketFly extends Module {
   public static PacketFly INSTANCE;
   public final EnumSetting<PacketFly.Mode> packetMode = this.add(
      new EnumSetting<>("PacketMode", PacketFly.Mode.Factor, this::packetSettingsVisible)
   );
   public final SliderSetting factor = this.add(new SliderSetting("PacketFactor", 1.0, 0.0, 10.0, this::packetSettingsVisible));
   public final EnumSetting<PacketFly.Phase> phase = this.add(new EnumSetting<>("PacketPhase", PacketFly.Phase.Full, this::packetSettingsVisible));
   public final EnumSetting<PacketFly.Type> type = this.add(new EnumSetting<>("PacketType", PacketFly.Type.Up, this::packetSettingsVisible));
   public final BooleanSetting antiKick = this.add(new BooleanSetting("PacketAntiKick", true, this::packetSettingsVisible));
   public final BooleanSetting noRotation = this.add(new BooleanSetting("PacketNoRotation", false, this::packetSettingsVisible));
   public final BooleanSetting noMovePacket = this.add(new BooleanSetting("PacketNoMovePacket", false, this::packetSettingsVisible));
   public final BooleanSetting bbOffset = this.add(new BooleanSetting("PacketBB-Offset", false, this::packetSettingsVisible));
   public final SliderSetting invalidY = this.add(new SliderSetting("PacketInvalid-Offset", 1337, 0, 1337, this::packetSettingsVisible));
   public final SliderSetting invalids = this.add(new SliderSetting("PacketInvalids", 1, 0, 10, this::packetSettingsVisible));
   public final SliderSetting sendTeleport = this.add(new SliderSetting("PacketTeleport", 1, 0, 10, this::packetSettingsVisible));
   public final SliderSetting concealY = this.add(new SliderSetting("PacketC-Y", 0.0, -256.0, 256.0, this::packetSettingsVisible));
   public final SliderSetting conceal = this.add(new SliderSetting("PacketC-Multiplier", 1.0, 0.0, 2.0, this::packetSettingsVisible));
   public final SliderSetting ySpeed = this.add(new SliderSetting("PacketY-Multiplier", 1.0, 0.0, 2.0, this::packetSettingsVisible));
   public final SliderSetting xzSpeed = this.add(new SliderSetting("PacketX/Z-Multiplier", 1.0, 0.0, 2.0, this::packetSettingsVisible));
   public final BooleanSetting elytra = this.add(new BooleanSetting("PacketElytra", false, this::packetSettingsVisible));
   public final BooleanSetting xzJitter = this.add(new BooleanSetting("PacketJitter-XZ", false, this::packetSettingsVisible));
   public final BooleanSetting yJitter = this.add(new BooleanSetting("PacketJitter-Y", false, this::packetSettingsVisible));
   public final BooleanSetting zeroSpeed = this.add(new BooleanSetting("PacketZero-Speed", false, this::packetSettingsVisible));
   public final BooleanSetting zeroY = this.add(new BooleanSetting("PacketZero-Y", false, this::packetSettingsVisible));
   public final BooleanSetting zeroTeleport = this.add(new BooleanSetting("PacketZero-Teleport", true, this::packetSettingsVisible));
   public final SliderSetting zoomer = this.add(new SliderSetting("PacketZoomies", 3, 0, 10, this::packetSettingsVisible));
   public final Map<Integer, PacketFly.TimeVec> posLooks = new ConcurrentHashMap<>();
   public final Set<Packet<?>> playerPackets = new ConcurrentSet();
   public final AtomicInteger teleportID = new AtomicInteger();
   public Vec3d vecDelServer;
   public int packetCounter;
   public boolean zoomies;
   public float lastFactor;
   public int zoomTimer = 0;

   public PacketFly() {
      this("PacketFly", Module.Category.Movement);
      this.setChinese("\u53d1\u5305\u98de\u884c");
   }

   protected PacketFly(String name, Module.Category category) {
      super(name, category);
      INSTANCE = this;
   }

   @Override
   public void onLogin() {
      this.disable();
      this.clearValues();
   }

   @EventListener
   public void onUpdate(UpdateEvent event) {
      if (this.packetFlyActive()) {
         this.posLooks.entrySet().removeIf(entry -> System.currentTimeMillis() - entry.getValue().getTime() > TimeUnit.SECONDS.toMillis(30L));
      }
   }

   @EventListener
   public void invoke(TickEvent event) {
      if (this.packetFlyActive()) {
         if (event.isPre() && this.packetMode.getValue() != PacketFly.Mode.Compatibility) {
            double var2 = 0.0;
            MovementUtil.setMotionX(0.0);
            MovementUtil.setMotionY(0.0);
            MovementUtil.setMotionZ(0.0);
            if (this.packetMode.getValue() != PacketFly.Mode.Setback && this.teleportID.get() == 0) {
               if (this.checkPackets(6)) {
                  this.sendPackets(0.0, 0.0, 0.0, true);
               }

               return;
            }

            boolean var4 = this.isPlayerCollisionBoundingBoxEmpty();
            if (!mc.player.input.jumping || !var4 && MovementUtil.isMoving()) {
               if (mc.player.input.sneaking) {
                  var2 = this.yJitter.getValue() && this.zoomies ? -0.061 : -0.062;
               } else if (!var4) {
                  if (this.checkPackets(4)) {
                     double var10000 = this.antiKick.getValue() ? -0.04 : 0.0;
                  } else {
                     double var15 = 0.0;
                  }
               } else {
                  var2 = 0.0;
               }
            } else {
               var2 = this.antiKick.getValue() && !var4
                  ? (this.checkPackets(this.packetMode.getValue() == PacketFly.Mode.Setback ? 10 : 20) ? -0.032 : 0.062)
                  : (this.yJitter.getValue() && this.zoomies ? 0.061 : 0.062);
            }

            if (this.phase.getValue() == PacketFly.Phase.Full && var4 && MovementUtil.isMoving() && var2 != 0.0) {
               var2 /= 2.5;
            }

            double var5 = this.xzJitter.getValue() && this.zoomies ? 0.25 : 0.26;
            double var7 = this.xzJitter.getValue() && this.zoomies ? 0.03 : 0.031;
            double[] var9 = MovementUtil.directionSpeed(this.phase.getValue() == PacketFly.Phase.Full && var4 ? var7 : var5);
            if (this.packetMode.getValue() == PacketFly.Mode.Increment) {
               if ((double)this.lastFactor >= this.factor.getValue()) {
                  this.lastFactor = 1.0F;
               } else {
                  this.lastFactor++;
                  if ((double)this.lastFactor > this.factor.getValue()) {
                     this.lastFactor = this.factor.getValueFloat();
                  }
               }
            } else {
               this.lastFactor = this.factor.getValueFloat();
            }

            int var10 = 1;

            while (true) {
               float var11 = (float)var10;
               float var12 = this.packetMode.getValue() != PacketFly.Mode.Factor
                     && this.packetMode.getValue() != PacketFly.Mode.Slow
                     && this.packetMode.getValue() != PacketFly.Mode.Increment
                  ? 1.0F
                  : this.lastFactor;
               if (!(var11 <= var12)) {
                  this.zoomTimer++;
                  if ((double)this.zoomTimer > this.zoomer.getValue()) {
                     this.zoomies = !this.zoomies;
                     this.zoomTimer = 0;
                  }
                  break;
               }

               double var13 = mc.player.getY() < this.concealY.getValue() && MovementUtil.isMoving() ? this.conceal.getValue() : 1.0;
               MovementUtil.setMotionX(var9[0] * (double)var10 * var13 * this.xzSpeed.getValue());
               MovementUtil.setMotionY(var2 * (double)var10 * this.ySpeed.getValue());
               MovementUtil.setMotionZ(var9[1] * (double)var10 * var13 * this.xzSpeed.getValue());
               this.sendPackets(
                  MovementUtil.getMotionX(), MovementUtil.getMotionY(), MovementUtil.getMotionZ(), this.packetMode.getValue() != PacketFly.Mode.Setback
               );
               var10++;
            }
         }
      }
   }

   @EventListener
   public void invoke(PacketEvent.Receive event) {
      if (this.packetFlyActive()) {
         if (!nullCheck()) {
            if (this.packetMode.getValue() != PacketFly.Mode.Compatibility) {
               if (event.getPacket() instanceof PlayerPositionLookS2CPacket var3) {
                  PacketFly.TimeVec var4;
                  if (mc.player.isAlive()
                     && this.packetMode.getValue() != PacketFly.Mode.Setback
                     && this.packetMode.getValue() != PacketFly.Mode.Slow
                     && !(mc.currentScreen instanceof DownloadingTerrainScreen)
                     && (var4 = this.posLooks.remove(var3.getTeleportId())) != null
                     && var4.x == var3.getX()
                     && var4.y == var3.getY()
                     && var4.z == var3.getZ()) {
                     event.setCancelled(true);
                     return;
                  }

                  this.teleportID.set(var3.getTeleportId());
               }
            }
         }
      }
   }

   @EventListener
   public void invoke(MoveEvent event) {
      if (this.packetFlyActive()) {
         if (this.phase.getValue() == PacketFly.Phase.Semi || this.isPlayerCollisionBoundingBoxEmpty()) {
            mc.player.noClip = true;
         }

         if (this.packetMode.getValue() != PacketFly.Mode.Compatibility && (this.packetMode.getValue() == PacketFly.Mode.Setback || this.teleportID.get() != 0)
            )
          {
            if (this.zeroSpeed.getValue()) {
               event.setX(0.0);
               event.setY(0.0);
               event.setZ(0.0);
            } else {
               event.setX(MovementUtil.getMotionX());
               event.setY(MovementUtil.getMotionY());
               event.setZ(MovementUtil.getMotionZ());
            }

            if (this.zeroY.getValue()) {
               event.setY(0.0);
            }
         }
      }
   }

   @EventListener
   public void onPacket(PacketEvent.Send event) {
      if (this.packetFlyActive()) {
         if (event.getPacket() instanceof PlayerMoveC2SPacket var3
            && this.packetMode.getValue() != PacketFly.Mode.Compatibility
            && !this.playerPackets.remove(event.getPacket())) {
            if (var3 instanceof LookAndOnGround && !this.noRotation.getValue()) {
               return;
            }

            if (!this.noMovePacket.getValue()) {
               return;
            }

            event.setCancelled(true);
         }
      }
   }

   @Override
   public void onEnable() {
      this.clearValues();
      if (mc.player == null) {
         this.disable();
      }
   }

   @Override
   public String getInfo() {
      return this.packetMode.getValue().toString();
   }

   public void clearValues() {
      this.lastFactor = 1.0F;
      this.packetCounter = 0;
      this.teleportID.set(0);
      this.playerPackets.clear();
      this.posLooks.clear();
      this.vecDelServer = null;
   }

   protected boolean packetFlyActive() {
      return this.isOn();
   }

   protected boolean packetSettingsVisible() {
      return true;
   }

   public boolean isPlayerCollisionBoundingBoxEmpty() {
      double var1 = this.bbOffset.getValue() ? -0.0625 : 0.0;
      return BlockUtil.canCollide(mc.player, mc.player.getBoundingBox().expand(var1, var1, var1));
   }

   public boolean checkPackets(int amount) {
      if (++this.packetCounter >= amount) {
         this.packetCounter = 0;
         return true;
      } else {
         return false;
      }
   }

   public void sendPackets(double x, double y, double z, boolean confirm) {
      Vec3d var8 = new Vec3d(x, y, z);
      Vec3d var9;
      this.vecDelServer = var9 = mc.player.getPos().add(var8);
      Vec3d var10 = this.type.getValue().createOutOfBounds(var9, this.invalidY.getValueInt());
      this.sendCPacket(new PositionAndOnGround(var9.x, var9.y, var9.z, mc.player.isOnGround()));
      if (!mc.isInSingleplayer()) {
         for (int var11 = 0; (double)var11 < this.invalids.getValue(); var11++) {
            this.sendCPacket(new PositionAndOnGround(var10.x, var10.y, var10.z, mc.player.isOnGround()));
            var10 = this.type.getValue().createOutOfBounds(var10, this.invalidY.getValueInt());
         }
      }

      if (confirm && (this.zeroTeleport.getValue() || this.teleportID.get() != 0)) {
         for (int var12 = 0; (double)var12 < this.sendTeleport.getValue(); var12++) {
            this.sendConfirmTeleport(var9);
         }
      }

      if (this.elytra.getValue()) {
         mc.getNetworkHandler().sendPacket(new ClientCommandC2SPacket(mc.player, ClientCommandC2SPacket.Mode.START_FALL_FLYING));
      }
   }

   public void sendConfirmTeleport(Vec3d vec) {
      int var2 = this.teleportID.incrementAndGet();
      mc.getNetworkHandler().sendPacket(new TeleportConfirmC2SPacket(var2));
      this.posLooks.put(var2, new PacketFly.TimeVec(vec));
   }

   public void sendCPacket(Packet<?> packet) {
      this.playerPackets.add(packet);
      mc.getNetworkHandler().sendPacket(packet);
   }
@Environment(EnvType.CLIENT)
   public static enum Mode {
      Setback,
      Fast,
      Factor,
      Slow,
      Increment,
      Compatibility;

}

   @Environment(EnvType.CLIENT)
   public static enum Phase {
      Off,
      Semi,
      Full;

}

   @Environment(EnvType.CLIENT)
   public static class TimeVec extends Vec3d {
      final long time;

      public TimeVec(Vec3d vec3d) {
         this(vec3d.x, vec3d.y, vec3d.z, System.currentTimeMillis());
      }

      public TimeVec(double xIn, double yIn, double zIn, long time) {
         super(xIn, yIn, zIn);
         this.time = time;
      }

      public long getTime() {
         return this.time;
      }
   }

   @Environment(EnvType.CLIENT)
   public static enum Type {
      Down {
         @Override
         public Vec3d createOutOfBounds(Vec3d vec3d, int invalid) {
            return vec3d.add(0.0, -invalid, 0.0);
         }
      },
      Up {
         @Override
         public Vec3d createOutOfBounds(Vec3d vec3d, int invalid) {
            return vec3d.add(0.0, invalid, 0.0);
         }
      },
      Preserve {
         private final Random random = new Random();

         private int randomInt() {
            int result = this.random.nextInt(29000000);
            return this.random.nextBoolean() ? result : -result;
         }

         @Override
         public Vec3d createOutOfBounds(Vec3d vec3d, int invalid) {
            return vec3d.add(this.randomInt(), 0.0, this.randomInt());
         }
      },
      Switch {
         private final Random random = new Random();

         @Override
         public Vec3d createOutOfBounds(Vec3d vec3d, int invalid) {
            return this.random.nextBoolean()
                  ? vec3d.add(0.0, -invalid, 0.0)
                  : vec3d.add(0.0, invalid, 0.0);
         }
      },
      X {
         @Override
         public Vec3d createOutOfBounds(Vec3d vec3d, int invalid) {
            return vec3d.add(invalid, 0.0, 0.0);
         }
      },
      Z {
         @Override
         public Vec3d createOutOfBounds(Vec3d vec3d, int invalid) {
            return vec3d.add(0.0, 0.0, invalid);
         }
      },
      XZ {
         @Override
         public Vec3d createOutOfBounds(Vec3d vec3d, int invalid) {
            return vec3d.add(invalid, 0.0, invalid);
         }
      };

      public abstract Vec3d createOutOfBounds(Vec3d vec3d, int i);
}
}
