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

import com.darkmagician6.eventapi.EventTarget;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.minecraft.block.AirBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.MapColor;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.ChunkDeltaUpdateS2CPacket;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.chunk.WorldChunk;
import zenith.zov.base.events.impl.render.EventRender3D;
import zenith.zov.base.events.impl.server.EventPacket;
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.impl.ItemSelectSetting;
import zenith.zov.client.modules.api.setting.impl.ModeSetting;
import zenith.zov.client.modules.api.setting.impl.NumberSetting;
import zenith.zov.utility.math.Timer;
import zenith.zov.utility.render.level.Render3DUtil;

@ModuleAnnotation(
    name = "BlockESP",
    category = Category.RENDER,
    description = "Highlights blocks and block entities like chests"
)
public final class BlockESP extends Module {
    private volatile List<BlockPos> blockPos = new ArrayList<>();
    private volatile List<BlockEntity> blockEntityPos = new ArrayList<>();
    private final ItemSelectSetting itemSelectSetting = new ItemSelectSetting("Blocks", new ArrayList<>());
    private ModeSetting mode = new ModeSetting("Mode");
    private ModeSetting.Value onlyUpdate = new ModeSetting.Value(this.mode, "Update").select();
    private ModeSetting.Value all = new ModeSetting.Value(this.mode, "Scan");
    private final NumberSetting range = new NumberSetting("Radius", 80.0F, 1.0F, 128.0F, 2.0F, this.all::isSelected);
    private final NumberSetting time = new NumberSetting("Timer", 4.0F, 0.0F, 100.0F, 5.0F, this.all::isSelected);
    private final Timer timerUtil = new Timer();
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    private volatile boolean isStarted = true;
    public static BlockESP INSTANCE = new BlockESP();

    private BlockESP() {
        this.itemSelectSetting.add(Blocks.DIAMOND_ORE);
        this.itemSelectSetting.add(Blocks.CHEST);
        this.itemSelectSetting.add(Blocks.ENDER_CHEST);
        this.itemSelectSetting.add(Blocks.TRAPPED_CHEST);
        this.itemSelectSetting.add(Blocks.BARREL);
        this.itemSelectSetting.add(Blocks.SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.WHITE_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.ORANGE_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.MAGENTA_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.LIGHT_BLUE_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.YELLOW_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.LIME_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.PINK_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.GRAY_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.LIGHT_GRAY_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.CYAN_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.PURPLE_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.BLUE_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.BROWN_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.GREEN_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.RED_SHULKER_BOX);
        this.itemSelectSetting.add(Blocks.BLACK_SHULKER_BOX);
    }

    @Override
    public void onEnable() {
        this.timerUtil.setMillis(0L);
        this.isStarted = true;
        super.onEnable();
    }

    @Override
    public void onDisable() {
        this.isStarted = false;
        super.onDisable();
    }

    @EventTarget
    public void render(EventRender3D eventRender3D) {
        if (this.timerUtil.finished(this.time.getCurrent() * 1000.0F) && !this.onlyUpdate.isSelected() && this.isStarted) {
            this.executor.submit(this::scan);
            this.executor.submit(this::scanBlockEntities);
            this.timerUtil.reset();
        }

        for (BlockPos pos : this.blockPos) {
            Block block = mc.world.getBlockState(pos).getBlock();
            if (this.itemSelectSetting.contains(block)) {
                if (block == Blocks.DIAMOND_ORE || block == Blocks.DEEPSLATE_DIAMOND_ORE) {
                    this.drawBox(pos, Color.cyan.getRGB());
                } else if (block == Blocks.GOLD_ORE || block == Blocks.DEEPSLATE_GOLD_ORE) {
                    this.drawBox(pos, -10496);
                } else if (block == Blocks.NETHER_GOLD_ORE) {
                    this.drawBox(pos, -10496);
                } else if (block == Blocks.EMERALD_ORE || block == Blocks.DEEPSLATE_EMERALD_ORE) {
                    this.drawBox(pos, -16711859);
                } else if (block == Blocks.IRON_ORE || block == Blocks.DEEPSLATE_IRON_ORE) {
                    this.drawBox(pos, -2763307);
                } else if (block == Blocks.REDSTONE_ORE || block == Blocks.DEEPSLATE_REDSTONE_ORE) {
                    this.drawBox(pos, -65536);
                } else if (block == Blocks.ANCIENT_DEBRIS) {
                    this.drawBox(pos, -1);
                } else if (block == Blocks.CHEST || block == Blocks.TRAPPED_CHEST) {
                    this.drawBox(pos, -7650029);
                } else if (block == Blocks.ENDER_CHEST) {
                    this.drawBox(pos, -11861886);
                } else if (block == Blocks.BARREL) {
                    this.drawBox(pos, -7650029);
                } else if (block.toString().contains("SHULKER_BOX")) {
                    this.drawBox(pos, -7114533);
                } else {
                    MapColor color = block.getDefaultMapColor();
                    int rgb = new Color(color.color).getRGB();
                    this.drawBox(pos, rgb);
                }
            }
        }

        for (BlockEntity blockEntity : this.blockEntityPos) {
            BlockPos posx = blockEntity.getPos();
            Block block = mc.world.getBlockState(posx).getBlock();
            if (this.itemSelectSetting.contains(block)) {
                int color = this.getBlockEntityColor(block);
                this.drawBox(posx, color);
            }
        }
    }

    @EventTarget
    public void packer(EventPacket eventPacket) {
        if (eventPacket.isReceive()) {
            if (eventPacket.getPacket() instanceof ChunkDeltaUpdateS2CPacket chunkDelta) {
                chunkDelta.visitUpdates((pos, state) -> {
                    if (this.itemSelectSetting.contains(state.getBlock())) {
                        this.blockPos.add(pos);
                    }
                });
            }

            if (eventPacket.getPacket() instanceof BlockUpdateS2CPacket chunkDelta && this.itemSelectSetting.contains(chunkDelta.getState().getBlock())
                )
             {
                this.blockPos.add(chunkDelta.getPos());
            }
        }
    }

    public void drawBox(BlockPos blockPos, int start) {
        Render3DUtil.drawBox(new Box(blockPos), start, 1.0F);
    }

    private int getBlockEntityColor(Block block) {
        if (block == Blocks.CHEST || block == Blocks.TRAPPED_CHEST) {
            return -7650029;
        } else if (block == Blocks.ENDER_CHEST) {
            return -11861886;
        } else if (block == Blocks.BARREL) {
            return -7650029;
        } else if (block.toString().contains("SHULKER_BOX")) {
            return -7114533;
        } else {
            return block != Blocks.FURNACE && block != Blocks.BLAST_FURNACE && block != Blocks.SMOKER ? -1 : -9868951;
        }
    }

    private void scan() {
        ArrayList<BlockPos> blocks = new ArrayList<>();
        int startX = (int)Math.floor(mc.player.getX() - (double)this.range.getCurrent());
        int endX = (int)Math.ceil(mc.player.getX() + (double)this.range.getCurrent());
        int startY = mc.world.getBottomY() + 1;
        int endY = mc.world.getTopYInclusive();
        int startZ = (int)Math.floor(mc.player.getZ() - (double)this.range.getCurrent());
        int endZ = (int)Math.ceil(mc.player.getZ() + (double)this.range.getCurrent());

        for (int x = startX; x <= endX; x++) {
            for (int y = startY; y <= endY; y++) {
                for (int z = startZ; z <= endZ; z++) {
                    if (!this.isStarted) {
                        return;
                    }

                    BlockPos pos = new BlockPos(x, y, z);
                    Block block = mc.world.getBlockState(pos).getBlock();
                    if (!(block instanceof AirBlock) && this.itemSelectSetting.contains(block)) {
                        blocks.add(pos);
                    }
                }
            }
        }

        this.blockPos = blocks;
        this.isStarted = true;
    }

    private void scanBlockEntities() {
        if (mc.world != null && mc.player != null) {
            ArrayList<BlockEntity> blockEntities = new ArrayList<>();
            double range = (double)this.range.getCurrent();
            int startX = (int)Math.floor(mc.player.getX() - range) >> 4;
            int endX = (int)Math.ceil(mc.player.getX() + range) >> 4;
            int startZ = (int)Math.floor(mc.player.getZ() - range) >> 4;
            int endZ = (int)Math.ceil(mc.player.getZ() + range) >> 4;

            for (int chunkX = startX; chunkX <= endX; chunkX++) {
                for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) {
                    if (!this.isStarted) {
                        return;
                    }

                    WorldChunk chunk = mc.world.getChunk(chunkX, chunkZ);
                    if (chunk != null) {
                        for (BlockEntity blockEntity : chunk.getBlockEntities().values()) {
                            if (!this.isStarted) {
                                return;
                            }

                            BlockPos blockPos = blockEntity.getPos();
                            Vec3d blockCenter = new Vec3d(
                                (double)blockPos.getX() + 0.5, (double)blockPos.getY() + 0.5, (double)blockPos.getZ() + 0.5
                            );
                            double distance = mc.player.getPos().distanceTo(blockCenter);
                            if (distance <= range) {
                                Block block = mc.world.getBlockState(blockPos).getBlock();
                                if (this.itemSelectSetting.contains(block)) {
                                    blockEntities.add(blockEntity);
                                }
                            }
                        }
                    }
                }
            }

            this.blockEntityPos = blockEntities;
        }
    }
}
