import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Simulasi cara profiler sampling mengubah "satuan kerja" menjadi tabel
 * porsi beban per method. Semua angka berupa penghitung operasi murni,
 * bukan hasil pengukuran waktu, sehingga output selalu sama.
 */
public class PetaProfil {

    static final int INTERVAL_SAMPLING = 5; // 1 sampel tiap 5 satuan kerja
    static int unitBerjalan = 0;

    static final class Metode {
        final String nama;
        final int unitKerja;
        final List<Metode> anak = new ArrayList<>();
        int sampel = 0;      // porsi eksklusif: satuan di method ini sendiri
        int inklusif = 0;    // porsi inklusif: method ini + seluruh pemanggilan anak

        Metode(String nama, int unitKerja) {
            this.nama = nama;
            this.unitKerja = unitKerja;
        }

        Metode panggil(Metode anak_) {
            anak.add(anak_);
            return this;
        }
    }

    public static void main(String[] args) {
        Metode akar = bangunPanggilan();

        // Eksekusi simulasi: setiap satuan kerja dikerjakan, sampler menyala
        // setiap kelipatan INTERVAL_SAMPLING dan mencatat method terdalam.
        jalankan(akar);
        hitungInklusif(akar);
        int totalUnit = akar.inklusif;

        List<Metode> semua = new ArrayList<>();
        kumpulkan(akar, semua);

        System.out.println("Model eksekusi (satuan kerja, bukan milidetik):");
        cetakPohon(akar, 0);
        System.out.println();
        System.out.println("Total satuan kerja = " + totalUnit
            + ", interval sampling = " + INTERVAL_SAMPLING
            + " -> total sampel = " + (totalUnit / INTERVAL_SAMPLING));
        System.out.println();

        semua.sort(Comparator.comparingInt((Metode m) -> -m.sampel)
            .thenComparing(m -> m.nama));
        System.out.printf("%-20s %8s %8s %12s%n", "method", "sampel", "unit", "porsi");
        for (Metode m : semua) {
            double porsi = 100.0 * m.sampel * INTERVAL_SAMPLING / totalUnit;
            System.out.printf("%-20s %8d %8d %11.1f%%%n",
                m.nama, m.sampel, m.unitKerja, porsi);
        }
        System.out.println();

        semua.sort(Comparator.comparingInt((Metode m) -> -m.inklusif)
            .thenComparing(m -> m.nama));
        System.out.printf("%-20s %10s%n", "method", "inklusif");
        for (Metode m : semua) {
            System.out.printf("%-20s %10d%n", m.nama, m.inklusif);
        }
        System.out.println();

        System.out.println("Sasaran optimasi (porsi eksklusif >= 20%):");
        for (Metode m : semua) {
            if (100.0 * m.sampel * INTERVAL_SAMPLING / totalUnit >= 20) {
                System.out.println("  - " + m.nama + ": periksa algoritma dan sumber daya");
            }
        }
    }

    static Metode bangunPanggilan() {
        return new Metode("main", 2).panggil(
            new Metode("prosesPesanan", 3)
                .panggil(new Metode("validasiPesanan", 4))
                .panggil(new Metode("hitungTotal", 10)
                    .panggil(new Metode("diskonBertingkat", 30)))
                .panggil(new Metode("simpanKeBasisData", 6)
                    .panggil(new Metode("koneksiBaru", 25))));
    }

    static void jalankan(Metode m) {
        for (int u = 0; u < m.unitKerja; u++) {
            unitBerjalan++;
            if (unitBerjalan % INTERVAL_SAMPLING == 0) {
                m.sampel++;
            }
        }
        for (Metode a : m.anak) {
            jalankan(a);
        }
    }

    static int hitungInklusif(Metode m) {
        int total = m.unitKerja;
        for (Metode a : m.anak) {
            total += hitungInklusif(a);
        }
        m.inklusif = total;
        return total;
    }

    static void kumpulkan(Metode m, List<Metode> wadah) {
        wadah.add(m);
        for (Metode a : m.anak) {
            kumpulkan(a, wadah);
        }
    }

    static void cetakPohon(Metode m, int kedalaman) {
        System.out.println("  ".repeat(kedalaman) + m.nama + " (" + m.unitKerja + ")");
        for (Metode a : m.anak) {
            cetakPohon(a, kedalaman + 1);
        }
    }
}
