/*
	Проверка интерфейса oess_1::db::storage::trx::snapshot_collection_t.
*/

#include <iostream>
#include <stdexcept>

#include <auto_ptr_3/h/obj_ptr.hpp>

#include <ace/OS.h>
#include <ace/Dirent.h>
#include <ace/Dirent_Selector.h>

#include <pcre++.h>

#include <cpp_util_2/h/lexcast.hpp>
#include <cpp_util_2/lexcasts/h/util.hpp>

#include <oess_1/defs/h/ex.hpp>

#include <oess_1/file/h/file.hpp>

#include <oess_1/db/storage/h/errno.hpp>

#include <oess_1/db/storage/trx/h/types.hpp>

//
// split_physic_name
//

/*!
	Разбить указанное имя на физическое имя БД и имя каталога.
*/
static void
split_physic_name(
	//! Исходное имя, которое, возможно, содержит имя каталога.
	const std::string & physic_name,
	//! Приемник для имени каталога.
	std::string & path_name,
	//! Приемник для имени БД.
	std::string & db_name )
	{
		// Выделяем каталог, в котором нужно искать файлы и само
		// имя БД, для которой файлы ищутся.
		try
			{
				pcrepp::Pcre name_sep_expr(
						"^(.*[\\\\/:]){0,1}([^\\\\/:]+$)" );
				if( !name_sep_expr.search( physic_name ) )
					OESS_THROW_PHYSIC(
							oess_1::db::storage::err::c_unexpected,
							physic_name 
							<< ": unable to split to <path> and <name>; "
									"Pcre::search failed!" )

				// Есть и имя каталога, и имя файла.
				path_name = name_sep_expr[ 0 ];
				db_name = name_sep_expr[ 1 ];
			}
		catch( const std::exception & x )
			{
				OESS_THROW_PHYSIC(
						oess_1::db::storage::err::c_unexpected,
						physic_name 
						<< ": unable to split to <path> and <name>; "
								"exception caught: " << x.what() )
			}
	}

//
// process_db_files
//

/*!
	Выполняет выборку имен файлов хранилища и удаляет их.
*/
static void
process_db_files(
	//! Найденые имена.
	ACE_Dirent_Selector & selector,
	//! Каталог, в котором должны быть файлы БД.
	const std::string & path_name,
	//! Физическое имя БД, которой должны файлы.
	const std::string & physic_name )
	{
		try
			{
				// Регулярное выражение, которое будет использоваться
				// для выделения нужных нам имен файлов.
				pcrepp::Pcre db_file_regex(
						std::string( "^") + physic_name + "\\..+$" );
   
				for( int i = 0; i != selector.length(); ++i )
					{
						if( db_file_regex.search( selector[ i ]->d_name ) )
							{
								oess_1::file::file_t::remove(
										path_name + selector[ i ]->d_name );
							}
					}
			}
		catch( const std::exception & x )
			{
				OESS_THROW_PHYSIC(
						oess_1::db::storage::err::c_unexpected,
						physic_name 
						<< ": unable to filter found file names; "
								"exception caught: " << x.what() )
			}
	}

//
// remove_all_db_files
//

void
remove_all_db_files(
	const std::string & physic_name )
	{
		// Получаем имя каталога, в котором нужно искать файлы и
		// собственное имя БД.
		std::string path_name, db_name;
		split_physic_name( physic_name, path_name, db_name );

		// Получаем список файлов в указанном каталоге для того,
		// чтобы затем выбрать оттуда имена наших trace-файлов.
		ACE_Dirent_Selector selector;
		if( -1 == selector.open( path_name.c_str() ) )
			OESS_THROW_PHYSIC(
					oess_1::db::storage::err::c_trx_file_search_failed,
					"unable to search snapshot files in: " << path_name )

		process_db_files( selector, path_name, db_name );
	}

void
test_no_files( const std::string & physic_name )
	{
		const std::string test_name = "test_no_files";

		remove_all_db_files( physic_name );

		std::auto_ptr< oess_1::db::storage::trx::snapshot_collection_t >
				collection( oess_1::db::storage::trx::
						create_std_snapshot_collection( physic_name ) );

		// Не должно быть ни одно snapshot-а.
		unsigned int snapshot_count = collection->size();
		if( snapshot_count )
			throw std::runtime_error( test_name + ": FAILED: "
					"snapshot_count != 0; snapshot_count: " +
					cpp_util_2::slexcast( snapshot_count ) );
		
		// Не должно быть ни одного не актуального snapshot-а.
		if( !collection->non_actual().empty() )
			throw std::runtime_error( test_name + ": FAILED: "
					"non_actual().size() != 0" );

		// Не должно быть актуального snapshot-а.
		if( collection->has_actual() )
			throw std::runtime_error( test_name + ": FAILED: "
					"has_actual() != false" );

		// При обращении к методу actual() должно возникнуть исключение.
		std::string actual_method_test_result = "exception not thrown";
		try
			{
				collection->actual();
			}
		catch( const oess_1::ex_t & x )
			{
				if( oess_1::db::storage::err::c_no_actual_snapshot ==
						x.query_err_code().m_code )
					actual_method_test_result = "ok";
				else
					actual_method_test_result = "x.query_err_code() != "
							"c_no_actual_snapshot";
			}
		if( "ok" != actual_method_test_result )
			throw std::runtime_error( test_name + ": FAILED: "
					+ "actual(): " + actual_method_test_result );
	}

void
test_single_file( const std::string & physic_name )
	{
		const std::string test_name = "test_single_file";

		const oess_1::db::storage::trx::trace_file_number_t file_number = 234;

		{
			remove_all_db_files( physic_name );

			oess_1::file::file_t f;
			f.create( oess_1::db::storage::trx::create_snapshot_file_name(
					physic_name, file_number ) );
		}

		std::auto_ptr< oess_1::db::storage::trx::snapshot_collection_t >
				collection( oess_1::db::storage::trx::
						create_std_snapshot_collection( physic_name ) );
		
		// Должен быть ровно один snapshot.
		unsigned int snapshot_count = collection->size();
		if( snapshot_count != 1 )
			throw std::runtime_error( test_name + ": FAILED: "
					"snapshot_count != 1; snapshot_count: " +
					cpp_util_2::slexcast( snapshot_count ) );
		
		// Не должно быть ни одного не актуального snapshot-а.
		if( !collection->non_actual().empty() )
			throw std::runtime_error( test_name + ": FAILED: "
					"non_actual().size() != 0" );

		// Должен быть актуальный snaphot.
		if( !collection->has_actual() )
			throw std::runtime_error( test_name + ": FAILED: "
					"has_actual() != true" );

		// При обращении к методу actual() не должно возникнуть исключение.
		std::string actual_method_test_result = "ok";
		try
			{
				oess_1::db::storage::trx::trace_file_number_t actual =
						collection->actual();
				if( actual != file_number )
					actual_method_test_result = "actual != required_file_number; "
							"actual: " + cpp_util_2::slexcast( actual ) +
							", required_file_number: " +
							cpp_util_2::slexcast( file_number );
			}
		catch( const oess_1::ex_t & x )
			{
				actual_method_test_result = "exception thrown: x.query_err_code()"
						" == " + cpp_util_2::slexcast( x.query_err_code() );
			}
		if( "ok" != actual_method_test_result )
			throw std::runtime_error( test_name + ": FAILED: "
					+ "actual(): " + actual_method_test_result );
	}

void
test_multiple_files( const std::string & physic_name )
	{
		const std::string test_name = "test_multiple_files";

		const oess_1::db::storage::trx::trace_file_number_t
				non_actual_file_number_1 = 1;
		const oess_1::db::storage::trx::trace_file_number_t
				non_actual_file_number_2 = 256;
		const oess_1::db::storage::trx::trace_file_number_t
				non_actual_file_number_3 = 548;

		const oess_1::db::storage::trx::trace_file_number_t
				required_actual_file_number = 1034;

		{
			remove_all_db_files( physic_name );

			oess_1::file::file_t f;

			f.create( oess_1::db::storage::trx::create_snapshot_file_name(
					physic_name, non_actual_file_number_1 ) );
			f.close();
			f.create( oess_1::db::storage::trx::create_snapshot_file_name(
					physic_name, non_actual_file_number_2 ) );
			f.close();
			f.create( oess_1::db::storage::trx::create_snapshot_file_name(
					physic_name, non_actual_file_number_3 ) );
			f.close();
			f.create( oess_1::db::storage::trx::create_snapshot_file_name(
					physic_name, required_actual_file_number ) );
			f.close();
		}

		std::auto_ptr< oess_1::db::storage::trx::snapshot_collection_t >
				collection( oess_1::db::storage::trx::
						create_std_snapshot_collection( physic_name ) );
		
		// Должно быть 4 snapshot-а.
		unsigned int snapshot_count = collection->size();
		if( snapshot_count != 4 )
			throw std::runtime_error( test_name + ": FAILED: "
					"snapshot_count != 4; snapshot_count: " +
					cpp_util_2::slexcast( snapshot_count ) );
		
		// Во множестве актуальных snapshot-ов должно быть 3 элемента.
		oess_1::db::storage::trx::trace_file_number_set_t non_actual_snapshots =
				collection->non_actual();
		std::string non_actual_snapshot_value =
				cpp_util_2::slexcast(
						cpp_util_2::lexcasts::all(
								non_actual_snapshots, " " ) );
		const std::string expected_non_actual_snapshot_value = "1 256 548";
		if( 3 != non_actual_snapshots.size() )
			throw std::runtime_error( test_name + ": FAILED: "
					"non_actual().size() != 3; non_actual(): " +
					non_actual_snapshot_value );
		if( expected_non_actual_snapshot_value != non_actual_snapshot_value )
			throw std::runtime_error( test_name + ": FAILED: "
					"expexted non_actual() != non_actual(); "
					"expected non_actual(): " +
					expected_non_actual_snapshot_value +
					"; non_actual(): " + non_actual_snapshot_value );

		// Должен быть актуальный snaphot.
		if( !collection->has_actual() )
			throw std::runtime_error( test_name + ": FAILED: "
					"has_actual() != true" );

		// При обращении к методу actual() не должно возникнуть исключение.
		std::string actual_method_test_result = "ok";
		try
			{
				oess_1::db::storage::trx::trace_file_number_t actual =
						collection->actual();
				if( actual != required_actual_file_number )
					actual_method_test_result = "actual != required_file_number; "
							"actual: " + cpp_util_2::slexcast( actual ) +
							", required_file_number: " +
							cpp_util_2::slexcast( required_actual_file_number );
			}
		catch( const oess_1::ex_t & x )
			{
				actual_method_test_result = "exception thrown: x.query_err_code()"
						" == " + cpp_util_2::slexcast( x.query_err_code() );
			}
		if( "ok" != actual_method_test_result )
			throw std::runtime_error( test_name + ": FAILED: "
					+ "actual(): " + actual_method_test_result );
	}

void
test_include_to_empty_collection( const std::string & physic_name )
	{
		const std::string test_name = "test_include_to_empty_collection";

		const oess_1::db::storage::trx::trace_file_number_t file_number = 582;
		const std::string tmp_file_name =
				oess_1::db::storage::trx::create_snapshot_file_name(
						physic_name, file_number ) + ".tmp";

		{
			remove_all_db_files( physic_name );

			oess_1::file::file_t f;
			f.create( tmp_file_name );
		}

		std::auto_ptr< oess_1::db::storage::trx::snapshot_collection_t >
				collection( oess_1::db::storage::trx::
						create_std_snapshot_collection( physic_name ) );

		collection->include( tmp_file_name, file_number );

		// Должен быть ровно один snapshot.
		unsigned int snapshot_count = collection->size();
		if( snapshot_count != 1 )
			throw std::runtime_error( test_name + ": FAILED: "
					"snapshot_count != 1; snapshot_count: " +
					cpp_util_2::slexcast( snapshot_count ) );
		
		// Не должно быть ни одного не актуального snapshot-а.
		if( !collection->non_actual().empty() )
			throw std::runtime_error( test_name + ": FAILED: "
					"non_actual().size() != 0" );

		// Должен быть актуальный snaphot.
		if( !collection->has_actual() )
			throw std::runtime_error( test_name + ": FAILED: "
					"has_actual() != true" );

		// При обращении к методу actual() не должно возникнуть исключение.
		std::string actual_method_test_result = "ok";
		try
			{
				oess_1::db::storage::trx::trace_file_number_t actual =
						collection->actual();
				if( actual != file_number )
					actual_method_test_result = "actual != required_file_number; "
							"actual: " + cpp_util_2::slexcast( actual ) +
							", required_file_number: " +
							cpp_util_2::slexcast( file_number );
			}
		catch( const oess_1::ex_t & x )
			{
				actual_method_test_result = "exception thrown: x.query_err_code()"
						" == " + cpp_util_2::slexcast( x.query_err_code() );
			}
		if( "ok" != actual_method_test_result )
			throw std::runtime_error( test_name + ": FAILED: "
					+ "actual(): " + actual_method_test_result );
	}

void
test_get_actual( const std::string & physic_name )
	{
		const std::string test_name = "test_get_actual";

		const oess_1::db::storage::trx::trace_file_number_t file_number = 600;
		const std::string tmp_file_name =
				oess_1::db::storage::trx::create_snapshot_file_name(
						physic_name, file_number ) + ".tmp";
		const std::string tmp_file_content =
				"123457890123457890123457890123457890123457890123457890"
				"123457890123457890123457890123457890123457890123457890"
				"123457890123457890123457890123457890123457890123457890"
				"123457890123457890123457890123457890123457890123457890"
				"123457890123457890123457890123457890123457890123457890";

		{
			remove_all_db_files( physic_name );

			oess_1::file::file_t f;
			f.create( tmp_file_name );
			f.write( tmp_file_content.data(), tmp_file_content.size() );
		}

		std::auto_ptr< oess_1::db::storage::trx::snapshot_collection_t >
				collection( oess_1::db::storage::trx::
						create_std_snapshot_collection( physic_name ) );

		collection->include( tmp_file_name, file_number );

		if( oess_1::file::file_t::is_file_exists( tmp_file_name ) )
			throw std::runtime_error( test_name + " FAILED: "
					"file '" + tmp_file_name + "' must be renamed to "
					"snapshot file" );

		collection->get_actual( tmp_file_name );

		oess_1::file::file_t tmp_file;
		tmp_file.open( tmp_file_name, true );

		if( tmp_file_content.size() != tmp_file.length() )	
			throw std::runtime_error( test_name + " FAILED: "
					"required file length != real file length; "
					"required file length: " +
					cpp_util_2::slexcast( tmp_file_content.size() ) +
					", real file length: " +
					cpp_util_2::slexcast( tmp_file.length() ) );

		std::vector< char > tmp_content( tmp_file.length() );
		tmp_file.read( &tmp_content[ 0 ], tmp_content.capacity() );

		if( 0 != ACE_OS::memcmp( tmp_file_content.data(),
				&tmp_content[ 0 ], tmp_content.capacity() ) )
			throw std::runtime_error( test_name + " FAILED: "
					"required file content != real file content" );
	}

int
main( int, char ** )
	{
		const std::string physic_name = "test/storage/snapshot_collection/db";
		try
			{
				test_no_files( physic_name );
				test_single_file( physic_name );
				test_multiple_files( physic_name );
				test_include_to_empty_collection( physic_name );
				test_get_actual( physic_name );

				return 0;
			}
		catch( const std::exception & x )
			{
				std::cerr << x.what() << std::endl;
			}
		catch( ... )
			{
				std::cerr << "Unknown exception!" << std::endl;
			}

		return 2;
	}
