package example.gc;

import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.List;

public final class CollectorLogProbe {
    private static final int KIB = 1024;

    private CollectorLogProbe() {
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            throw new IllegalArgumentException("usage: CollectorLogProbe <churn|explicit|g1-cycle|humongous-small|humongous-large|retained-pressure|bounded-retention>");
        }

        switch (args[0]) {
            case "churn" -> churn();
            case "explicit" -> explicitCollection();
            case "g1-cycle" -> g1Cycle();
            case "humongous-small" -> humongous(400 * KIB, "small");
            case "humongous-large" -> humongous(600 * KIB, "large");
            case "retained-pressure" -> retention(false);
            case "bounded-retention" -> retention(true);
            default -> throw new IllegalArgumentException("unknown mode: " + args[0]);
        }
    }

    private static void churn() {
        List<byte[]> survivors = new ArrayList<>();
        long checksum = 0;
        for (int round = 0; round < 40; round++) {
            for (int index = 0; index < 48; index++) {
                byte[] block = new byte[128 * KIB];
                block[0] = (byte) (round + index);
                checksum += block[0] & 0xff;
                if ((round + index) % 61 == 0) {
                    survivors.add(block);
                }
            }
        }
        checksum += firstByteChecksum(survivors);
        System.out.println("mode=churn");
        System.out.println("survivors=" + survivors.size());
        System.out.println("checksum=" + checksum);
        Reference.reachabilityFence(survivors);
    }

    private static void explicitCollection() {
        List<byte[]> live = new ArrayList<>();
        long checksum = 0;
        for (int index = 0; index < 16; index++) {
            byte[] block = new byte[256 * KIB];
            block[0] = (byte) (index + 1);
            live.add(block);
            checksum += block[0] & 0xff;
        }
        for (int index = 0; index < 384; index++) {
            byte[] dead = new byte[128 * KIB];
            dead[0] = (byte) index;
            checksum += dead[0] & 0xff;
        }
        System.gc();
        checksum += firstByteChecksum(live);
        System.out.println("mode=explicit");
        System.out.println("live-blocks=" + live.size());
        System.out.println("checksum=" + checksum);
        Reference.reachabilityFence(live);
    }

    private static void g1Cycle() {
        List<byte[]> keep = allocateBlocks(448, 32 * KIB, 11);
        List<byte[]> release = allocateBlocks(448, 32 * KIB, 37);

        // A full collection first places both cohorts in old regions. The released
        // cohort then gives the following concurrent mark reclaimable old regions.
        System.gc();
        long checksum = firstByteChecksum(keep) + firstByteChecksum(release);
        release = null;

        for (int index = 0; index < 8_000; index++) {
            byte[] churn = new byte[64 * KIB];
            churn[0] = (byte) index;
            checksum += churn[0] & 0xff;
            if ((index & 1023) == 0) {
                Thread.yield();
            }
        }

        checksum += firstByteChecksum(keep);
        System.out.println("mode=g1-cycle");
        System.out.println("retained-blocks=" + keep.size());
        System.out.println("checksum=" + checksum);
        Reference.reachabilityFence(keep);
    }

    private static List<byte[]> allocateBlocks(int count, int size, int seed) {
        List<byte[]> blocks = new ArrayList<>(count);
        for (int index = 0; index < count; index++) {
            byte[] block = new byte[size];
            block[0] = (byte) (seed + index);
            blocks.add(block);
        }
        return blocks;
    }

    private static void humongous(int size, String kind) {
        List<byte[]> live = allocateBlocks(4, size, 23);
        System.gc();
        System.out.println("mode=humongous-" + kind);
        System.out.println("array-payload-bytes=" + size);
        System.out.println("live-arrays=" + live.size());
        System.out.println("checksum=" + firstByteChecksum(live));
        Reference.reachabilityFence(live);
    }

    private static void retention(boolean bounded) {
        final int requestedBlocks = 96;
        final int blockBytes = 512 * KIB;
        final int retainedWindow = 8;
        List<byte[]> retained = new ArrayList<>();
        long checksum = 0;

        System.out.println("mode=" + (bounded ? "bounded-retention" : "retained-pressure"));
        System.out.println("requested-blocks=" + requestedBlocks);
        System.out.println("block-bytes=" + blockBytes);
        for (int index = 0; index < requestedBlocks; index++) {
            byte[] block = new byte[blockBytes];
            block[0] = (byte) (index + 7);
            checksum += block[0] & 0xff;
            retained.add(block);
            if (bounded && retained.size() > retainedWindow) {
                retained.remove(0);
            }
        }

        if (!bounded) {
            throw new AssertionError("retained-pressure unexpectedly completed");
        }
        System.out.println("retained-blocks=" + retained.size());
        System.out.println("checksum=" + checksum);
        Reference.reachabilityFence(retained);
    }

    private static long firstByteChecksum(List<byte[]> blocks) {
        long checksum = 0;
        for (byte[] block : blocks) {
            checksum += block[0] & 0xff;
        }
        return checksum;
    }
}
