import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class WadahIoC {

    public static void main(String[] args) {
        WadahIoC wadah = new WadahIoC();

        // Cukup minta bean paling atas; dependensinya diselesaikan otomatis.
        NotifikasiService layanan = wadah.ambil(NotifikasiService.class);
        layanan.pesananBaru("Budi", "Kopi Gayo 250g");

        System.out.println();
        System.out.println("Ambil lagi: bean yang sama dikembalikan (singleton)");
        NotifikasiService layananLagi = wadah.ambil(NotifikasiService.class);
        System.out.println("Instans identik: " + (layanan == layananLagi));
    }

    private final Map<Class<?>, Object> bean = new HashMap<>();

    @SuppressWarnings("unchecked")
    <T> T ambil(Class<T> tipe) {
        if (bean.containsKey(tipe)) {
            return (T) bean.get(tipe);
        }
        T instans = (T) cipta(tipe);
        bean.put(tipe, instans);
        System.out.println("[Wadah] bean siap: " + tipe.getSimpleName());
        return instans;
    }

    private Object cipta(Class<?> tipe) {
        Constructor<?> konstruktor = tipe.getDeclaredConstructors()[0];
        Class<?>[] tipeParam = konstruktor.getParameterTypes();
        Object[] argumen = new Object[tipeParam.length];
        for (int i = 0; i < tipeParam.length; i++) {
            System.out.println("[Wadah] " + tipe.getSimpleName() + " butuh "
                    + tipeParam[i].getSimpleName() + " -> selesaikan lebih dulu");
            argumen[i] = ambil(tipeParam[i]);
        }
        try {
            return konstruktor.newInstance(argumen);
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException("Gagal menciptakan " + tipe.getSimpleName());
        }
    }
}

class KatalogProduk {

    private final Map<String, Long> harga = new HashMap<>();

    KatalogProduk() {
        harga.put("Kopi Gayo 250g", 85_000L);
        System.out.println("[KatalogProduk] konstruktor berjalan");
    }

    long harga(String nama) {
        return harga.getOrDefault(nama, 0L);
    }
}

class PengirimLog {

    PengirimLog() {
        System.out.println("[PengirimLog] konstruktor berjalan");
    }

    void tulis(String pesan) {
        System.out.println("[LOG] " + pesan);
    }
}

class NotifikasiService {

    private final KatalogProduk katalog;
    private final PengirimLog log;

    NotifikasiService(KatalogProduk katalog, PengirimLog log) {
        this.katalog = katalog;
        this.log = log;
    }

    void pesananBaru(String pembeli, String produk) {
        log.tulis("Pesanan " + pembeli + ": " + produk
                + " Rp" + katalog.harga(produk));
    }
}
