/*

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 <map>
#include <vector>
#include <algorithm>

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

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

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

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

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

namespace oess_1 {

namespace db {

namespace storage {

namespace impl {

//
// header_processor_t
//

header_processor_t::~header_processor_t()
	{}

//
// page_use_timer_t
//

/*!
	\since v.1.4.0
	\brief Счетчик использования страницы хранилища.
*/
struct	page_use_timer_t
	{
		//! Номер страницы, для которой сохраняется время.
		oess_1::uint_t	m_page;
		//! Время последнего обращения к странице.
		oess_1::uint_t	m_access_time;

		page_use_timer_t()
			:	m_page( 0 )
			,	m_access_time( 0 )
			{}
		//! Инициализирующий конструктор.
		page_use_timer_t(
			oess_1::uint_t page,
			oess_1::uint_t access_time )
			:	m_page( page )
			,	m_access_time( access_time )
			{}

		//! Доступ к номеру страницы.
		oess_1::uint_t
		page() const
			{
				return m_page;
			}

		//! Оператор стравнения. Сравнивается только время доступа.
		bool
		operator<( const page_use_timer_t & o ) const
			{
				return ( m_access_time < o.m_access_time );
			}
	};

//
// std_header_processor_t
//

/*!
	\since v.1.4.0
	\brief Штатная реализация интерфейса header_processor_t.
*/
class	std_header_processor_t
	:	public header_processor_t
	{
	public :
		//! Инициализирующий конструктор.
		std_header_processor_t(
			//! Конфигурация хранилища.
			const config_t & config,
			//! Интерфейс доступа к значениям страниц.
			page_accessor_t & accessor,
			//! true, если должны использоваться только read-only операции.
			bool is_read_only );
		virtual ~std_header_processor_t();

	//! \name Реализация унаследованных методов.
	//! \{
		chain_id_t
		allocate_block();

		void
		deallocate_block(
			const chain_id_t & block );

		chain_id_t
		get_next(
			const chain_id_t & block );

		void
		change_next(
			const chain_id_t & block,
			const chain_id_t & next );

		void
		operation_finished( bool is_read_only );
	//! \}

	private :
		//! Конфигурация хранилища.
		const config_t &	m_config;
		//! Размеры хранилища.
		const page_index_calculator_t	m_calculator;
		//! Интерфейс для доступа к значениям страниц.
		page_accessor_t & m_page_accessor;
		//! true, если разрешены только read-only операции.
		bool	m_is_read_only;

		//! Загруженное значение общего заголовка.
		/*!
			Содержит 0, если общий заголовок не был загружен.
		*/
		ref_common_header_t	m_common_header;

		//! Тип карты заголовков областей.
		/*!
			Ключем является номер страницы, на которой располагается
			заголовок области.
		*/
		typedef std::map< oess_1::uint_t, ref_area_header_t >
				area_header_map_t;

		//! Карта загруженных заголовков областей.
		area_header_map_t	m_areas;

		//! Вспомогательный буфер для выполнения операций чтения/записи
		//! заголовков областей.
		area_header_t::io_buffer_t	m_area_header_io_buffer;

		//! Тип карты заголовков сегментов.
		/*!
			Ключем является номер страницы, на которой располагается
			заголовок области.
		*/
		typedef std::map< oess_1::uint_t, ref_segment_header_t >
				segment_header_map_t;

		//! Карта загруженных заголовков сегментов.
		segment_header_map_t	m_segments;

		//! Вспомогательный буфер для выполнения операций чтения/записи
		//! заголовков сегментов.
		segment_header_t::io_buffer_t	m_segment_header_io_buffer;

		//! Таймер, который отсчитывает время доступа к страницам.
		oess_1::uint_t	m_timer;

		//! Тип карты для отсчета времен доступа к страницам.
		/*!
			Ключем является номер страницы, а значением -- время последнего
			доступа к странице.
		*/
		typedef std::map< oess_1::uint_t, oess_1::uint_t >
				page_use_map_t;

		//! Карта для отсчета времен доступа к страницам.
		page_use_map_t	m_page_usage;

		//! Получить общий заголовок.
		/*!
			Если общий заголовок еще не загружен, то он загружается
			(или создается в хранилище, если хранилище пусто).
		*/
		common_header_t &
		get_common_header();

		//! Получить заголовок указанной области.
		/*!
			Если этот заголовок еще не загружен, то он загружается или
			создается, если его еще не было в хранилище.
		*/
		area_header_t &
		get_area_header(
			const area_ordinal_t & area );

		//! Получить заголовок указанного сегмента.
		/*!
			Если этот заголовок еще не загружен, то он загружается или
			создается, если его еще не было в хранилище.
		*/
		segment_header_t &
		get_segment_header(
			const area_ordinal_t & area,
			const segment_ordinal_t & segment );

		//! Зафиксировать в хранилище общий заголовок.
		/*! Так же сбрасывается признак модифицированности объекта. */
		void
		commit_common_header(
			common_header_t & header );

		//! Зафиксировать в хранилище заголовок области.
		/*! Так же сбрасывается признак модифицированности объекта. */
		void
		commit_area_header(
			area_header_t & header );

		//! Зафиксировать в хранилище заголовок сегмента.
		/*! Так же сбрасывается признак модифицированности объекта. */
		void
		commit_segment_header(
			segment_header_t & header );

		//! Обновить время использования для страницы.
		void
		update_page_usage(
			oess_1::uint_t page );

		//! Вытолкнуть из памяти те страницы, которые давно не
		//! использовались.
		/*!
			Если количество всех загруженных заголовков превышает
			1/4 от размера кэша, то из памяти выгружаются все лишние
			давно не использовавшиеся страницы.
		*/
		void
		reduce_memory_use();
	};

std_header_processor_t::std_header_processor_t(
	const config_t & config,
	page_accessor_t & accessor,
	bool is_read_only )
	:	m_config( config )
	,	m_calculator( config.page_size(), config.chain_link_size() )
	,	m_page_accessor( accessor )
	,	m_is_read_only( is_read_only )
	,	m_timer( 0 )
	,	m_area_header_io_buffer( m_calculator )
	,	m_segment_header_io_buffer( m_calculator )
	{}

std_header_processor_t::~std_header_processor_t()
	{}

chain_id_t
std_header_processor_t::allocate_block()
	{
		if( m_is_read_only )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_read_only_mode,
					"write-access to headers denied in read-only mode" )

		// Получаем свободный блок.
		common_header_t & common_header = get_common_header();
		area_ordinal_t free_area = common_header.get_free_area();
		area_header_t & area_header = get_area_header( free_area );
		segment_ordinal_t free_segment = area_header.get_free_segment();
		segment_header_t & segment_header = get_segment_header(
				free_area, free_segment );
		chain_id_t free_block = segment_header.allocate_block();

		// Если в сегменте больше нет свободных блоков, то сегмент
		// нужно вычеркнуть из общего заголовка.
		if( !segment_header.has_free_blocks() )
			{
				area_header.remove_free_segment( free_segment );
				if( !area_header.has_free_segments() )
					common_header.remove_free_area( free_area );
			}

		return free_block;
	}

void
std_header_processor_t::deallocate_block(
	const chain_id_t & block )
	{
		// Определяем область и сегмент, в котором расположен блок.
		area_ordinal_t area = m_calculator.chain_link_area( block );
		segment_ordinal_t segment = m_calculator.chain_link_segment( block );

		segment_header_t & segment_header = get_segment_header(
				area, segment );
		bool segment_has_free_blocks = segment_header.has_free_blocks();

		segment_header.deallocate_block( block );

		// Если до этого в сегменте не было пустых блоков, то
		// теперь один свободный блок появился и об этом нужно сказать
		// заголовку области.
		if( !segment_has_free_blocks )
			{
				area_header_t & area_header = get_area_header( area );
				bool area_has_free_segments = area_header.has_free_segments();

				area_header.return_free_segment( segment );

				// Если до этого в области не было пустых сегментов, то
				// теперь они появились и об этом нужно сказать
				// общему заголовку.
				if( !area_has_free_segments )
					{
						common_header_t & common_header = get_common_header();
						common_header.return_free_area( area );
					}
			}
	}

chain_id_t
std_header_processor_t::get_next(
	const chain_id_t & block )
	{
		area_ordinal_t block_area =
				m_calculator.chain_link_area( block );
		segment_ordinal_t block_segment =
				m_calculator.chain_link_segment( block );
		const segment_header_t & segment_header =
				get_segment_header( block_area, block_segment );

		return segment_header.next( block );
	}

void
std_header_processor_t::change_next(
	const chain_id_t & block,
	const chain_id_t & next )
	{
		if( m_is_read_only )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_read_only_mode,
					"write-access to headers denied in read-only mode" )

		area_ordinal_t block_area =
				m_calculator.chain_link_area( block );
		segment_ordinal_t block_segment =
				m_calculator.chain_link_segment( block );
		segment_header_t & segment_header =
				get_segment_header( block_area, block_segment );

		segment_header.change_next( block, next );
	}

void
std_header_processor_t::operation_finished( bool is_read_only )
	{
		if( !is_read_only )
			{
				if( m_common_header.query() && m_common_header->is_changed() )
					commit_common_header( *m_common_header );
   
				{
					for( area_header_map_t::iterator it = m_areas.begin(),
							it_end = m_areas.end();
							it != it_end; ++it )
						{
							if( it->second->is_changed() )
								commit_area_header( *( it->second ) );
						}
				}
				{
					for( segment_header_map_t::iterator it = m_segments.begin(),
							it_end = m_segments.end();
							it != it_end; ++it )
						{
							if( it->second->is_changed() )
								commit_segment_header( *( it->second ) );
						}
				}
			}

		// Может быть мы заняли слишком много памяти?
		reduce_memory_use();
	}

common_header_t &
std_header_processor_t::get_common_header()
	{
		oess_1::uint_t page = m_calculator.common_header_page();
		if( !m_common_header.query() )
			{
				// Заголовок нужно создавать или считывать.
				m_common_header = ref_common_header_t(
						new common_header_t( m_calculator ) );

				if( m_page_accessor.is_exists( page ) )
					{
						// Заголовок должен быть прочитан из существующей страницы.
						oess_1::io::ifixed_mem_buf_t istream(
								m_page_accessor.read_access( page ),
								m_config.page_size() );
						istream >> *m_common_header;
					}
				else
					// Заголовок должен быть создан.
					m_common_header->init_new();
			}

		update_page_usage( page );

		return *m_common_header;
	}


area_header_t &
std_header_processor_t::get_area_header(
	const area_ordinal_t & area )
	{
		oess_1::uint_t page = m_calculator.area_header_page( area );
		area_header_t * result = 0;

		area_header_map_t::iterator it = m_areas.find( page );
		if( it != m_areas.end() )
			result = it->second.query();
		else
			{
				// Заголовок нужно либо прочитать, либо создать.
				ref_area_header_t area_header( new area_header_t(
						m_calculator, area ) );
				if( m_page_accessor.is_exists( page ) )
					{
						// Заголовок должен быть прочитан из существующей страницы.
						oess_1::io::ifixed_mem_buf_t istream(
								m_page_accessor.read_access( page ),
								m_config.page_size() );
						area_header->read_from( istream, m_area_header_io_buffer );
					}
				else
					// Заголовок должен быть создан.
					area_header->init_new();

				m_areas[ page ] = area_header;

				result = area_header.query();
			}
	
		update_page_usage( page );

		return *result;
	}

segment_header_t &
std_header_processor_t::get_segment_header(
	const area_ordinal_t & area,
	const segment_ordinal_t & segment )
	{
		oess_1::uint_t page = m_calculator.segment_header_page(
				area, segment );

		segment_header_t * result = 0;

		segment_header_map_t::iterator it = m_segments.find( page );
		if( it != m_segments.end() )
			result = it->second.query();
		else
			{
				// Заголовок нужно либо прочитать, либо создать.
				ref_segment_header_t segment_header( new segment_header_t(
						m_calculator, area, segment ) );
				if( m_page_accessor.is_exists( page ) )
					{
						// Заголовок должен быть прочитан из существующей страницы.
						oess_1::io::ifixed_mem_buf_t istream(
								m_page_accessor.read_access( page ),
								m_config.page_size() );
						segment_header->read_from( istream,
								m_segment_header_io_buffer );
					}
				else
					// Заголовок должен быть создан.
					segment_header->init_new();
            
				m_segments[ page ] = segment_header;

				result = segment_header.query();
			}

		update_page_usage( page );

		return *result;
	}

void
std_header_processor_t::commit_common_header(
	common_header_t & header )
	{
		oess_1::char_t * page_value = m_page_accessor.write_access(
				m_calculator.common_header_page() );
		std::fill( page_value, page_value + m_config.page_size(), '\0' );

		oess_1::io::ofixed_mem_buf_t ostream( page_value,
				m_config.page_size() );
		ostream << header;

		header.force_not_changed();
	}

void
std_header_processor_t::commit_area_header(
	area_header_t & header )
	{
		oess_1::char_t * page_value = m_page_accessor.write_access(
				m_calculator.area_header_page(
						header.area_ordinal() ) );
		std::fill( page_value, page_value + m_config.page_size(), '\0' );

		oess_1::io::ofixed_mem_buf_t ostream( page_value,
				m_config.page_size() );
		header.write_to( ostream, m_area_header_io_buffer );

		header.force_not_changed();
	}

void
std_header_processor_t::commit_segment_header(
	segment_header_t & header )
	{
		oess_1::char_t * page_value = m_page_accessor.write_access(
				m_calculator.segment_header_page(
						header.area_ordinal(),
						header.segment_ordinal() ) );
		std::fill( page_value, page_value + m_config.page_size(), '\0' );

		oess_1::io::ofixed_mem_buf_t ostream( page_value,
				m_config.page_size() );
		header.write_to( ostream, m_segment_header_io_buffer );

		header.force_not_changed();
	}

void
std_header_processor_t::update_page_usage(
	oess_1::uint_t page )
	{
		m_page_usage[ page ] = ++m_timer;
	}

/*!
	Вычислить номера страниц, которые подлежат изъятию.

	\return вектор номеров страниц, упорядоченный по возрастанию.
*/
static std::vector< oess_1::uint_t >
detect_pages_to_throw_out(
	//! Статистика использования страниц.
	const std::map< oess_1::uint_t, oess_1::uint_t > & statistics,
	//! Количество страниц для изъятия.
	oess_1::uint_t pages_to_throw )
	{
		//! Для вычисления просто формируем вектор структур
		//! page_use_timer_t и сортируем его. Затем берем
		//! \a pages_to_throw первых элементов и переносим их
		//! в результирующий вектор. Затем результирующий
		//! вектор сортируется.
		std::vector< page_use_timer_t > pages_with_times;
		pages_with_times.reserve( statistics.size() );

		unsigned int i = 0;
		for( std::map< oess_1::uint_t, oess_1::uint_t >::const_iterator
				it = statistics.begin(), it_end = statistics.end();
				it != it_end; ++it )
			pages_with_times.push_back( page_use_timer_t(
					it->first, it->second ) );

		std::sort( pages_with_times.begin(), pages_with_times.end() );

		std::vector< oess_1::uint_t > result;
		result.reserve( pages_to_throw );

		for( i = 0; i != pages_to_throw; ++i )
			result.push_back( pages_with_times[ i ].page() );

		std::sort( result.begin(), result.end() );

		return result;
	}

/*!
	\return true, если страница входит в число подлежащих
		изъятию страниц.
*/
static bool
is_must_be_throw_out(
	//! Результат работы функции detect_pages_to_throw_out().
	const std::vector< oess_1::uint_t > & pages_to_throw,
	//! Страница, которую нужно вычеркнуть.
	oess_1::uint_t page )
	{
		return std::binary_search( pages_to_throw.begin(),
				pages_to_throw.end(), page );
	}

void
std_header_processor_t::reduce_memory_use()
	{
		oess_1::uint_t required_capacity = m_config.cache_size() / 4;
		if( m_page_usage.size() > required_capacity )
			{
				// Нужно выбрасывать лишние страницы.
				// Определяем, что это за страницы.
				oess_1::uint_t pages_to_throw_count =
						m_page_usage.size() - required_capacity;
				if( pages_to_throw_count < required_capacity / 2 )
					pages_to_throw_count = required_capacity / 2;

				std::vector< oess_1::uint_t > pages_to_throw =
						detect_pages_to_throw_out(
								m_page_usage, pages_to_throw_count );

				// Выбрасываем их.
				if( is_must_be_throw_out( pages_to_throw,
						m_calculator.common_header_page() ) )
					{
						m_page_usage.erase( m_calculator.common_header_page() );
						m_common_header = ref_common_header_t();
					}
   
				// В остальном расчитываем на то, что из map можно безопасно
				// удалять элемент по ключу, которого в map нет.
				for( unsigned int i = 0; i != pages_to_throw.size(); ++i )
					{
						m_areas.erase( pages_to_throw[ i ] );
						m_segments.erase( pages_to_throw[ i ] );
						m_page_usage.erase( pages_to_throw[ i ] );
					}
			}
	}

//
// create_std_header_processor
//

std::auto_ptr< header_processor_t >
create_std_header_processor(
	const config_t & config,
	page_accessor_t & accessor,
	bool is_read_only )
	{
		return std::auto_ptr< header_processor_t >(
				new std_header_processor_t(
						config, accessor, is_read_only ) );
	}

} /* namespace impl */

} /* namespace storage */

} /* namespace db */

} /* namespace oess_1 */

