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

import asm.accessors.IEntity;
import com.google.common.collect.Maps;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.TotemEvent;
import dev.crucifixbeta.api.events.impl.UpdateEvent;
import dev.crucifixbeta.api.utils.entity.CopyPlayerEntity;
import dev.crucifixbeta.api.utils.render.ModelPlayer;
import dev.crucifixbeta.api.utils.render.Render3DUtil;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.impl.client.hud.TextRadar;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.ColorSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.awt.Color;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.UUID;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.VertexConsumerProvider.Immediate;
import net.minecraft.client.util.BufferAllocator;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerRemoveS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket.Action;
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket.Entry;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.GameMode;

@Environment(EnvType.CLIENT)
public class LogoutSpots extends Module {
   private static final DateTimeFormatter TIME_FORMATTER;
   private final ColorSetting box = this.add(new ColorSetting("Box", new Color(255, 255, 255, 100)).injectBoolean(true));
   private final ColorSetting fill = this.add(new ColorSetting("Fill", new Color(255, 255, 255, 100)).injectBoolean(true));
   private final ColorSetting text = this.add(new ColorSetting("Text", new Color(255, 255, 255, 255)).injectBoolean(true));
   private final ColorSetting chamsFill = this.add(new ColorSetting("ChamsFill", new Color(255, 255, 255, 100)).injectBoolean(true));
   private final ColorSetting chamsLine = this.add(new ColorSetting("ChamsLine", new Color(255, 255, 255, 100)).injectBoolean(true));
   final Map<UUID, PlayerEntity> playerCache = Maps.newConcurrentMap();
   final Map<UUID, LogoutSpots.LogoutRecord> logoutCache = Maps.newConcurrentMap();
   private final SliderSetting distance = this.add(new SliderSetting("Distance", 128.0, 0.0, 512.0, 1.0));
   private final BooleanSetting health = this.add(new BooleanSetting("Health", true));
   private final BooleanSetting totem = this.add(new BooleanSetting("Totem", true));
   private final BooleanSetting potionTime = this.add(new BooleanSetting("PotionTime", true));
   private final BooleanSetting logoutTime = this.add(new BooleanSetting("LogoutTime", true));
   private final BooleanSetting renderSkin = this.add(new BooleanSetting("RenderSkin", true));
   private final SliderSetting skinAlpha = this.add(new SliderSetting("SkinAlpha", 100.0, 0.0, 100.0, 1.0));
   private final BooleanSetting message = this.add(new BooleanSetting("Message", true));

   public LogoutSpots() {
      super("LogoutSpots", Module.Category.Render);
      this.setChinese("\u9000\u51fa\u8bb0\u5f55");
   }

   @EventListener
   public void onPacketReceive(PacketEvent.Receive event) {
      if (!nullCheck()) {
         NameTags var2 = NameTags.INSTANCE;
         if (var2 != null && var2.isOff() && this.potionTime.getValue()) {
            var2.onPacketReceive(event);
         }

         Packet var3 = event.getPacket();
         if (var3 instanceof PlayerListS2CPacket var12) {
            if (var12.getActions().contains(Action.ADD_PLAYER)) {
               for (Entry var14 : var12.getPlayerAdditionEntries()) {
                  if (var14.gameMode() != GameMode.SPECTATOR) {
                     LogoutSpots.LogoutRecord var15 = this.logoutCache.remove(var14.profile().getId());
                     if (var15 != null && this.message.getValue()) {
                        PlayerEntity var16 = var15.model.player;
                        mc.execute(
                           () -> this.sendMessage(
                                 "§f"
                                    + var16.getName().getString()
                                    + " §rLogged back at §f"
                                    + var16.getBlockX()
                                    + ", "
                                    + var16.getBlockY()
                                    + ", "
                                    + var16.getBlockZ()
                              )
                        );
                     }
                  }
               }
            }
         } else {
            if (var3 instanceof PlayerRemoveS2CPacket var4) {
               for (UUID var7 : var4.profileIds()) {
                  PlayerEntity var8 = this.playerCache.get(var7);
                  if (var8 != null && !this.logoutCache.containsKey(var7)) {
                     CopyPlayerEntity var9 = new CopyPlayerEntity(var8, true);
                     AbstractClientPlayerEntity var11 = var8 instanceof AbstractClientPlayerEntity var10 ? var10 : null;
                     LogoutSpots.LogoutRecord var17 = new LogoutSpots.LogoutRecord(
                        new ModelPlayer(var9), var11, this.getPotionText(var8), LocalTime.now().format(TIME_FORMATTER)
                     );
                     if (this.message.getValue()) {
                        mc.execute(
                           () -> this.sendMessage(
                                 "§f"
                                    + var8.getName().getString()
                                    + " §rLogged out at §f"
                                    + var8.getBlockX()
                                    + ", "
                                    + var8.getBlockY()
                                    + ", "
                                    + var8.getBlockZ()
                              )
                        );
                     }

                     this.logoutCache.put(var7, var17);
                  }
               }
            }
         }
      }
   }

   @Override
   public void onDisable() {
      this.playerCache.clear();
      this.logoutCache.clear();
   }

   @EventListener
   public void onTotemPop(TotemEvent event) {
      NameTags var2 = NameTags.INSTANCE;
      if (var2 != null && var2.isOff() && this.potionTime.getValue()) {
         var2.onTotemPop(event);
      }
   }

   @EventListener
   public void onUpdate(UpdateEvent event) {
      this.playerCache.clear();

      for (AbstractClientPlayerEntity var3 : CrucifixBeta.THREAD.getPlayers()) {
         if (var3 != null && !var3.equals(mc.player)) {
            this.playerCache.put(var3.getGameProfile().getId(), var3);
         }
      }
   }

   @Override
   public void onRender3D(MatrixStack matrixStack) {
      double var2 = this.distance.getValue();
      Vec3d var4 = mc.player.getPos();

      for (LogoutSpots.LogoutRecord var6 : this.logoutCache.values()) {
         PlayerEntity var7 = var6.model.player;
         if (!(var4.distanceTo(var7.getPos()) > var2)) {
            Box var8 = ((IEntity)var7).getDimensions().getBoxAt(var7.getPos());
            if (this.box.booleanValue) {
               Render3DUtil.drawBox(matrixStack, var8, this.box.getValue());
            }

            if (this.fill.booleanValue) {
               Render3DUtil.drawFill(matrixStack, var8, this.fill.getValue());
            }

            if (this.renderSkin.getValue() && var6.skinPlayer != null) {
               this.renderPlayerSkin(matrixStack, var6.skinPlayer);
            }

            if (this.chamsFill.booleanValue || this.chamsLine.booleanValue) {
               var6.model.render(matrixStack, this.chamsFill, this.chamsLine, this.skinAlpha.getValue() / 100.0, 0.0, 1.0, 0.0, false, false);
            }

            if (this.text.booleanValue) {
               Render3DUtil.drawText3D(this.buildLogoutText(var7, var6), new Vec3d(var7.getX(), var8.maxY + 0.5, var7.getZ()), this.text.getValue(), false);
            }
         }
      }
   }

   private void renderPlayerSkin(MatrixStack matrixStack, AbstractClientPlayerEntity player) {
      Vec3d var3 = mc.getEntityRenderDispatcher().camera.getPos();
      double var4 = player.getX() - var3.getX();
      double var6 = player.getY() - var3.getY();
      double var8 = player.getZ() - var3.getZ();
      Immediate var10 = VertexConsumerProvider.immediate(new BufferAllocator(4096));
      float var11 = this.skinAlpha.getValueFloat() / 100.0F;
      boolean var12 = player.isCustomNameVisible();
      boolean var13 = player.isSneaking();
      player.setCustomNameVisible(false);
      player.setSneaking(true);
      matrixStack.push();

      try {
         RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, var11);
         mc.getEntityRenderDispatcher()
            .render(player, var4, var6, var8, player.getYaw(), mc.getRenderTickCounter().getTickDelta(true), matrixStack, var10, 15728880);
         var10.draw();
      } finally {
         matrixStack.pop();
         player.setSneaking(var13);
         player.setCustomNameVisible(var12);
         RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
      }
   }

   private String buildLogoutText(PlayerEntity player, LogoutSpots.LogoutRecord record) {
      StringBuilder var3 = new StringBuilder(player.getName().getString());
      if (this.health.getValue()) {
         var3.append(TextRadar.getHealthColor(player))
            .append(" ")
            .append(round2((double)(player.getHealth() + player.getAbsorptionAmount())));
      }

      if (this.totem.getValue() && CrucifixBeta.POP.getPop(player) > 0) {
         var3.append(TextRadar.getPopColor(CrucifixBeta.POP.getPop(player))).append(" -").append(CrucifixBeta.POP.getPop(player));
      }

      if (this.logoutTime.getValue()) {
         var3.append(" \u00a77Time:\u00a7f ").append(record.logoutTime);
      }

      if (this.potionTime.getValue() && !record.potionText.isEmpty()) {
         var3.append(record.potionText);
      }

      return var3.toString();
   }

   private String getPotionText(PlayerEntity player) {
      NameTags var2 = NameTags.INSTANCE;
      if (var2 != null) {
         var2.syncPotionState(player, System.currentTimeMillis());
         String var3 = var2.getPotionDisplayString(player, " ", true, true, true, true, true, false, true);
         if (var3 != null && !var3.isEmpty()) {
            return var3;
         }
      }

      StringBuilder var4 = new StringBuilder();
      appendPotionText(var4, player, StatusEffects.STRENGTH, Formatting.YELLOW, LogoutSpots.EffectLimit.STRENGTH);
      appendPotionText(var4, player, StatusEffects.RESISTANCE, Formatting.BLUE, LogoutSpots.EffectLimit.RESISTANCE);
      appendPotionText(var4, player, StatusEffects.REGENERATION, Formatting.LIGHT_PURPLE, LogoutSpots.EffectLimit.NONE);
      appendPotionText(var4, player, StatusEffects.FIRE_RESISTANCE, Formatting.GOLD, LogoutSpots.EffectLimit.NONE);
      appendPotionText(var4, player, StatusEffects.ABSORPTION, Formatting.BLUE, LogoutSpots.EffectLimit.NONE);
      return var4.toString();
   }

   private static void appendPotionText(
      StringBuilder builder, PlayerEntity player, RegistryEntry<StatusEffect> effect, Formatting color, LogoutSpots.EffectLimit limit
   ) {
      StatusEffectInstance var5 = player.getStatusEffect(effect);
      if (var5 != null && var5.getDuration() > 0) {
         long var6 = Math.max(1L, (long)var5.getDuration() / 20L);
         var6 = Math.min(var6, maxEffectSeconds(limit, var5.getAmplifier()));
         if (var6 > 0L) {
            builder.append(" ").append(color).append(var6).append("s");
            if (var5.getAmplifier() > 0) {
               builder.append(toRoman(var5.getAmplifier() + 1));
            }
         }
      }
   }

   private static long maxEffectSeconds(LogoutSpots.EffectLimit limit, int amplifier) {
      return switch (limit) {
         case NONE -> Long.MAX_VALUE;
         case STRENGTH -> amplifier >= 1 ? 90L : 480L;
         case RESISTANCE -> amplifier >= 3 ? 20L : (amplifier >= 2 ? 40L : Long.MAX_VALUE);
      };
   }

   private static String toRoman(int value) {
      return switch (value) {
         case 1 -> "I";
         case 2 -> "II";
         case 3 -> "III";
         case 4 -> "IV";
         case 5 -> "V";
         default -> Integer.toString(value);
      };
   }

   public static float round2(double value) {
      BigDecimal var2 = new BigDecimal(value);
      var2 = var2.setScale(1, RoundingMode.HALF_UP);
      return var2.floatValue();
   }

   static {

      TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
   }
@Environment(EnvType.CLIENT)
   private static enum EffectLimit {
      NONE,
      STRENGTH,
      RESISTANCE;

}

   @Environment(EnvType.CLIENT)
   private static class LogoutRecord {
      private final ModelPlayer model;
      private final AbstractClientPlayerEntity skinPlayer;
      private final String potionText;
      private final String logoutTime;

      private LogoutRecord(ModelPlayer model, AbstractClientPlayerEntity skinPlayer, String potionText, String logoutTime) {
         this.model = model;
         this.skinPlayer = skinPlayer;
         this.potionText = potionText == null ? "" : potionText;
         this.logoutTime = logoutTime;
      }
}
}
