/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2009, 2013 Oracle and/or its affiliates.  All rights reserved.
 *
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml;
using NUnit.Framework;
using BerkeleyDB;

namespace CsharpAPITest
{
	[TestFixture]
	public class SecondaryHashDatabaseTest : CSharpTestFixture
	{

		[TestFixtureSetUp]
		public void SetUpTestFixture()
		{
			testFixtureName = "SecondaryHashDatabaseTest";
			base.SetUpTestfixture();
		}

		[Test]
		public void TestHashFunction()
		{
			testName = "TestHashFunction";
			SetUpTest(true);
			string dbFileName = testHome + "/" + testName + ".db";
			string dbSecFileName = testHome + "/" +
			    testName + "_sec.db";

			// Open a primary hash database.
			HashDatabaseConfig dbConfig =
			    new HashDatabaseConfig();
			dbConfig.Creation = CreatePolicy.IF_NEEDED;
			HashDatabase hashDB = HashDatabase.Open(
			    dbFileName, dbConfig);

			/*
			 * Define hash function and open a secondary 
			 * hash database.
			 */
			SecondaryHashDatabaseConfig secDBConfig =
			    new SecondaryHashDatabaseConfig(hashDB, null);
			secDBConfig.HashFunction = 
			    new HashFunctionDelegate(HashFunction);
			secDBConfig.Creation = CreatePolicy.IF_NEEDED;
			SecondaryHashDatabase secDB =
			   SecondaryHashDatabase.Open(dbSecFileName,
			   secDBConfig);

			/*
			 * Confirm the hash function defined in the configuration.
			 * Call the hash function and the one from secondary 
			 * database. If they return the same value, then the hash 
			 * function is configured successfully.
			 */
			uint data = secDB.HashFunction(BitConverter.GetBytes(1));
			Assert.AreEqual(0, data);

			// Close all.
			secDB.Close();
			hashDB.Close();
		}

		public uint HashFunction(byte[] data)
		{
			data[0] = 0;
			return BitConverter.ToUInt32(data, 0);
		}

		[Test]
		public void TestCompare()
		{
			testName = "TestCompare";
			SetUpTest(true);
			string dbFileName = testHome + "/" + testName + ".db";
			string dbSecFileName = testHome + "/" + testName +
			    "_sec.db";

			// Open a primary hash database.
			HashDatabaseConfig dbConfig =
			    new HashDatabaseConfig();
			dbConfig.Creation = CreatePolicy.ALWAYS;
			HashDatabase db = HashDatabase.Open(
			    dbFileName, dbConfig);

			// Open a secondary hash database. 
			SecondaryHashDatabaseConfig secConfig =
			    new SecondaryHashDatabaseConfig(null, null);
			secConfig.Creation = CreatePolicy.IF_NEEDED;
			secConfig.Primary = db;
			secConfig.Compare =
			    new EntryComparisonDelegate(SecondaryEntryComparison);
			SecondaryHashDatabase secDB =
			    SecondaryHashDatabase.Open(dbSecFileName, secConfig);

			/*
			 * Get the compare function set in the configuration 
			 * and run it in a comparison to see if it is alright.
			 */
			DatabaseEntry dbt1, dbt2;
			dbt1 = new DatabaseEntry(
			    BitConverter.GetBytes((int)257));
			dbt2 = new DatabaseEntry(
			    BitConverter.GetBytes((int)255));
			Assert.Less(0, secDB.Compare(dbt1, dbt2));

			secDB.Close();
			db.Close();
		}

		[Test]
		public void TestDuplicates()
		{
			testName = "TestDuplicates";
			SetUpTest(true);
			string dbFileName = testHome + "/" + testName + ".db";
			string dbSecFileName = testHome + "/" + testName
			    + "_sec.db";

			// Open a primary hash database.
			HashDatabaseConfig dbConfig =
			    new HashDatabaseConfig();
			dbConfig.Creation = CreatePolicy.ALWAYS;
			HashDatabase db = HashDatabase.Open(
			    dbFileName, dbConfig);

			// Open a secondary hash database. 
			SecondaryHashDatabaseConfig secConfig =
			    new SecondaryHashDatabaseConfig(null, null);
			secConfig.Primary = db;
			secConfig.Duplicates = DuplicatesPolicy.SORTED;
			secConfig.Creation = CreatePolicy.IF_NEEDED;
			SecondaryHashDatabase secDB =
			    SecondaryHashDatabase.Open(
			    dbSecFileName, secConfig);

			// Confirm the duplicate in opened secondary database.
			Assert.AreEqual(DuplicatesPolicy.SORTED,
			    secDB.Duplicates);

			secDB.Close();
			db.Close();
		}

		/*
		 * Tests to all Open() share the same configuration in 
		 * AllTestData.xml.
		 */
		[Test]
		public void TestOpen()
		{
			testName = "TestOpen";
			SetUpTest(true);
			string dbFileName = testHome + "/" + testName + ".db";
			string dbSecFileName = testHome + "/" +
			    testName + "_sec.db";

			OpenSecHashDB(testFixtureName, "TestOpen",
			    dbFileName, dbSecFileName, false);
		}

		[Test]
		public void TestOpenWithDBName()
		{
			testName = "TestOpenWithDBName";
			SetUpTest(true);
			string dbFileName = testHome + "/" + testName + ".db";
			string dbSecFileName = testHome + "/" +
			    testName + "_sec.db";

			OpenSecHashDB(testFixtureName, "TestOpen",
			    dbFileName, dbSecFileName, true);
		}

		public void OpenSecHashDB(string className,
		    string funName, string dbFileName, string dbSecFileName,
		    bool ifDBName)
		{
			XmlElement xmlElem = Configuration.TestSetUp(
			    className, funName);

			// Open a primary recno database.
			HashDatabaseConfig primaryDBConfig =
			    new HashDatabaseConfig();
			primaryDBConfig.Creation = CreatePolicy.IF_NEEDED;
			HashDatabase primaryDB;

			/*
			 * If secondary database name is given, the primary 
			 * database is also opened with database name.
			 */
			if (ifDBName == false)
				primaryDB = HashDatabase.Open(dbFileName,
				    primaryDBConfig);
			else
				primaryDB = HashDatabase.Open(dbFileName,
				    "primary", primaryDBConfig);

			try
			{
				// Open a new secondary database.
				SecondaryHashDatabaseConfig secHashDBConfig =
				    new SecondaryHashDatabaseConfig(
				    primaryDB, null);
				SecondaryHashDatabaseConfigTest.Config(
				    xmlElem, ref secHashDBConfig, false);
				secHashDBConfig.Creation =
				    CreatePolicy.IF_NEEDED;

				SecondaryHashDatabase secHashDB;
				if (ifDBName == false)
					secHashDB = SecondaryHashDatabase.Open(
					    dbSecFileName, secHashDBConfig);
				else
					secHashDB = SecondaryHashDatabase.Open(
					    dbSecFileName, "secondary",
					    secHashDBConfig);

				// Close the secondary database.
				secHashDB.Close();

				// Open the existing secondary database.
				SecondaryDatabaseConfig secDBConfig =
				    new SecondaryDatabaseConfig(
				    primaryDB, null);

				SecondaryDatabase secDB;
				if (ifDBName == false)
					secDB = SecondaryHashDatabase.Open(
					    dbSecFileName, secDBConfig);
				else
					secDB = SecondaryHashDatabase.Open(
					    dbSecFileName, "secondary", secDBConfig);

				// Close secondary database.
				secDB.Close();
			}
			catch (DatabaseException)
			{
				throw new TestException();
			}
			finally
			{
				// Close primary database.
				primaryDB.Close();
			}
		}

		[Test]
		public void TestOpenWithinTxn()
		{
			testName = "TestOpenWithinTxn";
			SetUpTest(true);
			string dbFileName = testName + ".db";
			string dbSecFileName = testName + "_sec.db";

			OpenSecHashDBWithinTxn(testFixtureName,
			    "TestOpen", testHome, dbFileName,
			    dbSecFileName, false);
		}

		[Test]
		public void TestOpenDBNameWithinTxn()
		{
			testName = "TestOpenDBNameWithinTxn";
			SetUpTest(true);
			string dbFileName = testName + ".db";
			string dbSecFileName = testName + "_sec.db";

			OpenSecHashDBWithinTxn(testFixtureName,
			    "TestOpen", testHome, dbFileName,
			    dbSecFileName, true);
		}

		public void OpenSecHashDBWithinTxn(string className,
		    string funName, string home, string dbFileName,
		    string dbSecFileName, bool ifDbName)
		{
			XmlElement xmlElem = Configuration.TestSetUp(
			    className, funName);

			// Open an environment.
			DatabaseEnvironmentConfig envConfig =
			    new DatabaseEnvironmentConfig();
			envConfig.Create = true;
			envConfig.UseTxns = true;
			envConfig.UseMPool = true;
			envConfig.UseLogging = true;
			DatabaseEnvironment env = DatabaseEnvironment.Open(
			    home, envConfig);

			// Open a primary hash database.
			Transaction openDBTxn = env.BeginTransaction();
			HashDatabaseConfig dbConfig =
			    new HashDatabaseConfig();
			dbConfig.Creation = CreatePolicy.IF_NEEDED;
			dbConfig.Env = env;
			HashDatabase db = HashDatabase.Open(
			    dbFileName, dbConfig, openDBTxn);
			openDBTxn.Commit();

			// Open a secondary hash database.
			Transaction openSecTxn = env.BeginTransaction();
			SecondaryHashDatabaseConfig secDBConfig =
			    new SecondaryHashDatabaseConfig(db,
			    new SecondaryKeyGenDelegate(SecondaryKeyGen));
			SecondaryHashDatabaseConfigTest.Config(xmlElem,
			    ref secDBConfig, false);
			secDBConfig.HashFunction = null;
			secDBConfig.Env = env;
			SecondaryHashDatabase secDB;
			if (ifDbName == false)
				secDB = SecondaryHashDatabase.Open(
				    dbSecFileName, secDBConfig, openSecTxn);
			else
				secDB = SecondaryHashDatabase.Open(
				    dbSecFileName, "secondary", secDBConfig,
				    openSecTxn);
			openSecTxn.Commit();

			// Confirm its flags configured in secDBConfig.
			Confirm(xmlElem, secDB, true);
			secDB.Close();

			// Open the existing secondary database.
			Transaction secTxn = env.BeginTransaction();
			SecondaryDatabaseConfig secConfig =
			    new SecondaryDatabaseConfig(db,
			    new SecondaryKeyGenDelegate(SecondaryKeyGen));
			secConfig.Env = env;

			SecondaryDatabase secExDB;
			if (ifDbName == false)
				secExDB = SecondaryHashDatabase.Open(
				    dbSecFileName, secConfig, secTxn);
			else
				secExDB = SecondaryHashDatabase.Open(
				    dbSecFileName, "secondary", secConfig,
				    secTxn);
			secExDB.Close();
			secTxn.Commit();

			db.Close();
			env.Close();
		}

		public DatabaseEntry SecondaryKeyGen(
		    DatabaseEntry key, DatabaseEntry data)
		{
			DatabaseEntry dbtGen;
			dbtGen = new DatabaseEntry(data.Data);
			return dbtGen;
		}

		public int SecondaryEntryComparison(
		    DatabaseEntry dbt1, DatabaseEntry dbt2)
		{
			int a, b;
			a = BitConverter.ToInt32(dbt1.Data, 0);
			b = BitConverter.ToInt32(dbt2.Data, 0);
			return a - b;
		}

		public static void Confirm(XmlElement xmlElem,
		    SecondaryHashDatabase secDB, bool compulsory)
		{
			Configuration.ConfirmUint(xmlElem, "FillFactor",
			    secDB.FillFactor, compulsory);
			Configuration.ConfirmUint(xmlElem, "NumElements",
			    secDB.TableSize, compulsory);
		}
	}
}
