/*
 * Проверка корректности связей между элементами во всех entity_stream-ах
 * (первый элемент потока должен иметь 0 в качестве prev, последний элемент
 * потока должен иметь 0 в качестве next).
 */

#include <algorithm>
#include <functional>
#include <iostream>
#include <memory>
#include <stdexcept>

#include <cpp_util_2/h/lexcast.hpp>

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

#include <oess_1/io/h/mem_buf.hpp>
#include <oess_1/io/h/bstring_buf.hpp>

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

#include <oess_1/db/storage/impl/h/std_entity_stream.hpp>

/*
 * Предикат для std::for_each, который отвечает за проверку
 * содержимого очередного entity_stream.
 */
class entity_stream_checker_t
	:	public std::unary_function< const std::string &, void >
	{
	private :
		oess_1::db::storage::stream_storage_t & m_storage;
		int & m_damaged_stream_count;

		/*
		 * Извлечение метаописания первой сущности в потоке и
		 * проверка ее поля prev.
		 */
		void
		check_stream_first_entity_metainfo(
			const std::string & stream_name,
			const oess_1::db::storage::chain_id_t & first_id )
			{
				oess_1::db::storage::impl::stream_item_t item;
				oess_1::db::storage::load_from_storage(
						m_storage.chain_storage(),
						first_id,
						item );

				if( oess_1::db::storage::invalid_chain_id != item.prev() )
					{
						std::cerr << stream_name << ": invalid item.prev() for "
								"first item: " << item.prev() << std::endl;
						++m_damaged_stream_count;
					}
			}


		/*
		 * Проверка очередного открытого потока.
		 *
		 * Использует переданный entity_stream_t только для того,
		 * чтобы определить ссылку на первый объект потока. Затем через
		 * интерфейс chain_storage загружает метаинформацию для этого
		 * объекта и проверяет ссылку на prev. Эта ссылка должна быть
		 * нулевой.
		 */
		void
		enumerate_stream(
			const std::string & stream_name,
			oess_1::db::storage::entity_stream_t & stream )
			{
				oess_1::db::storage::chain_id_t first =
						stream.entity_find_next(
								oess_1::db::storage::invalid_chain_id );
				if( oess_1::db::storage::invalid_chain_id == first )
					std::cout << stream_name << ": empty" << std::endl;
				else
					check_stream_first_entity_metainfo(
							stream_name,
							first );
			}

	public :
		entity_stream_checker_t(
			oess_1::db::storage::stream_storage_t & storage,
			int & damaged_stream_count )
			:	m_storage( storage )
			,	m_damaged_stream_count( damaged_stream_count )
			{}

		result_type
		operator()( argument_type stream_name )
			{
				std::cout << stream_name << ": checking stream" << std::endl;

				oess_1::db::storage::entity_stream_t & stream =
						m_storage.stream_get( stream_name );

				enumerate_stream( stream_name, stream );
			}
	};

/*
 * Открыть указанную базу и выполнить проверку всех содержащихся в ней
 * потоков.
 */
void
test_all_entity_streams( const std::string & physic_name )
	{
		const oess_1::db::storage::config_t config;

		std::auto_ptr< oess_1::db::storage::stream_storage_t >
				storage( oess_1::db::storage::open_stream_storage(
						physic_name, config, true, false ) );

		std::set< std::string > streams = storage->stream_names();
		if( streams.empty() )
			std::cout << "no entity streams" << std::endl;

		int damaged_stream_count = 0;
		std::for_each(
				streams.begin(),
				streams.end(),
				entity_stream_checker_t(
						*storage,
						damaged_stream_count ) );

		if( damaged_stream_count )
			throw std::runtime_error(
					"database " + physic_name + " has " +
					cpp_util_2::slexcast( damaged_stream_count ) +
					" broken entity_stream(s)" );
	}

int
main( int argc, char ** argv )
	{
		try
			{
				if( 2 != argc )
					{
						std::cerr << "test.stream_storage.check_all_entity_streams"
								" <db>" << std::endl;
						return 1;
					}

				test_all_entity_streams( argv[ 1 ] );

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

		return 2;
	}

