/*
 Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
 

  The MySQL Connector/J is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.

  This program is free software; you can redistribute it and/or modify it under the terms
  of the GNU General Public License as published by the Free Software Foundation; version 2
  of the License.

  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA



 */
package testsuite.regression;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import testsuite.BaseTestCase;

/**
 * Tests for multi-thread stress regressions.
 * 
 * @author Mark Matthews
 * @version $Id: StressRegressionTest.java,v 1.1.2.1 2005/05/13 18:58:38
 *          mmatthews Exp $
 */
public class StressRegressionTest extends BaseTestCase {
	private int numThreadsStarted;

	/**
	 * Creates a new StressRegressionTest
	 * 
	 * @param name
	 *            the name of the test.
	 */
	public StressRegressionTest(String name) {
		super(name);
	}

	/**
	 * Runs all test cases in this test suite
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		junit.textui.TestRunner.run(StressRegressionTest.class);
	}

	/**
	 * 
	 * 
	 * @throws Exception
	 *             ...
	 */
	public synchronized void testContention() throws Exception {
		if (false) {
			System.out.println("Calculating baseline elapsed time...");

			long start = System.currentTimeMillis();

			contentiousWork(this.conn, this.stmt, 0);

			long singleThreadElapsedTimeMillis = System.currentTimeMillis()
					- start;

			System.out.println("Single threaded execution took "
					+ singleThreadElapsedTimeMillis + " ms.");

			int numThreadsToStart = 95;

			System.out.println("\nStarting " + numThreadsToStart + " threads.");

			this.numThreadsStarted = numThreadsToStart;

			ContentionThread[] threads = new ContentionThread[this.numThreadsStarted];

			for (int i = 0; i < numThreadsToStart; i++) {
				threads[i] = new ContentionThread(i);
				threads[i].start();
			}

			for (;;) {
				try {
					wait();

					if (this.numThreadsStarted == 0) {
						break;
					}
				} catch (InterruptedException ie) {
					// ignore
				}
			}

			// Collect statistics...
			System.out.println("Done!");

			double avgElapsedTimeMillis = 0;

			List<Long> elapsedTimes = new ArrayList<Long>();

			for (int i = 0; i < numThreadsToStart; i++) {
				elapsedTimes.add(new Long(threads[i].elapsedTimeMillis));

				avgElapsedTimeMillis += ((double) threads[i].elapsedTimeMillis / numThreadsToStart);
			}

			Collections.sort(elapsedTimes);

			System.out.println("Average elapsed time per-thread was "
					+ avgElapsedTimeMillis + " ms.");
			System.out.println("Median elapsed time per-thread was "
					+ elapsedTimes.get(elapsedTimes.size() / 2) + " ms.");
			System.out.println("Minimum elapsed time per-thread was "
					+ elapsedTimes.get(0) + " ms.");
			System.out.println("Maximum elapsed time per-thread was "
					+ elapsedTimes.get(elapsedTimes.size() - 1) + " ms.");
		}
	}

	/**
	 * 
	 * 
	 * @throws Exception
	 *             ...
	 */
	public void testCreateConnections() throws Exception {
		new CreateThread().start();
	}

	/**
	 * 
	 * 
	 * @throws Exception
	 *             ...
	 */
	public void testCreateConnectionsUnderLoad() throws Exception {
		new CreateThread(new BusyThread()).start();
	}

	/**
	 * 
	 * @param threadConn
	 * @param threadStmt
	 * @param threadNumber
	 */
	void contentiousWork(Connection threadConn, Statement threadStmt,
			int threadNumber) {
		Date now = new Date();

		try {
			for (int i = 0; i < 1000; i++) {
				ResultSet threadRs = threadStmt.executeQuery("SELECT 1, 2");

				while (threadRs.next()) {
					threadRs.getString(1);
					threadRs.getString(2);
				}

				threadRs.close();

				PreparedStatement pStmt = threadConn
						.prepareStatement("SELECT ?");
				pStmt.setTimestamp(1, new Timestamp(now.getTime()));

				threadRs = pStmt.executeQuery();

				while (threadRs.next()) {
					threadRs.getTimestamp(1);
				}

				threadRs.close();
				pStmt.close();
			}
		} catch (Exception ex) {
			throw new RuntimeException(ex.toString());
		}
	}

	synchronized void reportDone() {
		// TODO: This test should just be refactored to use an executor and futures.
		//this.numThreadsStarted--;
		notify();
	}

	public class BusyThread extends Thread {
		boolean stop = false;

		public void run() {
			while (!this.stop) {
			}
		}
	}

	class ContentionThread extends Thread {
		Connection threadConn;

		Statement threadStmt;

		int threadNumber;

		long elapsedTimeMillis;

		public ContentionThread(int num) throws SQLException {
			this.threadNumber = num;
			this.threadConn = getConnectionWithProps(new Properties());
			this.threadStmt = this.threadConn.createStatement();

			System.out.println(this.threadConn);
		}

		public void run() {
			long start = System.currentTimeMillis();

			try {
				contentiousWork(this.threadConn, this.threadStmt,
						this.threadNumber);
				this.elapsedTimeMillis = System.currentTimeMillis() - start;

				System.out
						.println("Thread " + this.threadNumber + " finished.");
			} finally {
				if (this.elapsedTimeMillis == 0) {
					this.elapsedTimeMillis = System.currentTimeMillis() - start;
				}

				reportDone();

				try {
					this.threadStmt.close();
					this.threadConn.close();
				} catch (SQLException ex) {
					// ignore
				}
			}
		}
	}

	class CreateThread extends Thread {
		BusyThread busyThread;

		int numConnections = 15;

		public CreateThread() {
		}

		public CreateThread(BusyThread toStop) {
			this.busyThread = toStop;
		}

		public CreateThread(int numConns) {
			this.numConnections = numConns;
		}

		public void run() {
			try {
				Connection[] connList = new Connection[this.numConnections];

				long maxConnTime = Long.MIN_VALUE;
				long minConnTime = Long.MAX_VALUE;
				double averageTime = 0;

				Properties nullProps = new Properties();

				for (int i = 0; i < this.numConnections; i++) {
					long startTime = System.currentTimeMillis();
					connList[i] = getConnectionWithProps(nullProps);

					long endTime = System.currentTimeMillis();
					long ellapsedTime = endTime - startTime;

					if (ellapsedTime < minConnTime) {
						minConnTime = ellapsedTime;
					}

					if (ellapsedTime > maxConnTime) {
						maxConnTime = ellapsedTime;
					}

					averageTime += ((double) ellapsedTime / this.numConnections);
				}

				if (this.busyThread != null) {
					this.busyThread.stop = true;
				}

				for (int i = 0; i < this.numConnections; i++) {
					connList[i].close();
				}

				System.out.println(minConnTime + "/" + maxConnTime + "/"
						+ averageTime);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}
	}
}
