import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

public class ShimJdbc {

    public static void main(String[] args) throws SQLException {
        // Langkah 1: driver buatan sendiri didaftarkan ke DriverManager
        DriverManager.registerDriver(new DemoDriver());

        // Langkah 2: DriverManager memilih driver berdasarkan URL JDBC
        Driver dipilih = DriverManager.getDriver("jdbc:demo:toko");
        System.out.println("driver terpilih : " + dipilih.getClass().getSimpleName());
        System.out.println("versi driver    : " + dipilih.getMajorVersion()
                + "." + dipilih.getMinorVersion());

        // Langkah 3: alur lengkap getConnection -> createStatement -> executeQuery
        try (Connection conn = DriverManager.getConnection("jdbc:demo:toko");
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT nama, kota FROM pelanggan")) {
            while (rs.next()) {
                System.out.println(rs.getString("nama") + " | " + rs.getString("kota"));
            }
        }

        // Langkah 4: URL yang tidak dikenal driver mana pun ditolak
        try {
            DriverManager.getConnection("jdbc:lain:xxx");
        } catch (SQLException e) {
            System.out.println("ditolak: " + e.getMessage());
        }
    }
}

class DemoDriver implements Driver {

    @Override
    public Connection connect(String url, Properties info) {
        if (url == null || !url.startsWith("jdbc:demo:")) {
            return null; // bukan milik driver ini: DriverManager mencoba driver lain
        }
        System.out.println("[shim] connect(" + url + ") -> objek Connection demo dibuat");
        return ShimFactory.connection();
    }

    @Override
    public boolean acceptsURL(String url) {
        return url != null && url.startsWith("jdbc:demo:");
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
        return new DriverPropertyInfo[0];
    }

    @Override
    public int getMajorVersion() {
        return 1;
    }

    @Override
    public int getMinorVersion() {
        return 0;
    }

    @Override
    public boolean jdbcCompliant() {
        return false;
    }

    @Override
    public java.util.logging.Logger getParentLogger() {
        return null;
    }
}

class ShimFactory {

    static final List<String[]> BARIS = List.of(
            new String[]{"Budi", "Jakarta"},
            new String[]{"Siti", "Bandung"},
            new String[]{"Dewi", "Surabaya"});
    static final List<String> KOLOM = List.of("nama", "kota");

    static Connection connection() {
        return (Connection) Proxy.newProxyInstance(
                Connection.class.getClassLoader(),
                new Class<?>[]{Connection.class},
                (proxy, method, args) -> {
                    if (method.getName().equals("createStatement")) {
                        System.out.println("[shim] Connection.createStatement() dipanggil");
                        return statement();
                    }
                    return umum("Connection", proxy, method);
                });
    }

    static Statement statement() {
        return (Statement) Proxy.newProxyInstance(
                Statement.class.getClassLoader(),
                new Class<?>[]{Statement.class},
                (proxy, method, args) -> {
                    if (method.getName().equals("executeQuery")) {
                        System.out.println("[shim] Statement.executeQuery(\""
                                + args[0] + "\") dipanggil");
                        return resultSet();
                    }
                    return umum("Statement", proxy, method);
                });
    }

    static ResultSet resultSet() {
        int[] kursor = {-1}; // kursor mulai SEBELUM baris pertama
        return (ResultSet) Proxy.newProxyInstance(
                ResultSet.class.getClassLoader(),
                new Class<?>[]{ResultSet.class},
                (proxy, method, args) -> {
                    switch (method.getName()) {
                        case "next" -> {
                            kursor[0]++;
                            return kursor[0] < BARIS.size();
                        }
                        case "getString" -> {
                            int idx = (args[0] instanceof Integer angka)
                                    ? angka - 1
                                    : KOLOM.indexOf(args[0]);
                            return BARIS.get(kursor[0])[idx];
                        }
                        case "close" -> {
                            System.out.println("[shim] ResultSet.close()");
                            return null;
                        }
                        default -> {
                            return umum("ResultSet", proxy, method);
                        }
                    }
                });
    }

    /** Penanganan method umum: close, toString, dan method yang tidak kita pakai. */
    static Object umum(String label, Object proxy, Method method) {
        if (method.getName().equals("close")) {
            System.out.println("[shim] " + label + ".close()");
            return null;
        }
        if (method.getName().equals("toString")) {
            return "Shim" + label;
        }
        Class<?> t = method.getReturnType();
        if (t == boolean.class) {
            return false;
        }
        if (t == int.class) {
            return 0;
        }
        if (t == long.class) {
            return 0L;
        }
        return null;
    }
}
