/*
 * Decompiled with CFR 0.152.
 * 
 * Could not load the following classes:
 *  meteordevelopment.orbit.EventHandler
 *  net.minecraft.class_1923
 *  net.minecraft.class_2246
 *  net.minecraft.class_2248
 *  net.minecraft.class_2338
 *  net.minecraft.class_2541
 *  net.minecraft.class_2680
 *  net.minecraft.class_2769
 *  net.minecraft.class_2818
 *  net.minecraft.class_2902$class_2903
 *  net.minecraft.class_3830
 *  net.minecraft.class_5689
 */
package meteordevelopment.meteorclient.systems.modules.donut;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import meteordevelopment.meteorclient.events.render.Render3DEvent;
import meteordevelopment.meteorclient.events.world.TickEvent;
import meteordevelopment.meteorclient.renderer.ShapeMode;
import meteordevelopment.meteorclient.settings.BoolSetting;
import meteordevelopment.meteorclient.settings.IntSetting;
import meteordevelopment.meteorclient.settings.Setting;
import meteordevelopment.meteorclient.settings.SettingGroup;
import meteordevelopment.meteorclient.systems.modules.Categories;
import meteordevelopment.meteorclient.systems.modules.Module;
import meteordevelopment.meteorclient.utils.render.color.Color;
import meteordevelopment.orbit.EventHandler;
import net.minecraft.class_1923;
import net.minecraft.class_2246;
import net.minecraft.class_2248;
import net.minecraft.class_2338;
import net.minecraft.class_2541;
import net.minecraft.class_2680;
import net.minecraft.class_2769;
import net.minecraft.class_2818;
import net.minecraft.class_2902;
import net.minecraft.class_3830;
import net.minecraft.class_5689;

public class GrowthFinder
extends Module {
    private final SettingGroup sgGeneral;
    private final Setting<Boolean> renderVines;
    private final Setting<Boolean> renderDripstone;
    private final Setting<Boolean> renderBerries;
    private final Setting<Boolean> renderStandardChunks;
    private final Setting<Integer> alpha;
    private static final int SCAN_RADIUS = 8;
    private static final int CHUNKS_PER_TICK = 12;
    private static final int MIN_VINE_LENGTH = 6;
    private static final int MAX_VINE_SCAN_PER_CHUNK = 2;
    private static final double PLATE_HEIGHT = 0.08;
    private static final Color HIGH_SUSPICION_GRAY = new Color(135, 135, 135);
    private static final Color LOW_SUSPICION_GRAY = new Color(100, 100, 100);
    private static final Color SOURCE_PLATE_COLOR_BASE = new Color(203, 64, 255);
    private static final Color EXTREME_PLATE_COLOR_BASE = new Color(255, 198, 64);
    private final Set<class_1923> scannedChunks;
    private final Map<class_1923, SuspiciousGrowthData> suspiciousChunks;
    private final List<class_1923> scanQueue;
    private final Map<class_1923, Double> sourceHistory;
    private class_1923 lastQueueCenter;
    private int scanCursor;

    public GrowthFinder() {
        super(Categories.Donut, "growth-finder", "Detects suspicious chunk loading patterns.");
        this.sgGeneral = this.settings.getDefaultGroup();
        this.renderVines = this.sgGeneral.add(((BoolSetting.Builder)((BoolSetting.Builder)new BoolSetting.Builder().name("render-vines")).defaultValue(true)).build());
        this.renderDripstone = this.sgGeneral.add(((BoolSetting.Builder)((BoolSetting.Builder)new BoolSetting.Builder().name("render-dripstone")).defaultValue(true)).build());
        this.renderBerries = this.sgGeneral.add(((BoolSetting.Builder)((BoolSetting.Builder)new BoolSetting.Builder().name("render-berries")).defaultValue(true)).build());
        this.renderStandardChunks = this.sgGeneral.add(((BoolSetting.Builder)((BoolSetting.Builder)new BoolSetting.Builder().name("render-gray-chunks")).defaultValue(true)).build());
        this.alpha = this.sgGeneral.add(((IntSetting.Builder)((IntSetting.Builder)new IntSetting.Builder().name("alpha")).defaultValue(80)).min(0).max(255).build());
        this.scannedChunks = new HashSet<class_1923>();
        this.suspiciousChunks = new HashMap<class_1923, SuspiciousGrowthData>();
        this.scanQueue = new ArrayList<class_1923>();
        this.sourceHistory = new HashMap<class_1923, Double>();
        this.lastQueueCenter = null;
        this.scanCursor = 0;
    }

    @Override
    public void onActivate() {
        this.clearData();
    }

    @Override
    public void onDeactivate() {
        this.clearData();
    }

    private void clearData() {
        this.suspiciousChunks.clear();
        this.scannedChunks.clear();
        this.scanQueue.clear();
        this.scanCursor = 0;
        this.lastQueueCenter = null;
        this.sourceHistory.clear();
    }

    @EventHandler
    private void onTick(TickEvent.Post event) {
        if (this.mc.field_1724 == null || this.mc.field_1687 == null) {
            return;
        }
        class_1923 playerChunk = new class_1923(this.mc.field_1724.method_24515());
        this.rebuildScanQueueIfNeeded(playerChunk);
        this.processChunkBatch();
        this.pruneFarChunks(playerChunk, 8);
        if (this.mc.field_1687.method_75260() % 20L == 0L) {
            this.refreshLockedBaseChunk();
        }
    }

    private void rebuildScanQueueIfNeeded(class_1923 playerChunk) {
        if (this.lastQueueCenter == null || playerChunk.field_9181 != this.lastQueueCenter.field_9181 || playerChunk.field_9180 != this.lastQueueCenter.field_9180 || this.scanCursor >= this.scanQueue.size()) {
            this.scanQueue.clear();
            for (int x = -8; x <= 8; ++x) {
                for (int z = -8; z <= 8; ++z) {
                    this.scanQueue.add(new class_1923(playerChunk.field_9181 + x, playerChunk.field_9180 + z));
                }
            }
            this.scanCursor = 0;
            this.lastQueueCenter = playerChunk;
        }
    }

    private void processChunkBatch() {
        int processed = 0;
        while (this.scanCursor < this.scanQueue.size() && processed < 12) {
            class_1923 chunkPos;
            if (this.scannedChunks.contains(chunkPos = this.scanQueue.get(this.scanCursor++))) continue;
            class_2818 chunk = this.mc.field_1687.method_2935().method_21730(chunkPos.field_9181, chunkPos.field_9180);
            if (chunk != null) {
                this.analyzeChunk(chunk, chunkPos);
                this.scannedChunks.add(chunkPos);
            }
            ++processed;
        }
    }

    private void pruneFarChunks(class_1923 playerChunk, int radius) {
        int maxDistance = radius + 2;
        int pX = playerChunk.field_9181;
        int pZ = playerChunk.field_9180;
        this.suspiciousChunks.keySet().removeIf(chunkPos -> Math.abs(chunkPos.field_9181 - pX) > maxDistance || Math.abs(chunkPos.field_9180 - pZ) > maxDistance);
        this.scannedChunks.removeIf(chunkPos -> Math.abs(chunkPos.field_9181 - pX) > maxDistance || Math.abs(chunkPos.field_9180 - pZ) > maxDistance);
    }

    private void refreshLockedBaseChunk() {
        if (this.suspiciousChunks.isEmpty()) {
            this.sourceHistory.clear();
            return;
        }
        for (SuspiciousGrowthData data : this.suspiciousChunks.values()) {
            class_1923 sourceChunk = data.baseChunk;
            this.sourceHistory.merge(sourceChunk, Double.valueOf(data.suspicionLevel), Double::sum);
        }
        this.sourceHistory.entrySet().removeIf(e -> {
            double val = (Double)e.getValue() * 0.95;
            e.setValue(val);
            return val < 0.5;
        });
    }

    private void analyzeChunk(class_2818 chunk, class_1923 chunkPos) {
        int xStart = chunkPos.method_8326();
        int zStart = chunkPos.method_8328();
        List<VineCluster> vineClusters = this.renderVines.get() != false ? this.detectTallGroundedVines(chunk, xStart, zStart) : Collections.emptyList();
        List<DripstoneCluster> dripstoneClusters = this.renderDripstone.get() != false ? this.detectMaxDripstoneClusters(chunk, xStart, zStart) : Collections.emptyList();
        List<BerryCluster> berryClusters = this.renderBerries.get() != false ? this.detectMaxGrownBerries(chunk, xStart, zStart) : Collections.emptyList();
        ArrayList<WeightedEvidence> evidencePoints = new ArrayList<WeightedEvidence>();
        double vineWeight = 0.0;
        int maxVineLength = 0;
        for (VineCluster cluster : vineClusters) {
            double weight = cluster.length;
            vineWeight += weight;
            if (cluster.length > maxVineLength) {
                maxVineLength = cluster.length;
            }
            evidencePoints.add(new WeightedEvidence(cluster.centroid(), weight));
        }
        double dripWeight = 0.0;
        for (DripstoneCluster cluster : dripstoneClusters) {
            dripWeight += 2.5;
            evidencePoints.add(new WeightedEvidence(cluster.center(), 2.5));
        }
        double berryWeight = 0.0;
        for (BerryCluster cluster : berryClusters) {
            berryWeight += 1.0;
            evidencePoints.add(new WeightedEvidence(cluster.pos, 1.0));
        }
        int suspicionLevel = (int)Math.round(vineWeight * 0.5 + dripWeight * 0.75 + berryWeight);
        if (suspicionLevel > 0) {
            class_2338 estimatedSource = this.calculateWeightedSource(evidencePoints, chunkPos);
            boolean isExtreme = maxVineLength >= 100;
            boolean isSource = maxVineLength >= 25;
            this.suspiciousChunks.put(chunkPos, new SuspiciousGrowthData(chunkPos, suspicionLevel, new class_1923(estimatedSource), isExtreme, isSource, maxVineLength));
        } else {
            this.suspiciousChunks.remove(chunkPos);
        }
    }

    private List<VineCluster> detectTallGroundedVines(class_2818 chunk, int xStart, int zStart) {
        ArrayList<VineCluster> clusters = new ArrayList<VineCluster>();
        HashSet<class_2338> visited = new HashSet<class_2338>();
        int groundY = chunk.method_31607();
        int tallClusters = 0;
        for (int x = xStart; x < xStart + 16; x += 2) {
            for (int z = zStart; z < zStart + 16 && tallClusters < 2; z += 2) {
                int y = chunk.method_12032(class_2902.class_2903.field_13197).method_12603(x - xStart, z - zStart);
                while (y >= groundY) {
                    class_2338 pos = new class_2338(x, y, z);
                    if (visited.contains(pos)) {
                        --y;
                        continue;
                    }
                    class_2680 state = chunk.method_8320(pos);
                    if (!this.isVineBlock(state.method_26204())) {
                        --y;
                        continue;
                    }
                    ArrayList<class_2338> positions = new ArrayList<class_2338>();
                    class_2338 currTrace = pos;
                    while (currTrace.method_10264() >= groundY && this.isVineBlock(chunk.method_8320(currTrace).method_26204())) {
                        visited.add(currTrace);
                        positions.add(currTrace);
                        currTrace = currTrace.method_10074();
                    }
                    if (positions.size() >= 6) {
                        clusters.add(new VineCluster(positions));
                        ++tallClusters;
                    }
                    y = currTrace.method_10264() - 1;
                }
            }
        }
        return clusters;
    }

    private boolean isVineBlock(class_2248 block) {
        return block instanceof class_2541 || block == class_2246.field_28675 || block == class_2246.field_28676 || block == class_2246.field_22123 || block == class_2246.field_22124 || block == class_2246.field_23078 || block == class_2246.field_23079;
    }

    private List<DripstoneCluster> detectMaxDripstoneClusters(class_2818 chunk, int xStart, int zStart) {
        ArrayList<DripstoneCluster> clusters = new ArrayList<DripstoneCluster>();
        for (int x = xStart; x < xStart + 16; x += 4) {
            block1: for (int z = zStart; z < zStart + 16; z += 4) {
                int heightmapY = chunk.method_12032(class_2902.class_2903.field_13197).method_12603(x - xStart, z - zStart);
                for (int y = -64; y <= heightmapY; y += 4) {
                    class_2338 pos = new class_2338(x, y, z);
                    if (!(chunk.method_8320(pos).method_26204() instanceof class_5689)) continue;
                    clusters.add(new DripstoneCluster(pos));
                    continue block1;
                }
            }
        }
        return clusters;
    }

    private List<BerryCluster> detectMaxGrownBerries(class_2818 chunk, int xStart, int zStart) {
        ArrayList<BerryCluster> clusters = new ArrayList<BerryCluster>();
        for (int x = xStart; x < xStart + 16; x += 2) {
            for (int z = zStart; z < zStart + 16; z += 2) {
                int heightmapY;
                for (int y = heightmapY = chunk.method_12032(class_2902.class_2903.field_13197).method_12603(x - xStart, z - zStart); y >= heightmapY - 5; --y) {
                    class_2338 pos = new class_2338(x, y, z);
                    class_2680 state = chunk.method_8320(pos);
                    if (!(state.method_26204() instanceof class_3830)) continue;
                    try {
                        if ((Integer)state.method_11654((class_2769)class_3830.field_17000) != 3) continue;
                        clusters.add(new BerryCluster(pos));
                        continue;
                    }
                    catch (Exception exception) {
                        // empty catch block
                    }
                }
            }
        }
        return clusters;
    }

    private class_2338 calculateWeightedSource(List<WeightedEvidence> evidencePoints, class_1923 chunkPos) {
        if (evidencePoints.isEmpty()) {
            return new class_2338(chunkPos.method_8326() + 8, 30, chunkPos.method_8328() + 8);
        }
        double sumX = 0.0;
        double sumY = 0.0;
        double sumZ = 0.0;
        double sumW = 0.0;
        for (WeightedEvidence e : evidencePoints) {
            sumX += (double)e.pos.method_10263() * e.weight;
            sumY += (double)e.pos.method_10264() * e.weight;
            sumZ += (double)e.pos.method_10260() * e.weight;
            sumW += e.weight;
        }
        return new class_2338((int)(sumX / sumW), (int)(sumY / sumW), (int)(sumZ / sumW));
    }

    @EventHandler
    private void onRender(Render3DEvent event) {
        if (this.suspiciousChunks.isEmpty()) {
            return;
        }
        for (SuspiciousGrowthData data : this.suspiciousChunks.values()) {
            boolean isStandard;
            double startX = data.chunkPos.method_8326();
            double startZ = data.chunkPos.method_8328();
            double y = 30.0;
            boolean bl = isStandard = !data.extreme && !data.source;
            if (isStandard && !this.renderStandardChunks.get().booleanValue()) continue;
            Color baseColor = data.extreme ? EXTREME_PLATE_COLOR_BASE : (data.source ? SOURCE_PLATE_COLOR_BASE : (data.suspicionLevel >= 5 || data.maxVineLength >= 4 ? HIGH_SUSPICION_GRAY : LOW_SUSPICION_GRAY));
            Color finalColor = new Color(baseColor.r, baseColor.g, baseColor.b, this.alpha.get());
            event.renderer.box(startX, y, startZ, startX + 16.0, y + 0.08, startZ + 16.0, finalColor, finalColor, ShapeMode.Both, 0);
        }
    }

    private record SuspiciousGrowthData(class_1923 chunkPos, int suspicionLevel, class_1923 baseChunk, boolean extreme, boolean source, int maxVineLength) {
    }

    private static class VineCluster {
        final int length;
        final List<class_2338> positions;

        VineCluster(List<class_2338> p) {
            this.positions = p;
            this.length = p.size();
        }

        class_2338 centroid() {
            long x = 0L;
            long y = 0L;
            long z = 0L;
            for (class_2338 p : this.positions) {
                x += (long)p.method_10263();
                y += (long)p.method_10264();
                z += (long)p.method_10260();
            }
            return new class_2338((int)(x / (long)this.positions.size()), (int)(y / (long)this.positions.size()), (int)(z / (long)this.positions.size()));
        }
    }

    private record WeightedEvidence(class_2338 pos, double weight) {
    }

    private record DripstoneCluster(class_2338 center) {
    }

    private record BerryCluster(class_2338 pos) {
    }
}

