/** Circuit breaker: CLOSED -> OPEN -> HALF_OPEN, digerakkan kejadian eksplisit tanpa waktu nyata. */
public class PemutusSirkit {

    enum Status { CLOSED, OPEN, HALF_OPEN }

    /** State machine circuit breaker dengan ambang kegagalan dan masa tenang berbentuk tick. */
    static class Pemutus {
        private final int ambangKegagalan;
        private final int masaTenangTick;
        private Status status = Status.CLOSED;
        private int kegagalanBeruntun = 0;
        private int tickTersisa = 0;

        Pemutus(int ambangKegagalan, int masaTenangTick) {
            this.ambangKegagalan = ambangKegagalan;
            this.masaTenangTick = masaTenangTick;
        }

        Status status() {
            return status;
        }

        /** Dipanggil sebelum menembak dependensi: apakah diizinkan lewat? */
        boolean bolehPanggil() {
            return status != Status.OPEN;
        }

        void sukses() {
            kegagalanBeruntun = 0;
            if (status == Status.HALF_OPEN) {
                status = Status.CLOSED;
                System.out.println("  sukses : percobaan lolos -> HALF_OPEN menuju CLOSED");
            } else {
                System.out.println("  sukses : kegagalan beruntun direset (tetap CLOSED)");
            }
        }

        void gagal() {
            kegagalanBeruntun++;
            if (status == Status.HALF_OPEN) {
                buka("percobaan gagal");
                return;
            }
            if (kegagalanBeruntun >= ambangKegagalan) {
                buka("kegagalan ke-" + kegagalanBeruntun + " mencapai ambang " + ambangKegagalan);
            } else {
                System.out.println("  gagal  : kegagalan ke-" + kegagalanBeruntun
                        + ", sirkit masih CLOSED");
            }
        }

        /** Satu satuan waktu berlalu; habiskan masa tenang bila sirkit sedang terbuka. */
        void tick() {
            if (status == Status.OPEN) {
                tickTersisa--;
                if (tickTersisa <= 0) {
                    status = Status.HALF_OPEN;
                    System.out.println("  tick   : masa tenang habis -> OPEN menuju HALF_OPEN");
                }
            }
        }

        private void buka(String alasan) {
            status = Status.OPEN;
            tickTersisa = masaTenangTick;
            System.out.println("  gagal  : " + alasan + " -> sirkit OPEN, tolak semua panggilan");
        }
    }

    public static void main(String[] args) {
        Pemutus sirkit = new Pemutus(3, 2); // ambang 3 kegagalan, masa tenang 2 tick

        System.out.println("== Skenario A: kegagalan beruntun membuka sirkit ==");
        for (int i = 1; i <= 3; i++) {
            System.out.println("  panggilan " + i + " diizinkan? " + sirkit.bolehPanggil());
            sirkit.gagal();
        }

        System.out.println();
        System.out.println("== Skenario B: selama OPEN semua panggilan ditolak ==");
        System.out.println("  panggilan baru diizinkan? " + sirkit.bolehPanggil()
                + " -> langsung fallback");
        sirkit.tick();
        System.out.println("  panggilan baru diizinkan? " + sirkit.bolehPanggil()
                + " (masih OPEN, tick ke-1)");
        sirkit.tick(); // masa tenang 2 tick habis

        System.out.println();
        System.out.println("== Skenario C: satu percobaan di HALF_OPEN ==");
        System.out.println("  panggilan percobaan diizinkan? " + sirkit.bolehPanggil());
        sirkit.sukses();

        System.out.println();
        System.out.println("== Skenario D: percobaan gagal membuka ulang sirkit ==");
        for (int i = 1; i <= 3; i++) {
            sirkit.gagal();
        }
        sirkit.tick();
        sirkit.tick(); // kembali HALF_OPEN
        System.out.println("  panggilan percobaan diizinkan? " + sirkit.bolehPanggil());
        sirkit.gagal(); // percobaan gagal -> OPEN lagi
        System.out.println("  status akhir: " + sirkit.status());
    }
}
