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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EksekusiSql {

    public static void main(String[] args) throws SQLException {
        DriverManager.registerDriver(new DemoDriver());

        try (Connection conn = DriverManager.getConnection("jdbc:demo:toko");
             Statement stmt = conn.createStatement()) {

            tampilKueri(stmt, "SELECT nama, kota FROM pelanggan");

            int n1 = stmt.executeUpdate("INSERT INTO pelanggan VALUES ('Ranto', 'Medan')");
            System.out.println("executeUpdate(INSERT) -> " + n1);

            int n2 = stmt.executeUpdate(
                    "UPDATE pelanggan SET kota = 'Depok' WHERE nama = 'Budi'");
            System.out.println("executeUpdate(UPDATE) -> " + n2);

            int n3 = stmt.executeUpdate("DELETE FROM pelanggan WHERE kota = 'Bandung'");
            System.out.println("executeUpdate(DELETE) -> " + n3);

            boolean kueri = stmt.execute("SELECT nama, kota FROM pelanggan");
            System.out.println("execute(SELECT)      -> " + kueri);
            try (ResultSet rs = stmt.getResultSet()) {
                while (rs.next()) {
                    System.out.println("  - " + rs.getString("nama")
                            + " (" + rs.getString("kota") + ")");
                }
            }

            boolean ubah = stmt.execute("INSERT INTO pelanggan VALUES ('Eka', 'Solo')");
            System.out.println("execute(INSERT)      -> " + ubah
                    + ", getUpdateCount() = " + stmt.getUpdateCount());

            tampilKueri(stmt, "SELECT nama, kota FROM pelanggan WHERE kota = 'Depok'");
        }
    }

    static void tampilKueri(Statement stmt, String sql) throws SQLException {
        System.out.println("kueri  : " + sql);
        try (ResultSet rs = stmt.executeQuery(sql)) {
            while (rs.next()) {
                System.out.println("  - " + rs.getString("nama")
                        + " (" + rs.getString("kota") + ")");
            }
        }
    }
}

class DemoDriver implements Driver {

    @Override
    public Connection connect(String url, Properties info) {
        if (url == null || !url.startsWith("jdbc:demo:")) {
            return null;
        }
        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 {

    /** Tabel pelanggan "di database" (in-memory). */
    static final List<String[]> TABEL = new ArrayList<>(List.of(
            new String[]{"Budi", "Jakarta"},
            new String[]{"Siti", "Bandung"},
            new String[]{"Dewi", "Surabaya"}));

    static Connection connection() {
        return (Connection) Proxy.newProxyInstance(
                Connection.class.getClassLoader(),
                new Class<?>[]{Connection.class},
                (proxy, method, argumen) -> {
                    if (method.getName().equals("createStatement")) {
                        return statement();
                    }
                    if (method.getName().equals("toString")) {
                        return "ShimConnection";
                    }
                    Class<?> t = method.getReturnType();
                    if (t == boolean.class) return false;
                    if (t == int.class) return 0;
                    return null;
                });
    }

    /** Statement shim dengan state hasil terakhir, seperti Statement sungguhan. */
    @SuppressWarnings("unchecked")
    static Statement statement() {
        List<String[]>[] hasilKini = new List[1];   // hasil SELECT terakhir
        int[] jumlahKini = {-1};                    // jumlah baris UPDATE/INSERT/DELETE
        return (Statement) Proxy.newProxyInstance(
                Statement.class.getClassLoader(),
                new Class<?>[]{Statement.class},
                (proxy, method, argumen) -> {
                    switch (method.getName()) {
                        case "executeQuery" -> {
                            return resultSet(MiniSql.query((String) argumen[0]));
                        }
                        case "executeUpdate" -> {
                            return MiniSql.update((String) argumen[0]);
                        }
                        case "execute" -> {
                            String sql = (String) argumen[0];
                            if (sql.startsWith("SELECT")) {
                                hasilKini[0] = MiniSql.query(sql);
                                jumlahKini[0] = -1;
                                return true;
                            }
                            hasilKini[0] = null;
                            jumlahKini[0] = MiniSql.update(sql);
                            return false;
                        }
                        case "getResultSet" -> {
                            return (hasilKini[0] == null)
                                    ? null
                                    : resultSet(hasilKini[0]);
                        }
                        case "getUpdateCount" -> {
                            return jumlahKini[0];
                        }
                        default -> {
                            if (method.getName().equals("toString")) {
                                return "ShimStatement";
                            }
                            Class<?> t = method.getReturnType();
                            if (t == boolean.class) return false;
                            if (t == int.class) return 0;
                            return null;
                        }
                    }
                });
    }

    /** ResultSet shim: kursor digerakkan di atas salinan hasil kueri. */
    static ResultSet resultSet(List<String[]> baris) {
        int[] kursor = {-1};
        return (ResultSet) Proxy.newProxyInstance(
                ResultSet.class.getClassLoader(),
                new Class<?>[]{ResultSet.class},
                (proxy, method, argumen) -> {
                    switch (method.getName()) {
                        case "next" -> {
                            kursor[0]++;
                            return kursor[0] < baris.size();
                        }
                        case "getString" -> {
                            int idx = (argumen[0] instanceof Integer angka)
                                    ? angka - 1
                                    : ("kota".equals(argumen[0]) ? 1 : 0);
                            return baris.get(kursor[0])[idx];
                        }
                        default -> {
                            Class<?> t = method.getReturnType();
                            if (t == boolean.class) return false;
                            if (t == int.class) return 0;
                            return null;
                        }
                    }
                });
    }
}

/** Parser SQL mini untuk empat bentuk pernyataan yang dipakai demo. */
class MiniSql {

    static int update(String sql) {
        Matcher ins = Pattern.compile(
                "INSERT INTO pelanggan VALUES \\('([^']+)', '([^']+)'\\)").matcher(sql);
        if (ins.matches()) {
            ShimFactory.TABEL.add(new String[]{ins.group(1), ins.group(2)});
            return 1;
        }
        Matcher upd = Pattern.compile(
                "UPDATE pelanggan SET kota = '([^']+)' WHERE nama = '([^']+)'").matcher(sql);
        if (upd.matches()) {
            int jumlah = 0;
            for (String[] baris : ShimFactory.TABEL) {
                if (baris[0].equals(upd.group(2))) {
                    baris[1] = upd.group(1);
                    jumlah++;
                }
            }
            return jumlah;
        }
        Matcher del = Pattern.compile(
                "DELETE FROM pelanggan WHERE kota = '([^']+)'").matcher(sql);
        if (del.matches()) {
            int jumlah = 0;
            for (Iterator<String[]> it = ShimFactory.TABEL.iterator(); it.hasNext(); ) {
                if (it.next()[1].equals(del.group(1))) {
                    it.remove();
                    jumlah++;
                }
            }
            return jumlah;
        }
        throw new IllegalArgumentException("SQL tidak dikenali: " + sql);
    }

    static List<String[]> query(String sql) {
        Matcher m = Pattern.compile(
                "SELECT nama, kota FROM pelanggan(?: WHERE kota = '([^']+)')?")
                .matcher(sql);
        if (!m.matches()) {
            throw new IllegalArgumentException("SQL tidak dikenali: " + sql);
        }
        List<String[]> hasil = new ArrayList<>();
        for (String[] baris : ShimFactory.TABEL) {
            if (m.group(1) == null || baris[1].equals(m.group(1))) {
                hasil.add(baris);
            }
        }
        return hasil;
    }
}
