package zenith.zov.client.modules.impl.misc;

import com.darkmagician6.eventapi.EventTarget;
import com.mojang.blaze3d.platform.GlStateManager.DstFactor;
import com.mojang.blaze3d.platform.GlStateManager.SrcFactor;
import com.mojang.blaze3d.systems.RenderSystem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.block.BlockState;
import net.minecraft.client.gl.ShaderProgramKeys;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.VertexFormat.DrawMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.network.packet.s2c.play.ChunkDeltaUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket;
import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.BlockPos.Mutable;
import org.apache.commons.lang3.StringUtils;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import zenith.zov.Zenith;
import zenith.zov.base.events.impl.input.EventKey;
import zenith.zov.base.events.impl.render.EventRender2D;
import zenith.zov.base.events.impl.render.EventRender3D;
import zenith.zov.base.events.impl.server.EventPacket;
import zenith.zov.base.font.Font;
import zenith.zov.base.font.Fonts;
import zenith.zov.client.modules.api.Category;
import zenith.zov.client.modules.api.Module;
import zenith.zov.client.modules.api.ModuleAnnotation;
import zenith.zov.client.modules.api.setting.Setting;
import zenith.zov.client.modules.api.setting.impl.BooleanSetting;
import zenith.zov.client.modules.api.setting.impl.KeySetting;
import zenith.zov.client.modules.impl.render.Predictions;
import zenith.zov.utility.game.player.PlayerIntersectionUtil;
import zenith.zov.utility.game.player.PlayerInventoryUtil;
import zenith.zov.utility.math.MathUtil;
import zenith.zov.utility.math.ProjectionUtil;
import zenith.zov.utility.other.BooleanSettable;
import zenith.zov.utility.render.display.base.BorderRadius;
import zenith.zov.utility.render.display.base.color.ColorRGBA;
import zenith.zov.utility.render.display.base.color.ColorUtil;
import zenith.zov.utility.render.display.shader.DrawUtil;
import zenith.zov.utility.render.level.Render3DUtil;

@ModuleAnnotation(
    name = "ServerHelper",
    category = Category.MISC,
    description = ""
)
public final class ServerHelper extends Module {
    private final Map<BlockPos, BlockState> blockStateMap = new HashMap<>();
    private final List<ServerHelper.ServerEvent> serverEvents = new ArrayList<>();
    private final List<ServerHelper.Structure> structures = new ArrayList<>();
    private final List<ServerHelper.KeyBind> keyBindings = new ArrayList<>();
    private final Map<BlockPos, Boolean> trapCache = new HashMap<>();
    private final Map<BlockPos, Boolean> bigTrapCache = new HashMap<>();
    private static final long CACHE_DURATION = 1000L;
    private final Map<BlockPos, Long> trapCacheTime = new HashMap<>();
    private final Map<BlockPos, Long> bigTrapCacheTime = new HashMap<>();
    public static final ServerHelper INSTANCE = new ServerHelper();
    private final BooleanSetting consumablesSetting = new BooleanSetting(
        "Consumables Timer", true, () -> Zenith.getInstance().getServerHandler().isCopyTime() || Zenith.getInstance().getServerHandler().isHolyWorld()
    );
    private final BooleanSetting autoPointSetting = new BooleanSetting("Auto Point", true, Zenith.getInstance().getServerHandler()::isFunTime);

    private ServerHelper() {
        this.initialize();
    }

    public void initialize() {
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.FIREWORK_STAR, new KeySetting("Anti Fly", Zenith.getInstance().getServerHandler()::isReallyWorld), 0.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.FLOWER_BANNER_PATTERN, new KeySetting("Experience Scroll", Zenith.getInstance().getServerHandler()::isReallyWorld), 0.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.PRISMARINE_SHARD, new KeySetting("Explosive Trap", Zenith.getInstance().getServerHandler()::isHolyWorld), 5.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.POPPED_CHORUS_FRUIT, new KeySetting("Regular Trap", Zenith.getInstance().getServerHandler()::isHolyWorld), 0.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.NETHER_STAR, new KeySetting("Stun", Zenith.getInstance().getServerHandler()::isHolyWorld), 30.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.FIRE_CHARGE, new KeySetting("Explosive Thing", Zenith.getInstance().getServerHandler()::isHolyWorld), 0.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.SNOWBALL,
                    new KeySetting(
                        "Snowball", () -> Zenith.getInstance().getServerHandler().isCopyTime() || Zenith.getInstance().getServerHandler().isHolyWorld()
                    ),
                    0.0F,
                    new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.PHANTOM_MEMBRANE, new KeySetting("Divine Aura", Zenith.getInstance().getServerHandler()::isCopyTime), 0.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.NETHERITE_SCRAP, new KeySetting("Trap", Zenith.getInstance().getServerHandler()::isCopyTime), 0.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.DRIED_KELP, new KeySetting("Layer", Zenith.getInstance().getServerHandler()::isCopyTime), 0.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.SUGAR, new KeySetting("Obvious Dust", Zenith.getInstance().getServerHandler()::isCopyTime), 10.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.FIRE_CHARGE, new KeySetting("Fire Tornado", Zenith.getInstance().getServerHandler()::isCopyTime), 10.0F, new BooleanSettable()
                )
            );
        this.keyBindings
            .add(
                new ServerHelper.KeyBind(
                    Items.ENDER_EYE, new KeySetting("Disorient", Zenith.getInstance().getServerHandler()::isCopyTime), 10.0F, new BooleanSettable()
                )
            );
    }

    @Override
    public List<Setting> getSettings() {
        ArrayList<Setting> settings = new ArrayList<>(List.of(this.consumablesSetting, this.autoPointSetting));
        settings.addAll(this.keyBindings.stream().map(ServerHelper.KeyBind::setting).toList());
        return settings;
    }

    @EventTarget
    public void onKey(EventKey e) {
        if (e.getAction() == 0) {
            this.keyBindings.stream().filter(bind -> e.is(bind.setting.getKeyCode()) && bind.setting.getVisible().get()).forEach(bind -> {
                if (mc.currentScreen == null) {
                    PlayerInventoryUtil.swapAndUse(bind.item);
                }

                bind.draw.setValue(false);
            });
        } else if (mc.currentScreen == null) {
            this.keyBindings
                .stream()
                .filter(
                    bind -> e.is(bind.setting.getKeyCode())
                            && bind.setting.getVisible().get()
                            && PlayerInventoryUtil.getSlot(bind.item, slot -> slot.getStack().get(DataComponentTypes.CUSTOM_DATA) != null) != null
                )
                .forEach(bind -> bind.draw.setValue(true));
        }
    }

    @EventTarget
    public void onPacket(EventPacket e) {
        if (this.consumablesSetting.isEnabled()) {
            if (Zenith.getInstance().getServerHandler().isCopyTime() && e.getPacket() instanceof ChunkDeltaUpdateS2CPacket chunkDelta) {
                chunkDelta.visitUpdates((pos, state) -> this.blockStateMap.put(pos.add(0, 0, 0), state));
                chunkDelta.visitUpdates((pos, state) -> {
                    Vec3d vec = pos.add(0, 0, 0).toCenterPos();
                    if (this.blockStateMap.size() > 50 && this.blockStateMap.size() < 600) {
                        if (this.isTrap(pos.up(2))) {
                            this.addStructure(Items.NETHERITE_SCRAP, vec, (double)(System.currentTimeMillis() + 15000L));
                        } else if (this.isBigTrap(pos.up(3))) {
                            this.addStructure(Items.NETHERITE_SCRAP, vec, (double)(System.currentTimeMillis() + 30000L));
                        }
                    }
                });
            }

            if (e.getPacket() instanceof PlaySoundS2CPacket soundS2CPacket
                && Zenith.getInstance().getServerHandler().isHolyWorld()
                && soundS2CPacket.getSound()
                    .toString()
                    .equals(
                        "Reference{ResourceKey[minecraft:sound_event / minecraft:block.beacon.deactivate]=SoundEvent[location=minecraft:block.beacon.deactivate, fixedRange=Optional.empty]}"
                    )) {
                this.addStructure(
                    Items.NETHER_STAR,
                    new Vec3d(soundS2CPacket.getX(), soundS2CPacket.getY(), soundS2CPacket.getZ()),
                    (double)(System.currentTimeMillis() + 15000L)
                );
            }

            if (Zenith.getInstance().getServerHandler().isHolyWorld() && e.getPacket() instanceof ParticleS2CPacket particleS2CPacket) {
                String particleType = particleS2CPacket.getParameters().getType().toString();
                if (particleType.contains("ExplosionSmokeParticle")) {
                    this.addStructure(
                        Items.PRISMARINE_SHARD,
                        new Vec3d(particleS2CPacket.getX(), particleS2CPacket.getY(), particleS2CPacket.getZ()),
                        (double)(System.currentTimeMillis() + 11000L)
                    );
                }
            }
        }

        if (e.getPacket() instanceof GameMessageS2CPacket gameMessage && this.autoPointSetting.isEnabled() && this.autoPointSetting.getVisible().get()) {
            Text content = gameMessage.content();
            String contentString = content.toString();
            String message = content.getString();
            String name = StringUtils.substringBetween(message, "|||   [", "]   ");
            if (name != null) {
                String position = StringUtils.substringBetween(contentString, "value='/gps ", "'");
                String lvl = StringUtils.substringBetween(message, "Loot Level: ", "\n ║");
                String owner = StringUtils.substringBetween(message, "Summoned by Player: ", "\n ║");
                if (position != null) {
                    String[] pose = position.split(" ");
                    Vec3d center = BlockPos.ofFloored(
                            (double)Integer.parseInt(pose[0]), (double)Integer.parseInt(pose[1]), (double)Integer.parseInt(pose[2])
                        )
                        .toCenterPos();
                    switch (name) {
                        case "Mystical Chest":
                            this.addEvent(name, lvl, owner, center, "overworld", 300, 0);
                            break;
                        case "Volcano":
                            this.addEvent(name, lvl, owner, center, "overworld", 300, 120);
                            break;
                        case "Meteor Shower":
                        case "Killer Beacon":
                        case "Mystical Altar":
                            this.addEvent(name, lvl, owner, center, "overworld", 360, 0);
                            break;
                        case "Mysterious Beacon":
                            this.addEvent(name, lvl, owner, center, "overworld", 60, 180);
                    }
                } else {
                    switch (name) {
                        case "Death Chest":
                            this.addEvent(name, lvl, owner, BlockPos.ofFloored(-155.0, 64.0, 205.0).toCenterPos(), "lobby", 300, 0);
                            break;
                        case "Hell Slaughter":
                            this.addEvent(name, lvl, owner, BlockPos.ofFloored(48.0, 87.0, 73.0).toCenterPos(), "lobby", 180, 120);
                    }
                }
            }
        }
    }

    @EventTarget
    public void onWorldRender(EventRender3D e) {
        long currentTime = System.currentTimeMillis();
        if (currentTime % 5000L < 16L) {
            this.cleanCache();
        }

        MatrixStack matrix = e.getMatrix();
        this.keyBindings
            .stream()
            .filter(bind -> bind.draw.isValue())
            .forEach(
                bind -> {
                    BlockPos playerPos = mc.player.getBlockPos();
                    Vec3d smooth = MathUtil.interpolate(
                            Vec3d.of(BlockPos.ofFloored(mc.player.prevX, mc.player.prevY, mc.player.prevZ)),
                            Vec3d.of(playerPos)
                        )
                        .subtract(Vec3d.of(playerPos));
                    String var5 = bind.setting.getName();
                    switch (var5) {
                        case "Trap":
                        case "Regular Trap":
                            this.drawItemCube(playerPos, smooth, 1.99F, Zenith.getInstance().getThemeManager().getClientColor(90).getRGB());
                            break;
                        case "Disorient":
                        case "Fire Tornado":
                        case "Obvious Dust":
                            this.drawItemRadius(matrix, bind.distance, ColorUtil.LIGHT_RED);
                            break;
                        case "Explosive Thing":
                            this.drawItemRadius(matrix, 5.0F, Zenith.getInstance().getThemeManager().getClientColor(90).getRGB());
                            break;
                        case "Layer":
                            float yaw = MathHelper.wrapDegrees(mc.player.getYaw());
                            if (Math.abs(mc.player.getPitch()) > 60.0F) {
                                BlockPos blockPos = playerPos.up().offset(mc.player.getFacing(), 3);
                                Vec3d pos1 = Vec3d.of(blockPos.east(3).south(3).down()).add(smooth);
                                Vec3d pos2 = Vec3d.of(blockPos.west(2).north(2).up()).add(smooth);
                                Render3DUtil.drawBox(
                                    new Box(pos1, pos2), Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(), 3.0F, true, true, true
                                );
                            } else if (yaw <= -157.5F || yaw >= 157.5F) {
                                BlockPos blockPos = playerPos.north(3).up();
                                Vec3d pos1 = Vec3d.of(blockPos.down(2).east(3)).add(smooth);
                                Vec3d pos2 = Vec3d.of(blockPos.up(3).west(2).south(2)).add(smooth);
                                Render3DUtil.drawBox(
                                    new Box(pos1, pos2), Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(), 3.0F, true, true, true
                                );
                            } else if (yaw <= -112.5F) {
                                this.drawSidePlast(
                                    playerPos.east(5).south().down(),
                                    smooth,
                                    Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(),
                                    -1,
                                    true
                                );
                            } else if (yaw <= -67.5F) {
                                BlockPos blockPos = playerPos.east(2).up();
                                Vec3d pos1 = Vec3d.of(blockPos.down(2).south(3)).add(smooth);
                                Vec3d pos2 = Vec3d.of(blockPos.up(3).north(2).east(2)).add(smooth);
                                Render3DUtil.drawBox(
                                    new Box(pos1, pos2), Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(), 3.0F, true, true, true
                                );
                            } else if (yaw <= -22.5F) {
                                this.drawSidePlast(
                                    playerPos.east(5).down(),
                                    smooth,
                                    Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(),
                                    1,
                                    false
                                );
                            } else if ((double)yaw >= -22.5 && (double)yaw <= 22.5) {
                                BlockPos blockPos = playerPos.south(2).up();
                                Vec3d pos1 = Vec3d.of(blockPos.down(2).east(3)).add(smooth);
                                Vec3d pos2 = Vec3d.of(blockPos.up(3).west(2).south(2)).add(smooth);
                                Render3DUtil.drawBox(
                                    new Box(pos1, pos2), Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(), 3.0F, true, true, true
                                );
                            } else if (yaw <= 67.5F) {
                                this.drawSidePlast(
                                    playerPos.west(4).down(),
                                    smooth,
                                    Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(),
                                    1,
                                    true
                                );
                            } else if (yaw <= 112.5F) {
                                BlockPos blockPos = playerPos.west(3).up();
                                Vec3d pos1 = Vec3d.of(blockPos.down(2).south(3)).add(smooth);
                                Vec3d pos2 = Vec3d.of(blockPos.up(3).north(2).east(2)).add(smooth);
                                Render3DUtil.drawBox(
                                    new Box(pos1, pos2), Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(), 3.0F, true, true, true
                                );
                            } else if (yaw <= 157.5F) {
                                this.drawSidePlast(
                                    playerPos.west(4).south().down(),
                                    smooth,
                                    Zenith.getInstance().getThemeManager().getClientColor(90).getRGB(),
                                    -1,
                                    false
                                );
                            }
                            break;
                        case "Explosive Trap":
                            this.drawItemCube(playerPos, smooth, 3.99F, ColorUtil.LIGHT_RED);
                            break;
                        case "Stun":
                            this.drawItemCube(playerPos, smooth, 15.01F, ColorUtil.LIGHT_RED);
                            break;
                        case "Snowball":
                            Predictions.INSTANCE.drawPredictionInHand(matrix, List.of(Items.SNOWBALL.getDefaultStack()));
                    }
                }
            );
    }

    @EventTarget
    public void onDraw(EventRender2D e) {
        DrawContext context = e.getContext();
        MatrixStack matrix = context.getMatrices();
        this.structures
            .forEach(
                cons -> {
                    double time = (cons.time - (double)System.currentTimeMillis()) / 1000.0;
                    Vec3d vec3d = ProjectionUtil.worldSpaceToScreenSpace(cons.vec);
                    String text = MathUtil.round(time, 0.1F) + "s";
                    Font font = Fonts.MEDIUM.getFont(10.0F);
                    float width = font.width(text);
                    float posX = (float)(vec3d.x - (double)(width / 2.0F));
                    float posY = (float)vec3d.y;
                    float padding = 2.0F;
                    if (ProjectionUtil.canSee(cons.vec)
                        && cons.anarchy == Zenith.getInstance().getServerHandler().getAnarchy()
                        && Zenith.getInstance().getServerHandler().getWorldType().equals(cons.world)) {
                        DrawUtil.drawBlurHud(
                            e.getContext().getMatrices(),
                            posX - 4.0F,
                            posY - 4.0F,
                            16.8F + font.width(text) + 8.0F,
                            20.8F,
                            22.0F,
                            BorderRadius.all(4.0F),
                            ColorRGBA.WHITE
                        );
                        e.getContext()
                            .drawRoundedRect(
                                posX - 4.0F,
                                posY - 4.0F,
                                16.8F + font.width(text) + 8.0F,
                                20.8F,
                                BorderRadius.all(4.0F),
                                Zenith.getInstance().getThemeManager().getCurrentTheme().getForegroundLight()
                            );
                        DrawUtil.drawRoundedCorner(
                            e.getContext().getMatrices(),
                            posX - 4.0F,
                            posY - 4.0F,
                            16.8F + font.width(text) + 8.0F,
                            20.8F,
                            0.1F,
                            10.0F,
                            Zenith.getInstance().getThemeManager().getCurrentTheme().getColor(),
                            BorderRadius.all(4.0F)
                        );
                        e.getContext()
                            .drawText(font, text, posX + 12.8F + 4.0F, posY + 2.5F, Zenith.getInstance().getThemeManager().getCurrentTheme().getColor());
                        e.getContext().getMatrices().push();
                        e.getContext().getMatrices().translate(posX, posY, 0.0F);
                        e.getContext().getMatrices().scale(0.8F, 0.8F, 1.0F);
                        e.getContext().drawItem(cons.item.getDefaultStack(), 0, 0);
                        e.getContext().getMatrices().pop();
                    }
                }
            );
        this.serverEvents
            .forEach(
                event -> {
                    Vec3d vec3d = ProjectionUtil.worldSpaceToScreenSpace(event.vec);
                    double timeOpen = (event.timeOpen - (double)System.currentTimeMillis()) / 1000.0;
                    double timeEnd = (event.timeEnd - (double)System.currentTimeMillis()) / 1000.0;
                    String distance = " [" + MathUtil.round(mc.getEntityRenderDispatcher().camera.getPos().distanceTo(event.vec), 0.1) + "m]";
                    String time = timeOpen > 0.0
                        ? ("Until Start: " + MathUtil.round(timeOpen, timeOpen < 30.0 ? 0.1F : 1.0) + "s").replace(".0", "")
                        : (timeEnd > 0.0 ? ("Until End: " + MathUtil.round(timeEnd, timeEnd < 30.0 ? 0.1F : 1.0) + "s").replace(".0", "") : "Event End!");
                    if (ProjectionUtil.canSee(event.vec)
                        && event.anarchy == Zenith.getInstance().getServerHandler().getAnarchy()
                        && Zenith.getInstance().getServerHandler().getWorldType().equals(event.world)) {
                        List<String> list = new ArrayList<>(Collections.singletonList(event.name + distance));
                        if (event.owner != null) {
                            list.add("Summoned: " + Formatting.GOLD + event.owner);
                        }

                        list.add(time);
                        if (event.lvl != null) {
                            list.add(event.lvl);
                        }
                    }
                }
            );
        this.structures.removeIf(cons -> cons.time - (double)System.currentTimeMillis() <= 0.0);
        this.serverEvents.removeIf(event -> event.timeEnd + 90000.0 - (double)System.currentTimeMillis() <= 0.0);
    }

    private void drawItemCube(BlockPos playerPos, Vec3d smooth, float size, int color) {
        Box box = new Box(playerPos.up()).offset(smooth).contract(0.0, 0.2F, 0.0).expand((double)size);
        boolean inBox = mc.world
            .getPlayers()
            .stream()
            .anyMatch(
                ent -> ent != mc.player
                        && box.intersects(ent.getBoundingBox())
                        && !Zenith.getInstance().getFriendManager().isFriend(ent.getGameProfile().getName())
            );
        Render3DUtil.drawBox(box, inBox ? Zenith.getInstance().getThemeManager().getCurrentTheme().getColor().getRGB() : color, 3.0F, true, true, true);
    }

    private void drawItemRadius(MatrixStack matrix, float distance, int clr) {
        float playerHalfWidth = mc.player.getWidth() / 2.0F;
        int color = this.validDistance(distance) ? Zenith.getInstance().getThemeManager().getCurrentTheme().getColor().getRGB() : clr;
        Vec3d pos = MathUtil.interpolate(mc.player).add((double)playerHalfWidth, 0.02, (double)playerHalfWidth);
        Vec3d vec3d = pos.subtract(mc.getEntityRenderDispatcher().camera.getPos());
        GL11.glEnable(2881);
        RenderSystem.enableBlend();
        RenderSystem.enableDepthTest();
        RenderSystem.depthMask(false);
        RenderSystem.disableCull();
        RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_CONSTANT_ALPHA);
        RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
        BufferBuilder buffer = Tessellator.getInstance().begin(DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR);
        int i = 0;

        for (int size = 90; i <= size; i++) {
            Vec3d cosSin = MathUtil.cosSin(i, size, (double)distance);
            Vec3d nextCosSin = MathUtil.cosSin(i + 1, size, (double)distance);
            Render3DUtil.vertexLine(
                matrix,
                buffer,
                vec3d.add(cosSin),
                vec3d.add(cosSin.x, cosSin.y + 2.0, cosSin.z),
                ColorUtil.multAlpha(color, 0.2F),
                ColorUtil.multAlpha(color, 0.0F)
            );
            Render3DUtil.drawLine(pos.add(cosSin), pos.add(nextCosSin), color, 2.0F, true);
        }

        i = 0;

        for (int size = 90; i <= size; i++) {
            Vec3d cosSin = MathUtil.cosSin(i, size, (double)distance);
            Render3DUtil.vertexLine(
                matrix,
                buffer,
                vec3d.add(cosSin),
                vec3d.add(cosSin.x, cosSin.y - 2.0, cosSin.z),
                ColorUtil.multAlpha(color, 0.2F),
                ColorUtil.multAlpha(color, 0.0F)
            );
        }

        BufferRenderer.drawWithGlobalProgram(buffer.end());
        RenderSystem.disableDepthTest();
        RenderSystem.depthMask(true);
        RenderSystem.disableBlend();
        GL11.glDisable(2881);
    }

    private void draw(MatrixStack matrix, Font font, List<String> list, Vec3d vec3d) {
        float offsetY = 0.0F;

        for (int i = 0; i < list.size(); i++) {
            String string = list.get(i);
            float width = font.width(string);
            float posX = (float)(vec3d.x - (double)(width / 2.0F));
            offsetY += 10.0F;
        }
    }

    public void drawSidePlast(BlockPos blockPos, Vec3d smooth, int color, int i, boolean ff) {
        Vec3d vec3d = Vec3d.of(blockPos).add(smooth);
        float width = 2.0F;
        int quadColor = ColorUtil.multAlpha(color, 0.15F);
        this.drawHorizontalLines(vec3d, color, width, i, ff);
        this.drawHorizontalLines(vec3d, color, width, i, ff);
        this.drawVerticalLines(vec3d, color, width, i, ff);
        this.drawHorizontalQuads(vec3d, quadColor, i, ff);
        this.drawHorizontalQuads(vec3d, quadColor, i, ff);
        this.drawVerticalQuads(vec3d, quadColor, i, ff);
    }

    private void drawHorizontalLines(Vec3d vec3d, int color, float width, int i, boolean ff) {
        float x = ff ? (float)i : (float)(-i);
        Vec3d current;
        Render3DUtil.drawLine(vec3d, current = vec3d.add((double)x, 0.0, 0.0), color, width, true);

        for (int f = 0; f < 4; f++) {
            Vec3d var9;
            Render3DUtil.drawLine(current, var9 = current.add(0.0, 0.0, (double)i), color, width, true);
            Render3DUtil.drawLine(var9, current = var9.add((double)x, 0.0, 0.0), color, width, true);
        }

        Vec3d var10;
        Render3DUtil.drawLine(current, var10 = current.add(0.0, 0.0, (double)i), color, width, true);
        Render3DUtil.drawLine(var10, current = var10.add((double)(x * -2.0F), 0.0, 0.0), color, width, true);

        for (int f = 0; f < 3; f++) {
            Vec3d var12;
            Render3DUtil.drawLine(current, var12 = current.add(0.0, 0.0, (double)(i * -1)), color, width, true);
            Render3DUtil.drawLine(var12, current = var12.add((double)(x * -1.0F), 0.0, 0.0), color, width, true);
        }

        Render3DUtil.drawLine(current, current.add(0.0, 0.0, (double)(i * -2)), color, width, true);
    }

    private void drawVerticalLines(Vec3d vec3d, int color, float width, int i, boolean ff) {
        float x = ff ? (float)i : (float)(-i);
        Render3DUtil.drawLine(vec3d, vec3d.add(0.0, 5.0, 0.0), color, width, true);
        Vec3d var8;
        Render3DUtil.drawLine(var8 = vec3d.add((double)x, 0.0, 0.0), var8.add(0.0, 5.0, 0.0), color, width, true);

        for (int f = 0; f < 4; f++) {
            Render3DUtil.drawLine(var8 = var8.add((double)x, 0.0, (double)i), var8.add(0.0, 5.0, 0.0), color, width, true);
        }

        Render3DUtil.drawLine(vec3d = var8.add(0.0, 0.0, (double)i), vec3d.add(0.0, 5.0, 0.0), color, width, true);
        Vec3d var10;
        Render3DUtil.drawLine(var10 = vec3d.add((double)(x * -2.0F), 0.0, 0.0), var10.add(0.0, 5.0, 0.0), color, width, true);

        for (int f = 0; f < 3; f++) {
            Render3DUtil.drawLine(var10 = var10.add((double)(x * -1.0F), 0.0, (double)(i * -1)), var10.add(0.0, 5.0, 0.0), color, width, true);
        }
    }

    private void drawHorizontalQuads(Vec3d vec3d, int color, int i, boolean ff) {
        vec3d = vec3d.add(0.0, 0.001, 0.0);
        float x = ff ? (float)i : (float)(-i);
        Render3DUtil.drawQuad(
            vec3d,
            vec3d.add((double)x, 0.0, 0.0),
            vec3d.add((double)x, 0.0, (double)(i * 2)),
            vec3d.add(0.0, 0.0, (double)(i * 2)),
            color,
            true
        );

        for (int f = 0; f < 3; f++) {
            Render3DUtil.drawQuad(
                vec3d = vec3d.add((double)x, 0.0, (double)i),
                vec3d.add((double)x, 0.0, 0.0),
                vec3d.add((double)x, 0.0, (double)(i * 2)),
                vec3d.add(0.0, 0.0, (double)(i * 2)),
                color,
                true
            );
        }

        Vec3d var8;
        Render3DUtil.drawQuad(
            var8 = vec3d.add((double)x, 0.0, (double)i),
            var8.add((double)x, 0.0, 0.0),
            var8.add((double)x, 0.0, (double)i),
            var8.add(0.0, 0.0, (double)i),
            color,
            true
        );
    }

    private void drawVerticalQuads(Vec3d vec3d, int color, int i, boolean ff) {
        float x = ff ? (float)i : (float)(-i);
        Render3DUtil.drawQuad(
            vec3d, vec3d.add((double)x, 0.0, 0.0), vec3d.add((double)x, 5.0, 0.0), vec3d.add(0.0, 5.0, 0.0), color, true
        );

        for (int f = 0; f < 4; f++) {
            Vec3d var7;
            Render3DUtil.drawQuad(
                var7 = vec3d.add((double)x, 0.0, 0.0),
                var7.add(0.0, 0.0, (double)i),
                var7.add(0.0, 5.0, (double)i),
                var7.add(0.0, 5.0, 0.0),
                color,
                true
            );
            Render3DUtil.drawQuad(
                vec3d = var7.add(0.0, 0.0, (double)i),
                vec3d.add((double)x, 0.0, 0.0),
                vec3d.add((double)x, 5.0, 0.0),
                vec3d.add(0.0, 5.0, 0.0),
                color,
                true
            );
        }

        Vec3d var8;
        Render3DUtil.drawQuad(
            var8 = vec3d.add((double)x, 0.0, 0.0),
            var8.add(0.0, 0.0, (double)i),
            var8.add(0.0, 5.0, (double)i),
            var8.add(0.0, 5.0, 0.0),
            color,
            true
        );
        Render3DUtil.drawQuad(
            vec3d = var8.add(0.0, 0.0, (double)i),
            vec3d.add((double)(x * -2.0F), 0.0, 0.0),
            vec3d.add((double)(x * -2.0F), 5.0, 0.0),
            vec3d.add(0.0, 5.0, 0.0),
            color,
            true
        );
        vec3d = vec3d.add((double)(x * -1.0F), 0.0, 0.0);

        for (int f = 0; f < 3; f++) {
            Vec3d var11;
            Render3DUtil.drawQuad(
                var11 = vec3d.add((double)(x * -1.0F), 0.0, 0.0),
                var11.add(0.0, 0.0, (double)(i * -1)),
                var11.add(0.0, 5.0, (double)(i * -1)),
                var11.add(0.0, 5.0, 0.0),
                color,
                true
            );
            Render3DUtil.drawQuad(
                vec3d = var11.add(0.0, 0.0, (double)(i * -1)),
                vec3d.add((double)(x * -1.0F), 0.0, 0.0),
                vec3d.add((double)(x * -1.0F), 5.0, 0.0),
                vec3d.add(0.0, 5.0, 0.0),
                color,
                true
            );
        }

        Vec3d var12;
        Render3DUtil.drawQuad(
            var12 = vec3d.add((double)(x * -1.0F), 0.0, 0.0),
            var12.add(0.0, 0.0, (double)(i * -2)),
            var12.add(0.0, 5.0, (double)(i * -2)),
            var12.add(0.0, 5.0, 0.0),
            color,
            true
        );
    }

    private void addEvent(String name, String lvl, String owner, Vec3d vec3d, String world, int timeOpen, int timeLoot) {
        if (this.serverEvents.stream().noneMatch(server -> server.vec.equals(vec3d))) {
            long open = System.currentTimeMillis() + (long)timeOpen * 1000L;
            long loot = open + (long)timeLoot * 1000L;
            this.serverEvents
                .add(
                    new ServerHelper.ServerEvent(
                        name, lvl, owner, vec3d, world, Zenith.getInstance().getServerHandler().getAnarchy(), (double)open, (double)loot
                    )
                );
        }
    }

    private void addStructure(Item item, Vec3d vec, double time) {
        if (this.structures.stream().noneMatch(str -> str.vec.equals(vec))) {
            this.structures
                .add(
                    new ServerHelper.Structure(
                        item, vec, Zenith.getInstance().getServerHandler().getWorldType(), Zenith.getInstance().getServerHandler().getAnarchy(), time
                    )
                );
        }
    }

    private Vector4f getRound(Font font, List<String> list, int i, float width) {
        if (i == 0) {
            float next = font.width(list.get(i + 1));
            return next >= width ? new Vector4f(2.0F, 0.0F, 2.0F, 0.0F) : new Vector4f(2.0F);
        } else if (i == list.size() - 1) {
            float prev = font.width(list.get(i - 1));
            return prev >= width ? new Vector4f(0.0F, 2.0F, 0.0F, 2.0F) : new Vector4f(2.0F);
        } else {
            float prev = font.width(list.get(i - 1));
            float next = font.width(list.get(i + 1));
            return prev >= width ? (next >= width ? new Vector4f() : new Vector4f(0.0F, 2.0F, 0.0F, 2.0F)) : new Vector4f(2.0F);
        }
    }

    private boolean validDistance(float dist) {
        return dist == 0.0F
            || mc.world
                .getPlayers()
                .stream()
                .anyMatch(
                    p -> p != mc.player
                            && !Zenith.getInstance().getFriendManager().isFriend(p.getGameProfile().getName())
                            && mc.player.distanceTo(p) <= dist
                );
    }

    private boolean isTrap(BlockPos center) {
        long currentTime = System.currentTimeMillis();
        if (this.trapCacheTime.containsKey(center) && currentTime - this.trapCacheTime.get(center) < 1000L) {
            return this.trapCache.get(center);
        } else {
            boolean result = this.checkTrap(center);
            this.trapCache.put(center, result);
            this.trapCacheTime.put(center, currentTime);
            return result;
        }
    }

    private boolean checkTrap(BlockPos center) {
        int inconsistencies = 0;

        for (BlockPos pos : PlayerIntersectionUtil.getCube(center, 2.0F)) {
            if (MathUtil.getDistance(pos.toCenterPos(), center.toCenterPos()) < 2.0) {
                BlockState state = this.blockStateMap.get(pos);
                if (state != null && !state.isAir()) {
                    inconsistencies++;
                }
            } else if (!pos.equals(center.up(2).north().east())
                && !pos.equals(center.up(2).north().west())
                && !pos.equals(center.up(2).south().east())
                && !pos.equals(center.up(2).south().west())) {
                BlockState state = this.blockStateMap.get(pos);
                if (state == null || state.isAir()) {
                    inconsistencies++;
                }
            }

            if (inconsistencies > 1) {
                return false;
            }
        }

        return true;
    }

    private boolean isBigTrap(BlockPos center) {
        long currentTime = System.currentTimeMillis();
        if (this.bigTrapCacheTime.containsKey(center) && currentTime - this.bigTrapCacheTime.get(center) < 1000L) {
            return this.bigTrapCache.get(center);
        } else {
            boolean result = this.checkBigTrap(center);
            this.bigTrapCache.put(center, result);
            this.bigTrapCacheTime.put(center, currentTime);
            return result;
        }
    }

    private boolean checkBigTrap(BlockPos center) {
        int inconsistencies = 0;

        for (BlockPos pos : PlayerIntersectionUtil.getCube(center, 3.0F)) {
            if (Math.abs(pos.getX() - center.getX()) <= 2
                && Math.abs(pos.getY() - center.getY()) <= 2
                && Math.abs(pos.getZ() - center.getZ()) <= 2) {
                BlockState state = this.blockStateMap.get(pos);
                if (state != null && !state.isAir()) {
                    inconsistencies++;
                }
            } else if (!pos.equals(center.up(3))) {
                BlockState state = this.blockStateMap.get(pos);
                if (state == null || state.isAir()) {
                    inconsistencies++;
                }
            }

            if (inconsistencies > 1) {
                return false;
            }
        }

        return true;
    }

    private static boolean isSolid(BlockState state) {
        return state != null && !state.isAir();
    }

    private void cleanCache() {
        long currentTime = System.currentTimeMillis();
        this.trapCacheTime.entrySet().removeIf(entry -> currentTime - entry.getValue() > 1000L);
        this.trapCache.entrySet().removeIf(entry -> !this.trapCacheTime.containsKey(entry.getKey()));
        this.bigTrapCacheTime.entrySet().removeIf(entry -> currentTime - entry.getValue() > 1000L);
        this.bigTrapCache.entrySet().removeIf(entry -> !this.bigTrapCacheTime.containsKey(entry.getKey()));
        if (this.trapCache.size() > 1000) {
            this.trapCache.clear();
            this.trapCacheTime.clear();
        }

        if (this.bigTrapCache.size() > 1000) {
            this.bigTrapCache.clear();
            this.bigTrapCacheTime.clear();
        }
    }

    private boolean matchesMask(BlockPos center, byte[][][] mask, int radius, int tolerance) {
        int inconsistencies = 0;
        Mutable m = new Mutable();

        for (int dy = -radius; dy <= radius; dy++) {
            for (int dz = -radius; dz <= radius; dz++) {
                for (int dx = -radius; dx <= radius; dx++) {
                    byte req = mask[dy + radius][dz + radius][dx + radius];
                    if (req != -1) {
                        m.set(center.getX() + dx, center.getY() + dy, center.getZ() + dz);
                        BlockState st = this.blockStateMap.get(m);
                        boolean solid = isSolid(st);
                        if (req == 1 && !solid || req == 0 && solid) {
                            if (++inconsistencies > tolerance) {
                                return false;
                            }
                        }
                    }
                }
            }
        }

        return true;
    }

    public static record KeyBind(Item item, KeySetting setting, float distance, BooleanSettable draw) {
    }

    public static record ServerEvent(String name, String lvl, String owner, Vec3d vec, String world, int anarchy, double timeOpen, double timeEnd) {
    }

    public static record Structure(Item item, Vec3d vec, String world, int anarchy, double time) {
    }
}
