package edu.cmu.cs.vbc.prog.prevayler.demos.scalability;

import edu.cmu.cs.vbc.prog.prevayler.demos.scalability.jdbc.JDBCQuerySubject;
import edu.cmu.cs.vbc.prog.prevayler.demos.scalability.jdbc.JDBCTransactionSubject;
import edu.cmu.cs.vbc.prog.prevayler.demos.scalability.prevayler.PrevaylerQuerySubject;
import edu.cmu.cs.vbc.prog.prevayler.demos.scalability.prevayler.PrevaylerTransactionSubject;
import org.prevayler.foundation.serialization.JavaSerializer;

import java.io.*;
import java.util.Properties;

public class Main {

	static private final Properties properties = new Properties();


	static public void main(String[] args) {

		out("\n=============================================================");
		out(  "             Prevayler vs JDBC Scalability Tests             ");
		out(  "=============================================================\n");
		out("If you have any trouble running the tests, just write to");
		out("prevayler-scalability@lists.sourceforge.net and we will be glad to help.\n");

		try {
			out("Reading the properties file:\n" + propertiesFile().getAbsolutePath());
			out("You can edit this file to configure the tests for the next run.\n");

			properties.load(new FileInputStream(propertiesFile()));

			if (isPrevaylerQueryChosen()) runPrevaylerQuery();
			if (isPrevaylerTransactionChosen()) runPrevaylerTransaction();
			if (isJdbcQueryChosen()) runJdbcQuery();
			if (isJdbcTransactionChosen()) runJdbcTransaction();

			out("\n\n\nFor better results, edit the properties file:");
			out(propertiesFile().getAbsolutePath());
			out("\nYou can publish your best results by mail to:");
			out("prevayler-scalability@lists.sourceforge.net. Please include info about your");
			out("processors (quantity, type, speed), compiler, VM, operating system and DBMS.");
			out("");
			out("Scalability test results are published on www.prevayler.org.");
			out("See you there.\n");
			out("Klaus Wuestefeld and Daniel Santos.\n\n");

		} catch (Exception ex) {
			ex.printStackTrace();
		} catch (OutOfMemoryError err) {
			ScalabilityTestRun.outOfMemory();
		}

	}


	static private void runPrevaylerQuery() throws Exception {
		new QueryTestRun(
			new PrevaylerQuerySubject(),
			numberOfObjects(),
			prevaylerQueryThreadsMin(),
			prevaylerQueryThreadsMax()
		);
	}

	static private void runPrevaylerTransaction() throws Exception {
		PrevaylerTransactionSubject subject = new PrevaylerTransactionSubject(prevaylerTransactionLogDirectory(), prevaylerJournalSerializer());
		new TransactionTestRun(
			subject,
			numberOfObjects(),
			prevaylerTransactionThreadsMin(),
			prevaylerTransactionThreadsMax()
		);
		if (isPrevaylerTransactionConsistencyChecked()) {
			out("Checking transaction log consistency.");
			if (!subject.isConsistent()) throw new RuntimeException("Transaction log consistency check failed.");
			out("Transaction log OK.\n");
		}
	}

	static private void runJdbcQuery() {
		new QueryTestRun(
			new JDBCQuerySubject(jdbcDriverClassName(), jdbcConnectionURL(), jdbcUser(), jdbcPassword()),
			numberOfObjects(),
			jdbcQueryThreadsMin(),
			jdbcQueryThreadsMax()
		);
	}

	static private void runJdbcTransaction() {
		new TransactionTestRun(
			new JDBCTransactionSubject(jdbcDriverClassName(), jdbcConnectionURL(), jdbcUser(), jdbcPassword()),
			numberOfObjects(),
			jdbcTransactionThreadsMin(),
			jdbcTransactionThreadsMax()
		);
	}


	static private File propertiesFile() throws IOException {
		File result = new File("ScalabilityTest.properties");
		if (!result.exists()) {
			out("Creating the properties file.");
			createPropertiesFile(result);
		}
		return result;
	}

	static private void createPropertiesFile(File file) throws IOException {
		PrintStream stream = new PrintStream(new FileOutputStream(file));
		stream.println(
			"###########################################################\n" +
			"#                                                         #\n" +
			"#      PREVAYLER VS JDBC SCALABILITY TEST PROPERTIES      #\n" +
			"#                                                         #\n" +
			"###########################################################\n" +
			"\n" +
			"NumberOfObjects = TEN\n" +
			"# NumberOfObjects = ONE_MILLION\n" +
			"# NumberOfObjects = TEN_MILLION\n" +
			"# NumberOfObjects = TWENTY_MILLION\n" +
			"#\n" +
			"# The results are only valid if both Prevayler and the\n" +
			"# database can run the tests without paging memory to disk.\n" +
			"#\n" +
			"# Running the tests with one hundred thousand objects\n" +
			"# (default option) requires approx. 128MB free RAM.\n" +
			"# The VM must be started with a sufficient maximum heap\n" +
			"# size or you will get an OutOfMemoryError.\n" +
			"#\n" +
			"# Example for Linux and Windows:  java -Xmx128000000 ...\n" +
			"#\n" +
			"# (This can be set with the scalability.jvmarg property\n" +
			"# in build.properties; see sample.build.properties for\n" +
			"# examples.)\n" +
			"#\n" +
			"# Running the tests with one million objects requires\n" +
			"# approx. 940MB free RAM.\n" +
			"# Running the tests with ten million objects requires\n" +
			"# approx. 9.4GB free RAM and a 64bit VM.\n" +
			"#\n" +
			"# IMPORTANT: Remember to shutdown all other non-vital\n" +
			"# processes before running the tests. Even the database\n" +
			"# process should be down while running the Prevayler tests\n" +
			"# that do not use it.\n" +
			"\n" +	
			"\n" +	
			"###########################################################\n" +
			"# PREVAYLER QUERY TEST\n" +
			"\n" +
			"RunPrevaylerQueryTest = YES\n" +
			"# RunPrevaylerQueryTest = NO\n" +
			"\n" +
			"PrevaylerQueryThreadsMinimum = 1\n" +
			"PrevaylerQueryThreadsMaximum = 1\n" +
			"# More threads can produce better results on\n" +
			"# multi-processor machines.\n" +
			"\n" +
			"\n" +
			"###########################################################\n" +
			"# PREVAYLER TRANSACTION TEST\n" +
			"\n" +
			"# RunPrevaylerTransactionTest = YES\n" +
			"RunPrevaylerTransactionTest = NO\n" +
			"\n" +
			"PrevaylerTransactionThreadsMinimum = 1\n" +
			"PrevaylerTransactionThreadsMaximum = 1\n" +
			"#\n" +
			"# More threads can produce better results on machines with\n" +
			"# multiple disks.\n" +
			"\n" +
			"# TransactionTestCheckConsistency = YES\n" +
			"TransactionTestCheckConsistency = NO\n" +
			"#\n" +
			"# Verifies the integrity of the journal files produced in\n" +
			"# your particular environment.\n" +
			"\n" +
			"TransactionLogDirectory = TransactionTest\n" +
			"#\n" +
			"# The full path name can be used. Example for Windows:\n" +
			"# TransactionLogDirectory1 = c:\\\\temp\\\\TransactionTest\n" +
			"# The back-slash (\\) is the escape character so you must\n" +
			"# use two back-slashes (\\\\).\n" +
			"\n" +
			"PrevaylerJournalSerializer = " + JavaSerializer.class.getName() + "\n" +
			"\n" +
			"\n" +
			"###########################################################\n" +
			"# JDBC QUERY TEST\n" +
			"\n" +
			"RunJdbcQueryTest = NO\n" +
			"# RunJdbcQueryTest = YES\n" +
			"\n" +
			"JdbcQueryThreadsMinimum = 1\n" +
			"JdbcQueryThreadsMaximum = 1\n" +
			"# More threads can produce better results on some machines.\n" +
			"\n" +
			"\n" +
			"###########################################################\n" +
			"# JDBC TRANSACTION TEST\n" +
			"\n" +
			"RunJdbcTransactionTest = NO\n" +
			"# RunJdbcTransactionTest = YES\n" +
			"\n" +
			"JdbcTransactionThreadsMinimum = 1\n" +
			"JdbcTransactionThreadsMaximum = 1\n" +
			"# More threads can produce better results on some machines.\n" +
			"\n" +
			"\n" +
			"###########################################################\n" +
			"# JDBC CONNECTION\n" +
			"# (necessary to run the JDBC tests)\n" +
			"\n" +
			"JdbcDriverClassName =\n" +
			"JdbcConnectionURL =\n" +
			"JdbcUser =\n" +
			"JdbcPassword =\n" +
			"# These two tables are necessary for the JDBC tests:\n" +
			"# QUERY_TEST and TRANSACTION_TEST.\n" +
			"# Both tables have the same column structure:\n" +
			"#    ID DECIMAL,\n" +
			"#    NAME VARCHAR2(8),\n" +
			"#    STRING1 VARCHAR2(1000),\n" +
			"#    BIGDECIMAL1 DECIMAL,\n" +
			"#    BIGDECIMAL2 DECIMAL,\n" +
			"#    DATE1 DATE,\n" +
			"#    DATE2 DATE.\n" +
			"\n" +
			"# IMPORTANT: For best results, create indices on the\n" +
			"# QUERY_TEST.NAME and TRANSACTION_TEST.ID columns.\n" +
			"# Do not create indices on any other column.\n"
		);
	}


	static private int numberOfObjects() {
		String property = property("NumberOfObjects");
		if ("TEN".equals(property)) return   10;
		if ("ONE_MILLION"         .equals(property)) return  1000000;
		if ("TEN_MILLION"         .equals(property)) return 10000000;
		if ("TWENTY_MILLION"      .equals(property)) return 20000000;
		throw new RuntimeException("NumberOfObjects property must be equal to ONE_HUNDRED_THOUSAND, ONE_MILLION, TEN_MILLION or TWENTY_MILLION.");
	}

	static private boolean isPrevaylerQueryChosen() {
		return booleanProperty("RunPrevaylerQueryTest");
	}

	static private int prevaylerQueryThreadsMin() {
		return intProperty("PrevaylerQueryThreadsMinimum");
	}

	static private int prevaylerQueryThreadsMax() {
		return intProperty("PrevaylerQueryThreadsMaximum");
	}


	static private boolean isPrevaylerTransactionChosen() {
		return booleanProperty("RunPrevaylerTransactionTest");
	}

	static private int prevaylerTransactionThreadsMin() {
		return intProperty("PrevaylerTransactionThreadsMinimum");
	}

	static private int prevaylerTransactionThreadsMax() {
		return intProperty("PrevaylerTransactionThreadsMaximum");
	}

	static private boolean isPrevaylerTransactionConsistencyChecked() {
		return booleanProperty("TransactionTestCheckConsistency");
	}
	
	static private String prevaylerTransactionLogDirectory() {
		String result = property("TransactionLogDirectory");
		out("\n\nPrevayler TransactionLog Directory: " + result);
		return result;
	}

	static private String prevaylerJournalSerializer() {
		String result = properties.getProperty("PrevaylerJournalSerializer");
		if (result == null) result = JavaSerializer.class.getName();
		out("\n\nPrevayler Journal Serializer: " + result);
		return result;
	}

	static private boolean isJdbcQueryChosen() {
		return booleanProperty("RunJdbcQueryTest");
	}

	static private int jdbcQueryThreadsMin() {
		return intProperty("JdbcQueryThreadsMinimum");
	}

	static private int jdbcQueryThreadsMax() {
		return intProperty("JdbcQueryThreadsMaximum");
	}


	static private boolean isJdbcTransactionChosen() {
		return booleanProperty("RunJdbcTransactionTest");
	}

	static private int jdbcTransactionThreadsMin() {
		return intProperty("JdbcTransactionThreadsMinimum");
	}

	static private int jdbcTransactionThreadsMax() {
		return intProperty("JdbcTransactionThreadsMaximum");
	}


	static private String jdbcDriverClassName() {
		return property("JdbcDriverClassName");
	}

	static private String jdbcConnectionURL() {
		return property("JdbcConnectionURL");
	}

	static private String jdbcUser() {
		return property("JdbcUser");
	}

	static private String jdbcPassword() {
		return property("JdbcPassword");
	}


	static private String property(String name) {
		String result = properties.getProperty(name);
		if (result == null) throw new RuntimeException("Property " + name + " not found.");
		return result;
	}

	static private int intProperty(String name) {
		try {
			return Integer.valueOf(property(name)).intValue();
		} catch (NumberFormatException nfx) {
			out("NumberFormatException reading property " + name);
			throw nfx;
		}
	}

	static private boolean booleanProperty(String name) {
		boolean result = "yes".equalsIgnoreCase(property(name));
		if (result) return true;
		out("\n\n\n" + name + " property is set to " + property(name) + ".");
		out("This test will be skipped (see properties file).");
		return false;
	}


	static private void out(Object message) {
		System.out.println(message);
	}
}
