/*

Copyright (c) 2002-2005, Yauheni Akhotnikau
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

- The name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.

*/

/*
	ObjESSty-1
*/
#include <oess_1/defs/h/ex.hpp>

#include <oess_1/defs/log/h/out.hpp>
#include <oess_1/defs/log/h/err.hpp>

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

#include <oess_1/stdsn/h/ent_std.hpp>

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

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

namespace oess_1 {

namespace db {

namespace storage {

namespace impl {

//
// make_outer_err_code
//
inline oess_1::err_code_t
make_outer_err_code(
	const std::string & physic_name,
	const std::string & method_name,
	const oess_1::err_code_t & nested )
	{
		return oess_1::err_code_t(
				nested.m_code,
				nested.m_file,
				nested.m_line,
				"storage: " + physic_name +
				", method: " + method_name + ", nested exception: '"
				+ nested.m_desc + "'" );
	}

#define STREAM_STORAGE__START_METHOD_WRAPPER(method_name) \
const char * wrp_method_name = #method_name; \
try {

#define STREAM_STORAGE__FINISH_METHOD_WRAPPER } \
catch( const oess_1::logic_ex_t & x ) \
	{ \
		throw oess_1::logic_ex_t(\
				make_outer_err_code(\
						m_physic_name, wrp_method_name, \
						x.query_err_code() ) ); \
	} \
catch( const oess_1::physic_ex_t & x ) \
	{ \
		m_is_ok = false; \
		throw oess_1::physic_ex_t(\
				make_outer_err_code(\
						m_physic_name, wrp_method_name, \
						x.query_err_code() ) ); \
	}


/*
** std_stream_storage_t
*/

std_stream_storage_t::std_stream_storage_t(
	const std::string & physic_name,
	std::auto_ptr< chain_storage_t > storage,
	const chain_id_t & meta_info_id,
	oess_1::io::istream_t & meta_info_stream )
	:	m_physic_name( physic_name )
	,	m_storage( storage )
	,	m_meta_info_id( meta_info_id )
	,	m_is_ok( false )
	{
		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::std_stream_storage_t )

		// Нужно поднять все описания из базы.
		parse_meta_info( meta_info_stream );

		STREAM_STORAGE__FINISH_METHOD_WRAPPER
	}

std_stream_storage_t::~std_stream_storage_t()
	{}

void
std_stream_storage_t::trans_start()
	{
		ensure_all_right();

		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::trans_start )

		m_storage->trx_start();

		STREAM_STORAGE__FINISH_METHOD_WRAPPER
	}

void
std_stream_storage_t::trans_commit()
	{
		ensure_all_right();

		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::trans_commit )

		m_storage->trx_commit();

		STREAM_STORAGE__FINISH_METHOD_WRAPPER
	}

void
std_stream_storage_t::trans_rollback()
	{
		ensure_all_right();

		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::trans_rollback )

		m_storage->trx_rollback();

		// Все содержимое мета-информации нужно перечитать!
		reload_meta_info_on_rollback();

		STREAM_STORAGE__FINISH_METHOD_WRAPPER
	}

void 
std_stream_storage_t::stream_create(
	const std::string & name )
	{
		ensure_all_right();

		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::stream_create )

		lo_stream_create( name );

		STREAM_STORAGE__FINISH_METHOD_WRAPPER
	}

bool
std_stream_storage_t::stream_is_exists(
	const std::string & name ) const
	{
		ensure_all_right();

		return m_dir.is_exists( name );
	}

void
std_stream_storage_t::stream_destroy(
	const std::string & name )
	{
		ensure_all_right();

		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::stream_destroy )

		lo_stream_destroy( name );

		STREAM_STORAGE__FINISH_METHOD_WRAPPER
	}

std::set< std::string >
std_stream_storage_t::stream_names() const
	{
		ensure_all_right();

		return m_dir.names();
	}

entity_stream_t &
std_stream_storage_t::stream_get(
	const std::string & name )
	{
		ensure_all_right();

		entity_stream_t * result = 0;

		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::stream_get(name) )

		const stream_storage_dir_item_t & item = m_dir.item( name );
		result = &stream_get( item.id() );

		STREAM_STORAGE__FINISH_METHOD_WRAPPER

		return *result;
	}

entity_stream_t &
std_stream_storage_t::stream_get(
	const oess_1::uint_t & id )
	{
		ensure_all_right();

		entity_stream_t * result = 0;

		STREAM_STORAGE__START_METHOD_WRAPPER(
				std_stream_storage_t::stream_get(id) )

		result = &lo_stream_get( id );

		STREAM_STORAGE__FINISH_METHOD_WRAPPER

		return *result;
	}

void 
std_stream_storage_t::lo_stream_create(
	const std::string & name )
	{
		// Потока с таким именем не должно существовать!
		if( m_dir.is_exists( name ) )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_stream_name_not_unique,
					name << ": stream is already in storage" )

		// Сначала нужно создать описатель потока в хранилище.
		// Затем нужно выделить этому потоку идентификатор.
		// Затем нужно связать идентификатор и описатель вместе с
		// именем в оглавлении хранилища.
		// Только после этого можно будет создавать объект entity_stream.
		chain_id_t description_chain = create_entity_stream_description(
				*m_storage );
		oess_1::uint_t id = m_dir.allocate_id();
		m_dir.add( name, id, description_chain );

		m_streams[ id ] = ref_entity_stream_t(
				create_std_entity_stream(
						*m_storage, id, description_chain ) );
	
		// Новое оглавление должно быть сохранено в хранилище.
		save_meta_info();
	}

void
std_stream_storage_t::lo_stream_destroy(
	const std::string & name )
	{
		oess_1::uint_t id;
		chain_id_t description_chain;

		{
			// Сохраняем информацию из оглавления, т.к. после удаления
			// описания из оглавления ее неоткуда будет взять.
			const stream_storage_dir_item_t & item = m_dir.item( name );
			description_chain = item.description_chain();
			id = item.id();
		}
   
		// Нужно уничтожить все содержимое потока.
		lo_stream_get( id ).entity_destroy_all();

		// Нужно изъять поток из всех списков.
		m_streams.erase( id );
		m_dir.destroy( name );

		// Нужно уничтожить поток в хранилище.
		destroy_entity_stream_description(
				*m_storage, description_chain );
	
		// Новое оглавление должно быть сохранено в хранилище.
		save_meta_info();
	}

entity_stream_t &
std_stream_storage_t::lo_stream_get(
	const oess_1::uint_t & id )
	{
		entity_stream_map_t::iterator it = m_streams.find( id );
		if( it == m_streams.end() )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_stream_is_not_in_directory,
					id << ": stream with this id not found" )

		return *( it->second );
	}

void
std_stream_storage_t::ensure_all_right() const
	{
		if( !m_is_ok )
			OESS_THROW_PHYSIC(
					oess_1::db::storage::err::c_storage_damage_possible,
					"std_stream_storage_t::m_is_ok != true; "
					"may be storage damaged in previous calls" )
	}

void
std_stream_storage_t::parse_meta_info(
	oess_1::io::istream_t & meta_info_stream )
	{
		oess_1::stdsn::ient_std_t ient( meta_info_stream );
		ient >> m_dir;

		create_entity_stream_objects();

		m_is_ok = true;
	}

void
std_stream_storage_t::create_entity_stream_objects()
	{
		std::set< std::string > stream_names = m_dir.names();
		for( std::set< std::string >::iterator it = stream_names.begin(),
				it_end = stream_names.end();
				it != it_end; ++it )
			{
				const stream_storage_dir_item_t & dir_item =
						m_dir.item( *it );

				m_streams[ dir_item.id() ] = ref_entity_stream_t(
						create_std_entity_stream(
								*m_storage, dir_item.id(),
								dir_item.description_chain() ) );
			}
	}

void
std_stream_storage_t::reload_meta_info_on_rollback()
	{
		// Объекты-потоки будут пересозданны заново.
		m_streams.clear();

		// Поднимаем мета-информацию из хранилища и парсим ее заново.
		oess_1::io::mem_buf_t meta_stream;
		m_storage->chain_load( m_meta_info_id,
				meta_stream, no_chain_length_limit );

		meta_stream.set_pos( 0 );
		parse_meta_info( meta_stream );
	}

void
std_stream_storage_t::save_meta_info()
	{
		update_in_storage( *m_storage, m_meta_info_id, m_dir );
	}

} /* namespace impl */

//
// create_stream_storage
//

OESS_1__DB__FUNC(stream_storage_t *)
create_stream_storage(
	const std::string & physic_name,
	const config_t & config )
	{
		// Сначала создаем пустой объект stream_storage_dir_t, который
		// будет использоваться в качестве начальной мета-информации при
		// создании chain_storage. Затем создаем chain_storage и
		// stream_storage.
		impl::stream_storage_dir_t empty_dir;
		oess_1::io::mem_buf_t empty_dir_stream;
		oess_1::stdsn::oent_std_t oent_empty_dir( empty_dir_stream );
		oent_empty_dir << empty_dir;
		empty_dir_stream.set_pos( 0 );

		chain_id_t meta_info_id;
		std::auto_ptr< chain_storage_t > chain_storage(
				create_chain_storage( physic_name, config,
						empty_dir_stream,	empty_dir_stream.size(),
						meta_info_id ) );

		empty_dir_stream.set_pos( 0 );
		return new impl::std_stream_storage_t(
				physic_name,
				chain_storage,
				meta_info_id, empty_dir_stream );
	}

//
// open_stream_storage
//

OESS_1__DB__FUNC(stream_storage_t *)
open_stream_storage(
	const std::string & physic_name,
	const config_t & config,
	bool is_read_only,
	bool is_auto_repair_enabled )
	{
		// Открываем chain_stream и считываем мета-информацию.
		// Затем создаем объект stream_storage и передаем ему
		// полученную мета-информацию.
		oess_1::io::mem_buf_t dir_stream;

		chain_id_t meta_info_id;
		std::auto_ptr< chain_storage_t > chain_storage(
				open_chain_storage( physic_name, config,
						is_read_only, is_auto_repair_enabled,
						dir_stream, no_chain_length_limit,
						meta_info_id ) );
	
		dir_stream.set_pos( 0 );
		return new impl::std_stream_storage_t(
				physic_name,
				chain_storage,
				meta_info_id, dir_stream );
	}

//
// is_stream_storage_exists
//

OESS_1__DB__FUNC(bool)
is_stream_storage_exists(
	const std::string & physic_name )
	{
		return is_chain_storage_exists( physic_name );
	}

//
// destroy_stream_storage
//

OESS_1__DB__FUNC(void)
destroy_stream_storage(
	const std::string & physic_name )
	{
		destroy_chain_storage( physic_name );
	}

} /* namespace storage */

} /* namespace db */

} /* namespace oess_1 */
