/*

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.

*/

/*!
	\since v.1.4.0
	\file
	\brief Типы различных заголовков файла хранилища.
*/

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

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

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

namespace oess_1 {

namespace db {

namespace storage {

namespace impl {

//
// common_header_t
//

common_header_t::common_header_t(
	const page_index_calculator_t & calculator )
	:	m_calculator( calculator )
	,	m_is_changed( false )
	{}

area_ordinal_t
common_header_t::get_free_area() const
	{
		// Если есть элементы, то берем первый из них.
		if( !m_free_areas.size() )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_no_more_free_areas,
					"no more free areas" )

		return m_first_free_area;
	}

void
common_header_t::remove_free_area(
	const area_ordinal_t & area )
	{
		if( area >= m_calculator.area_count() )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_area_ordinal,
					area << ": invalid area ordinal;"
							"total area count: "
					<< m_calculator.area_count() )

		if( m_free_areas.end() == m_free_areas.find( area ) )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_area_already_full,
					area << ": area is not in list of free areas" )

		m_free_areas.erase( area );

		m_is_changed = true;

		if( m_first_free_area == area && m_free_areas.size() )
			m_first_free_area = *( m_free_areas.begin() );
	}

void
common_header_t::return_free_area(
	const area_ordinal_t & area )
	{
		if( area >= m_calculator.area_count() )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_area_ordinal,
					area << ": invalid area ordinal;"
							"total area count: "
					<< m_calculator.area_count() )

		if( m_free_areas.end() != m_free_areas.find( area ) )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_area_already_free,
					area << ": area is already in list of "
							"free areas" )

		if( m_free_areas.size() )
			{
				if( area < m_first_free_area )
					// Эта область станет первой свободной областью.
					m_first_free_area = area;
			}
		else
			// Это область должна стать первой свободной областью.
			m_first_free_area = area;

		m_free_areas.insert( area );

		m_is_changed = true;
	}

void
common_header_t::init_new()
	{
		m_free_areas.clear();

		for( area_ordinal_t i = 0;
				i != m_calculator.area_count();
				++i )
			m_free_areas.insert( i );

		m_first_free_area = 0;
		m_is_changed = true;
	}

oess_1::io::ibinstream_t &
common_header_t::read_from(
	oess_1::io::ibinstream_t & from )
	{
		m_free_areas.clear();
		m_is_changed = false;

		area_ordinal_t count;
		from >> count;

		// Для повышения эффективности попробуем читать не по одному
		// элементу из потока, а сразу все существующие в потоке элементы.
		if( count )
			{
				std::vector< area_ordinal_t > tmp_buffer( count, 0 );

				from.read( &tmp_buffer[ 0 ], count );
				m_free_areas.insert( &tmp_buffer[ 0 ],
						&tmp_buffer[ 0 ] + count );
   
				m_first_free_area = *( m_free_areas.begin() );
			}

		return from;
	}

oess_1::io::obinstream_t &
common_header_t::write_to(
	oess_1::io::obinstream_t & to ) const
	{
		area_ordinal_t count = static_cast< area_ordinal_t >(
				m_free_areas.size() );
		to << count;

		// Для повышения эффективности попробуем писать не по одному
		// элементу в поток, а сразу все существующие элементы.
		if( count )
			{
				std::vector< area_ordinal_t > tmp_buffer( count, 0 );

				std::copy( m_free_areas.begin(), m_free_areas.end(),
						&tmp_buffer[ 0 ] );

				to.write( &tmp_buffer[ 0 ], count );
			}

		return to;
	}

bool
common_header_t::is_changed() const
	{
		return m_is_changed;
	}

void
common_header_t::force_not_changed()
	{
		m_is_changed = false;
	}

bool
common_header_t::has_free_areas() const
	{
		return ( 0 != m_free_areas.size() );
	}

//
// ref_common_header_t
//

SMART_REF_3_IMPL_REF( common_header_t )

//
// area_header_t::io_buffer_t
//

area_header_t::io_buffer_t::io_buffer_t(
	const page_index_calculator_t & calculator )
	:	m_buffer( calculator.area_segment_count( 0 ), 0 )
	{}

area_header_t::io_buffer_t::~io_buffer_t()
	{}

//
// area_header_t
//

area_header_t::area_header_t(
	const page_index_calculator_t & calculator,
	const area_ordinal_t & area_ordinal )
	:	m_calculator( calculator )
	,	m_area_ordinal( area_ordinal )
	,	m_is_changed( false )
	{}

const area_ordinal_t &
area_header_t::area_ordinal() const
	{
		return m_area_ordinal;
	}

segment_ordinal_t
area_header_t::get_free_segment() const
	{
		// Если есть элементы, то берем первый из них.
		if( !m_free_segments.size() )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_no_more_free_segments,
					"no more free segments; area: " << area_ordinal() )

		return m_first_free_segment;
	}

void
area_header_t::remove_free_segment(
	const segment_ordinal_t & segment )
	{
		if( segment >= m_calculator.area_segment_count( area_ordinal() ) )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_segment_ordinal,
					segment << ": invalid segment ordinal;"
							"total segment count: "
					<< m_calculator.area_segment_count( area_ordinal() )
					<< "; area: " << area_ordinal() )

		if( m_free_segments.end() == m_free_segments.find( segment ) )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_segment_already_full,
					segment << ": segment is not in list of free segments"
					"; area: " << area_ordinal() )

		m_free_segments.erase( segment );

		if( segment == m_first_free_segment && m_free_segments.size() )
			// Нужно узнать, какой из свободных сегментов является первым.
			m_first_free_segment = *( m_free_segments.begin() );

		m_is_changed = true;
	}

void
area_header_t::return_free_segment(
	const segment_ordinal_t & segment )
	{
		if( segment >= m_calculator.area_segment_count( area_ordinal() ) )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_segment_ordinal,
					segment << ": invalid segment ordinal;"
							"total segment count: "
					<< m_calculator.area_segment_count( area_ordinal() ) 
					<< "; area: " << area_ordinal() )

		if( m_free_segments.end() != m_free_segments.find( segment ) )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_segment_already_free,
					segment << ": segment is already in list of "
							"free segments" 
					"; area: " << area_ordinal() )

		if( m_free_segments.size() )
			{
				if( segment < m_first_free_segment )
					// Теперь возвращенный сегмент будет первым свободным.
					m_first_free_segment = segment;
			}
		else
			// Возвращенный сегмент будет первым свободным.
			m_first_free_segment = segment;

		m_free_segments.insert( segment );

		m_is_changed = true;
	}

void
area_header_t::init_new()
	{
		m_free_segments.clear();

		for( segment_ordinal_t i = 0;
				i != m_calculator.area_segment_count( area_ordinal() );
				++i )
			m_free_segments.insert( i );

		m_first_free_segment = 0;

		m_is_changed = true;
	}

oess_1::io::ibinstream_t &
area_header_t::read_from(
	oess_1::io::ibinstream_t & from,
	io_buffer_t & io_buffer )
	{
		m_free_segments.clear();
		m_is_changed = false;

		segment_ordinal_t count;
		from >> count;

		// Для повышения эффективности попробуем читать не по одному
		// элементу из потока, а сразу все существующие в потоке элементы.
		if( count )
			{
				from.read( io_buffer.get(), count );
				m_free_segments.insert(
						io_buffer.get(), io_buffer.get() + count );
   
				m_first_free_segment = *( m_free_segments.begin() );
			}

		return from;
	}

oess_1::io::obinstream_t &
area_header_t::write_to(
	oess_1::io::obinstream_t & to,
	io_buffer_t & io_buffer ) const
	{
		segment_ordinal_t count = static_cast< segment_ordinal_t >(
				m_free_segments.size() );
		to << count;

		// Для повышения эффективности попробуем писать не по одному
		// элементу в поток, а сразу все существующие элементы.
		if( count )
			{
				std::copy( m_free_segments.begin(), m_free_segments.end(),
						io_buffer.get() );

				to.write( io_buffer.get(), count );
			}

		return to;
	}

bool
area_header_t::is_changed() const
	{
		return m_is_changed;
	}

void
area_header_t::force_not_changed()
	{
		m_is_changed = false;
	}

bool
area_header_t::has_free_segments() const
	{
		return ( 0 != m_free_segments.size() );
	}

//
// ref_area_header_t
//

SMART_REF_3_IMPL_REF( area_header_t )

//
// segment_header_t::io_buffer_t
//

segment_header_t::io_buffer_t::io_buffer_t(
	const page_index_calculator_t & calculator )
	:	m_buffer(
			( calculator.segment_page_count() - 1 ) *
					calculator.chain_link_on_page_count() *
					segment_header_item_t::uints_count,
			0 )
	{}

segment_header_t::io_buffer_t::~io_buffer_t()
	{}

//
// segment_header_t
//

segment_header_t::segment_header_t(
	const page_index_calculator_t & calculator,
	const area_ordinal_t & area_ordinal,
	const segment_ordinal_t & ordinal )
	:	m_calculator( calculator )
	,	m_area_ordinal( area_ordinal )
	,	m_segment_ordinal( ordinal )
	,	m_is_changed( false )
	,	m_total_block_count(
			calculator.segment_page_count() *
			calculator.chain_link_on_page_count() )
	,	m_useful_block_count(
			m_total_block_count - calculator.chain_link_on_page_count() )
	,	m_first_chain_id(
			calculator.first_chain_link_on_page(
					calculator.segment_header_page( area_ordinal, ordinal ) + 1 ) )
	,	m_last_chain_id( m_first_chain_id + m_useful_block_count - 1 )
	,	m_first_free_id( m_first_chain_id )
	,	m_free_count( m_useful_block_count )
	{
		m_items.reserve( m_useful_block_count );

		block_info_t empty_item( true, invalid_chain_id );
		m_items.resize( m_useful_block_count, empty_item );
	}

const area_ordinal_t &
segment_header_t::area_ordinal() const
	{
		return m_area_ordinal;
	}

const segment_ordinal_t &
segment_header_t::segment_ordinal() const
	{
		return m_segment_ordinal;
	}

chain_id_t
segment_header_t::allocate_block()
	{
		if( !m_free_count )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_segment_already_full,
					m_segment_ordinal << ": segment has no free item"
					<< "; area: " << area_ordinal() )

		// Этот элемент будет выделен.
		chain_id_t result = m_first_free_id;
		m_items[ item_index( result ) ].first = false;

		if( --m_free_count )
			{
				// Нужно найти следующий свободный элемент.
				// Делаем это простым перебором.
				for( unsigned int i = item_index( result ) + 1,
						i_max = m_useful_block_count;
						i != i_max;
						++i )
					if( m_items[ i ].first )
						{
							// Свободный элемент найден.
							m_first_free_id = m_first_chain_id + i;
							break;
						}
			}

		m_is_changed = true;

		return result;
	}

void
segment_header_t::deallocate_block(
	const chain_id_t & block_id )
	{
		unsigned int pos = item_index( block_id );
		if( m_items[ pos ].first )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_block_not_allocated,
					block_id << ": this block is not in use in segment: "
					<< m_segment_ordinal 
					<< "; area: " << area_ordinal() )

		m_items[ pos ].first = true;
		m_items[ pos ].second = invalid_chain_id;

		// Если свободных блоков не было, то это будет первый свободный блок.
		if( m_free_count )
			{
				// Если же свободные блоки были, то он может стать первым
				// свободным блоком.
				if( block_id < m_first_free_id )
					m_first_free_id = block_id;
			}
		else
			m_first_free_id = block_id;

		++m_free_count;
            	
		m_is_changed = true;
	}

chain_id_t
segment_header_t::next(
	const chain_id_t & block_id ) const
	{
		unsigned int pos = item_index( block_id );

		// Указанный блок должен быть среди занятых блоков.
		if( m_items[ pos ].first )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_block_not_allocated,
					block_id << ": this block is not in use in segment: "
					<< m_segment_ordinal
					<< "; area: " << area_ordinal() )

		return m_items[ pos ].second;
	}

void
segment_header_t::change_next(
	const chain_id_t & block_id,
	const chain_id_t & next )
	{
		unsigned int pos = item_index( block_id );

		// Указанный блок должен быть среди занятых блоков.
		if( m_items[ pos ].first )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_block_not_allocated,
					block_id << ": this block is not in use in segment: "
					<< m_segment_ordinal
					<< "; area: " << area_ordinal() )
		m_items[ pos ].second = next;

		m_is_changed = true;
	}

void
segment_header_t::init_new()
	{
		m_is_changed = true;

		block_info_t empty_item( true, invalid_chain_id );

		chain_id_t ordinal = m_first_chain_id;
		m_free_count = m_useful_block_count;
		m_first_free_id = m_first_chain_id;

		for( unsigned int i = 0; i != m_useful_block_count; ++i, ++ordinal )
			{
				m_items[ i ] = empty_item;
			}
	}

oess_1::io::ibinstream_t &
segment_header_t::read_from(
	oess_1::io::ibinstream_t & from,
	io_buffer_t & io_buffer )
	{
		m_is_changed = false;

		oess_1::uint_t count_buffer[ 2 ];
		from.read( count_buffer,
				sizeof( count_buffer ) / sizeof( count_buffer[ 0 ] ) );
		oess_1::uint_t & free_count = count_buffer[ 0 ];
		oess_1::uint_t & used_count = count_buffer[ 1 ];

		// Для повышения эффективности работаем через временный буфер.
		from.read( io_buffer.get(), io_buffer.size() );
		const oess_1::uint_t * f = io_buffer.get();

		if( 0 != ( m_free_count = free_count ) )
			{
				segment_header_item_t i;

				while( free_count )
					{
						f = ( f >> i );
   
						block_info_t & info = m_items[ item_index( i.self() ) ];
						info.first = true;
						info.second = invalid_chain_id;
   
						if( free_count == m_free_count )
							// Был прочитан первый элемент. Он и будет
							// первым свободным элементом в заголовке.
							m_first_free_id = i.self();
   
						--free_count;
					}
			}

		if( used_count )
			{
				segment_header_item_t i;

				while( used_count-- )
					{
						f = ( f >> i );
   
						block_info_t & info = m_items[ item_index( i.self() ) ];
						info.first = false;
						info.second = i.next();
					}
			}

		return from;
	}

oess_1::io::obinstream_t &
segment_header_t::write_to(
	oess_1::io::obinstream_t & to,
	io_buffer_t & io_buffer ) const
	{
		oess_1::uint_t count_buffer[ 2 ] = {
				m_free_count, m_useful_block_count - m_free_count
		};
		to.write( count_buffer,
				sizeof( count_buffer ) / sizeof( count_buffer[ 0 ] ) );

		// Для повышения эффективности работаем через временный буфер.
		// Сюда будут записываться свободные элементы.
		oess_1::uint_t * f = io_buffer.get();
		// А сюда -- занятые.
		oess_1::uint_t * u = io_buffer.get() +
				m_free_count * segment_header_item_t::uints_count;

		chain_id_t ordinal = m_first_chain_id;
		for( unsigned int i = 0, i_max = m_useful_block_count;
				i != i_max; ++i, ++ordinal )
			if( m_items[ i ].first )
				{
					segment_header_item_t item( ordinal );
					f = ( f << item );
				}
			else
				{
					segment_header_item_t item( ordinal, m_items[ i ].second );
					u = ( u << item );
				}

		to.write( io_buffer.get(), io_buffer.size() );

		return to;
	}

bool
segment_header_t::is_changed() const
	{
		return m_is_changed;
	}

void
segment_header_t::force_not_changed()
	{
		m_is_changed = false;
	}

bool
segment_header_t::has_free_blocks() const
	{
		return ( 0 != m_free_count );
	}

unsigned int
segment_header_t::item_index( const chain_id_t & id ) const
	{
		if( id < m_first_chain_id || m_last_chain_id < id )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_block_not_allocated,
					id << ": this block is not in use in segment: "
					<< m_segment_ordinal
					<< ", area: " << area_ordinal()
					<< ", segment range: [" << m_first_chain_id
					<< "," << m_last_chain_id << "]" )

		return id - m_first_chain_id;
	}

//
// ref_segment_header_t
//

SMART_REF_3_IMPL_REF( segment_header_t )

} /* namespace impl */

} /* namespace storage */

} /* namespace db */

} /* namespace oess_1 */

