import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class SiklusBean {

    public static void main(String[] args) throws Exception {
        ContainerMini wadah = new ContainerMini();

        System.out.println("-- pendaftaran bean --");
        wadah.daftarSingleton("penyimpanSesi", PenyimpanSesi.class);
        wadah.daftarPrototype("pembuatId", PembuatId.class);

        System.out.println();
        System.out.println("-- pemakaian bean --");
        PenyimpanSesi sesi = wadah.ambil("penyimpanSesi");
        sesi.simpan("Budi", "token-abc");
        System.out.println("Sesi Budi: " + sesi.ambil("Budi"));

        System.out.println();
        System.out.println("-- lingkup singleton vs prototype --");
        PenyimpanSesi sesiLagi = wadah.ambil("penyimpanSesi");
        PembuatId idA = wadah.ambil("pembuatId");
        PembuatId idB = wadah.ambil("pembuatId");
        System.out.println("singleton identik : " + (sesi == sesiLagi));
        System.out.println("prototype identik : " + (idA == idB));

        System.out.println();
        System.out.println("-- container ditutup: hanya singleton yang dihancurkan --");
        wadah.tutup();
    }
}

// Marker penanda tahap siklus, mirror dari anotasi Spring.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface PascaBuat { }

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface PraHapus { }

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Nilai {
    String value();
}

class ContainerMini {

    private final Map<String, Object> singleton = new LinkedHashMap<>();
    private final Map<String, Class<?>> prototype = new LinkedHashMap<>();

    void daftarSingleton(String nama, Class<?> tipe) throws Exception {
        System.out.println("[Siklus] " + nama + ": instansiasi");
        Object instans = cipta(tipe);
        isiNilai(instans);
        panggilAnotasi(instans, PascaBuat.class);
        singleton.put(nama, instans);
    }

    void daftarPrototype(String nama, Class<?> tipe) {
        prototype.put(nama, tipe);
    }

    @SuppressWarnings("unchecked")
    <T> T ambil(String nama) throws Exception {
        if (singleton.containsKey(nama)) {
            return (T) singleton.get(nama);
        }
        Class<?> tipe = prototype.get(nama);
        if (tipe == null) {
            throw new IllegalStateException("bean tak dikenal: " + nama);
        }
        Object instans = cipta(tipe);
        isiNilai(instans);
        panggilAnotasi(instans, PascaBuat.class);
        return (T) instans;
    }

    private Object cipta(Class<?> tipe) throws Exception {
        return tipe.getDeclaredConstructor().newInstance();
    }

    // Tahap populate: container mengisi field bertanda @Nilai.
    private void isiNilai(Object instans) throws Exception {
        for (Field f : instans.getClass().getDeclaredFields()) {
            Nilai tanda = f.getAnnotation(Nilai.class);
            if (tanda == null) {
                continue;
            }
            f.setAccessible(true);
            if (f.getType() == int.class) {
                f.setInt(instans, Integer.parseInt(tanda.value()));
            } else {
                f.set(instans, tanda.value());
            }
            System.out.println("[Siklus] populate " + f.getName() + " = " + tanda.value());
        }
    }

    private void panggilAnotasi(Object instans, Class<? extends java.lang.annotation.Annotation> anotasi)
            throws Exception {
        for (Method m : instans.getClass().getDeclaredMethods()) {
            if (m.isAnnotationPresent(anotasi)) {
                m.setAccessible(true);
                m.invoke(instans);
            }
        }
    }

    void tutup() {
        List<String> urutan = new ArrayList<>(singleton.keySet());
        for (int i = urutan.size() - 1; i >= 0; i--) {
            String nama = urutan.get(i);
            Object instans = singleton.get(nama);
            System.out.println("[Siklus] " + nama + ": tahap hancur");
            try {
                panggilAnotasi(instans, PraHapus.class);
            } catch (Exception e) {
                throw new IllegalStateException("Gagal menghancurkan " + nama);
            }
        }
        System.out.println("[Siklus] container ditutup");
    }
}

class PenyimpanSesi {

    @Nilai("64")
    private int kapasitas;

    private final Map<String, String> sesi = new LinkedHashMap<>();

    @PascaBuat
    void mulai() {
        System.out.println("[Siklus] @PostConstruct PenyimpanSesi siap (kapasitas=" + kapasitas + ")");
    }

    @PraHapus
    void berhenti() {
        System.out.println("[Siklus] @PreDestroy membersihkan " + sesi.size() + " sesi");
        sesi.clear();
    }

    void simpan(String kunci, String nilai) {
        sesi.put(kunci, nilai);
    }

    String ambil(String kunci) {
        return sesi.get(kunci);
    }
}

class PembuatId {

    private static int pembuatke = 0;

    @PascaBuat
    void mulai() {
        pembuatke++;
        System.out.println("[Siklus] @PostConstruct instance PembuatId ke-" + pembuatke);
    }

    long baru() {
        return System.nanoTime(); // tidak dicetak: nilai tidak deterministik
    }
}
