package org.testcontainers.jdbc;

import com.googlecode.junittoolbox.ParallelParameterized;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.vibur.dbcp.ViburDBCPDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static java.util.Arrays.asList;
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;

/**
 *
 */
@RunWith(ParallelParameterized.class)
public class JDBCDriverWithPoolTest {

    public static final String URL = "jdbc:tc:mysql://hostname/databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverWithPoolTest::sampleInitFunction";
    private final DataSource dataSource;

    @Parameterized.Parameters
    public static Iterable<Supplier<DataSource>> dataSourceSuppliers() {
        return asList(
                JDBCDriverWithPoolTest::getTomcatDataSourceWithDriverClassName,
                JDBCDriverWithPoolTest::getTomcatDataSource,
                JDBCDriverWithPoolTest::getHikariDataSourceWithDriverClassName,
                JDBCDriverWithPoolTest::getHikariDataSource,
                JDBCDriverWithPoolTest::getViburDataSourceWithDriverClassName,
                JDBCDriverWithPoolTest::getViburDataSource
        );
    }

    public JDBCDriverWithPoolTest(Supplier<DataSource> dataSourceSupplier) {
        this.dataSource = dataSourceSupplier.get();
    }

    private ExecutorService executorService = Executors.newFixedThreadPool(5);

    @Test
    public void testMySQLWithConnectionPoolUsingSameContainer() throws SQLException, InterruptedException {

        // Populate the database with some data in multiple threads, so that multiple connections from the pool will be used
        for (int i = 0; i < 100; i++) {
            executorService.submit(() -> {
                try {
                    new QueryRunner(dataSource).insert("INSERT INTO my_counter (n) VALUES (5)",
                            (ResultSetHandler<Object>) rs -> true);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            });
        }

        // Complete population of the database
        executorService.shutdown();
        executorService.awaitTermination(5, TimeUnit.MINUTES);

        // compare to expected results
        int count = new QueryRunner(dataSource).query("SELECT COUNT(1) FROM my_counter", rs -> {
            rs.next();
            return rs.getInt(1);
        });
        assertEquals("Reuse of a datasource points to the same DB container", 100, count);


        int sum = new QueryRunner(dataSource).query("SELECT SUM(n) FROM my_counter", rs -> {
            rs.next();
            return rs.getInt(1);
        });
        // 100 records * 5 = 500 expected
        assertEquals("Reuse of a datasource points to the same DB container", 500, sum);
    }


    private static DataSource getTomcatDataSourceWithDriverClassName() {
        PoolProperties poolProperties = new PoolProperties();
        poolProperties.setUrl(URL + ";TEST=TOMCAT_WITH_CLASSNAME"); // append a dummy URL element to ensure different DB per test
        poolProperties.setValidationQuery("SELECT 1");
        poolProperties.setMinIdle(3);
        poolProperties.setMaxActive(10);
        poolProperties.setDriverClassName(ContainerDatabaseDriver.class.getName());

        return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
    }

    private static DataSource getTomcatDataSource() {
        PoolProperties poolProperties = new PoolProperties();
        poolProperties.setUrl(URL + ";TEST=TOMCAT"); // append a dummy URL element to ensure different DB per test
        poolProperties.setValidationQuery("SELECT 1");
        poolProperties.setInitialSize(3);
        poolProperties.setMaxActive(10);

        return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
    }

    private static HikariDataSource getHikariDataSourceWithDriverClassName() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(URL + ";TEST=HIKARI_WITH_CLASSNAME"); // append a dummy URL element to ensure different DB per test
        hikariConfig.setConnectionTestQuery("SELECT 1");
        hikariConfig.setMinimumIdle(3);
        hikariConfig.setMaximumPoolSize(10);
        hikariConfig.setDriverClassName(ContainerDatabaseDriver.class.getName());

        return new HikariDataSource(hikariConfig);
    }

    private static HikariDataSource getHikariDataSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(URL + ";TEST=HIKARI"); // append a dummy URL element to ensure different DB per test
        hikariConfig.setConnectionTestQuery("SELECT 1");
        hikariConfig.setMinimumIdle(3);
        hikariConfig.setMaximumPoolSize(10);

        return new HikariDataSource(hikariConfig);
    }

    private static DataSource getViburDataSourceWithDriverClassName() {
        ViburDBCPDataSource ds = new ViburDBCPDataSource();

        ds.setJdbcUrl(URL + ";TEST=VIBUR_WITH_CLASSNAME");
        ds.setPoolInitialSize(3);
        ds.setPoolMaxSize(10);
        ds.setTestConnectionQuery("SELECT 1");
        ds.setDriverClassName(ContainerDatabaseDriver.class.getName());

        ds.start();

        return ds;
    }

    private static DataSource getViburDataSource() {
        ViburDBCPDataSource ds = new ViburDBCPDataSource();
        ds.setJdbcUrl(URL + ";TEST=VIBUR");
        ds.setPoolInitialSize(3);
        ds.setPoolMaxSize(10);
        ds.setTestConnectionQuery("SELECT 1");

        ds.start();

        return ds;
    }

    @SuppressWarnings("SqlNoDataSourceInspection")
    public static void sampleInitFunction(Connection connection) throws SQLException {
        connection.createStatement().execute("CREATE TABLE bar (\n" +
                "  foo VARCHAR(255)\n" +
                ");");
        connection.createStatement().execute("INSERT INTO bar (foo) VALUES ('hello world');");
        connection.createStatement().execute("CREATE TABLE my_counter (\n" +
                "  n INT\n" +
                ");");
    }
}

@@ -958,16 +958,18 @@
 c:mysql:
+5:
 //hostna

use MySQL v5 in JDBCDriverWithPoolTest