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

public class WadahDependensi {

    interface RepositoriProduk {
        String namaSemua();
    }

    static class RepositoriMemori implements RepositoriProduk {
        @Override
        public String namaSemua() {
            return "buku, pena, tas";
        }
    }

    static class RepositoriCadangan implements RepositoriProduk {
        @Override
        public String namaSemua() {
            return "(cadangan) buku, pena, tas";
        }
    }

    static class LayananProduk {
        private final RepositoriProduk repositori;

        LayananProduk(RepositoriProduk repositori) {
            this.repositori = repositori;
        }

        String ringkas() {
            return "produk: " + repositori.namaSemua();
        }
    }

    static class KontrolerProduk {
        private final LayananProduk layanan;

        KontrolerProduk(LayananProduk layanan) {
            this.layanan = layanan;
        }

        String tampilkan() {
            return "200 OK: " + layanan.ringkas();
        }
    }

    static class Wadah {
        private final Map<Class<?>, Object> instance = new HashMap<>();
        private final Map<Class<?>, Class<?>> ikatan = new HashMap<>();

        <T> void ikat(Class<T> kontrak, Class<? extends T> implementasi) {
            ikatan.put(kontrak, implementasi);
        }

        @SuppressWarnings("unchecked")
        <T> T ambil(Class<T> tipe) throws Exception {
            if (instance.containsKey(tipe)) {
                return (T) instance.get(tipe);
            }
            Class<?> implementasi = ikatan.getOrDefault(tipe, tipe);
            Constructor<?> konstruktor = implementasi.getDeclaredConstructors()[0];
            Class<?>[] tipeParam = konstruktor.getParameterTypes();
            Object[] argumen = new Object[tipeParam.length];
            for (int i = 0; i < tipeParam.length; i++) {
                argumen[i] = ambil(tipeParam[i]);
            }
            Object objek = konstruktor.newInstance(argumen);
            instance.put(tipe, objek);
            return (T) objek;
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("== Instansiasi manual tanpa wadah ==");
        KontrolerProduk manual = new KontrolerProduk(
                new LayananProduk(new RepositoriMemori()));
        System.out.println(manual.tampilkan());

        System.out.println();
        System.out.println("== Wadah produksi: wadah menyusun graf dependensi ==");
        Wadah produksi = new Wadah();
        produksi.ikat(RepositoriProduk.class, RepositoriMemori.class);
        KontrolerProduk kontroler = produksi.ambil(KontrolerProduk.class);
        System.out.println(kontroler.tampilkan());

        System.out.println();
        System.out.println("== Wadah pengujian: cukup ganti satu ikatan ==");
        Wadah uji = new Wadah();
        uji.ikat(RepositoriProduk.class, RepositoriCadangan.class);
        KontrolerProduk kontrolerUji = uji.ambil(KontrolerProduk.class);
        System.out.println(kontrolerUji.tampilkan());

        System.out.println();
        System.out.println("== Wadah menyimpan instance tunggal (scope) ==");
        KontrolerProduk sama = produksi.ambil(KontrolerProduk.class);
        System.out.println("ambil() kedua mengembalikan objek sama: " + (kontroler == sama));
    }
}
