/**
 * Copyright 2013 Simeon Malchev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.vibur.dbcp.perf;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vibur.dbcp.ViburDBCPDataSource;
import org.vibur.dbcp.ViburDBCPException;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Simeon Malchev
 */
public class ViburDBCPGetConnectionTestPerf {

    private static final Logger logger = LoggerFactory.getLogger(ViburDBCPGetConnectionTestPerf.class);

    // pool metrics:
    private static final int INITIAL_SIZE = 50;
    private static final int MAX_SIZE = 200;
    private static final long TIMEOUT_MS = 2000;
    private static final boolean FAIR = true;

    // threads metrics:
    private static final int ITERATIONS = 100;
    private static final int THREADS_COUNT = 500;
    private static final long DO_WORK_FOR_MS = 2;

    public static void main(String[] args) throws InterruptedException, ViburDBCPException {

        // Creates a DataSource with an INITIAL_SIZE and a MAX_SIZE, and starts a THREADS_COUNT threads
        // where each thread executes ITERATIONS times the following code:
        //
        //     Connection connection = ds.getConnection();
        //     doWork(DO_WORK_FOR_MS);
        //     connection.close();
        //
        // Each getConnection() call has a TIMEOUT_MS and the number of unsuccessful calls is recorded.
        // Measures and reports the total time taken by the test in ms.

        ViburDBCPDataSource ds = createDataSource();
        ds.start();

        AtomicInteger errors = new AtomicInteger(0);

        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch readySignal = new CountDownLatch(THREADS_COUNT);
        CountDownLatch doneSignal = new CountDownLatch(THREADS_COUNT);

        for (int i = 0; i < THREADS_COUNT; i++) {
            Thread thread = new Thread(new Worker(ds, errors, DO_WORK_FOR_MS, readySignal, startSignal, doneSignal));
            thread.start();
        }

        readySignal.await();
        long startNanoTime = System.nanoTime();
        startSignal.countDown();
        doneSignal.await();

        System.out.println(String.format("Total execution time %f ms, unsuccessful takes %d.",
            (System.nanoTime() - startNanoTime) * 0.000_001, errors.get()));

        ds.close();
    }

    private static class Worker implements Runnable {
        private final ViburDBCPDataSource ds;
        private final AtomicInteger errors;
        private final long millis;

        private final CountDownLatch readySignal;
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        private Worker(ViburDBCPDataSource ds, AtomicInteger errors, long millis,
                       CountDownLatch readySignal, CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.ds = ds;
            this.errors = errors;
            this.millis = millis;
            this.startSignal = startSignal;
            this.readySignal = readySignal;
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            try {
                readySignal.countDown();
                startSignal.await();

                for (int i = 0; i < ITERATIONS; i++) {
                    try {
                        Connection connection = ds.getConnection();
                        doWork(millis);
                        connection.close();
                    } catch (SQLException e) {
                        logger.error(e.toString());
                        errors.incrementAndGet();
                    }
                }
            } catch (InterruptedException ignored) {
                errors.incrementAndGet();
            } finally {
                doneSignal.countDown();
            }
        }
    }

    private static ViburDBCPDataSource createDataSource() {
        ViburDBCPDataSource ds = new ViburDBCPDataSource();
        ds.setJdbcUrl("jdbc:hsqldb:mem:sakila;shutdown=false");
        ds.setUsername("sa");
        ds.setPassword("");

        ds.setPoolInitialSize(INITIAL_SIZE);
        ds.setPoolMaxSize(MAX_SIZE);
        ds.setConnectionTimeoutInMs(TIMEOUT_MS);
        ds.setPoolFair(FAIR);
        return ds;
    }

    private static void doWork(long millis) {
        if (millis <= 0) {
            return;
        }

        try {
            Thread.sleep(millis);
        } catch (InterruptedException ignored) { }
    }
}