/*

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.

*/

/*!
	\file
	\since v.1.4.0
	\brief Реализация интерфейса кэша основного файла хранилища.
*/

#include <vector>
#include <algorithm>

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

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

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

namespace oess_1 {

namespace db {

namespace storage {

namespace impl {

std_cache_t::std_cache_t()
	:	m_max_size( 0 )
	,	m_page_size( 0 )
	,	m_timer( 0 )
	,	m_last_accessed_page_index( 0 )
	,	m_last_accessed_page( 0 )
	{}

std_cache_t::~std_cache_t()
	{}

void
std_cache_t::reinit(
	oess_1::uint_t cache_size,
	oess_1::uint_t page_size )
	{
		cleanup();

		m_max_size = cache_size;
		m_page_size = page_size;
	}

void
std_cache_t::cleanup()
	{
		m_pages.clear();

		m_max_size = 0;
		m_page_size = 0;
		m_timer = 0;

		m_last_accessed_page_index = 0;
		m_last_accessed_page = 0;
	}

const oess_1::char_t *
std_cache_t::read_access(
	oess_1::uint_t ordinal )
	{
		if( !m_last_accessed_page || m_last_accessed_page_index != ordinal )
			{
				page_map_t::iterator it = m_pages.find( ordinal );
				if( it == m_pages.end() )
					return 0;
   
				m_last_accessed_page_index = ordinal;
				m_last_accessed_page = &( it->second );
			}

		return m_last_accessed_page->current( ++m_timer );
	}

oess_1::char_t *
std_cache_t::write_access(
	oess_1::uint_t ordinal )
	{
		if( !m_last_accessed_page || m_last_accessed_page_index != ordinal )
			{
				page_map_t::iterator it = m_pages.find( ordinal );
				if( it == m_pages.end() )
					return 0;
   
				m_last_accessed_page_index = ordinal;
				m_last_accessed_page = &( it->second );
			}

		m_last_accessed_page->make_dirty();
		return m_last_accessed_page->current( ++m_timer );
	}

oess_1::char_t *
std_cache_t::push(
	const ref_loaded_page_t & page,
	dirty_page_storage_t & storage,
	bool is_read_only_access )
	{
		if( !m_max_size )
			OESS_THROW_LOGIC(
				oess_1::db::storage::err::c_cache_not_initialized,
				"reinit() must be called first" )

		if( m_max_size == m_pages.size() )
			push_out_some_page( storage );

		page_t & pushed_page = ( m_pages[ page->ordinal() ] = page_t(
			page, m_timer ) );

		if( !is_read_only_access )
			pushed_page.make_dirty();

		m_last_accessed_page_index = page->ordinal();
		m_last_accessed_page = &pushed_page;

		return pushed_page.current( ++m_timer );
	}

void
std_cache_t::copy_all_dirties(
	dirty_page_storage_t & storage )
	{
		for( page_map_t::iterator it = m_pages.begin(),
				it_end = m_pages.end();
				it != it_end; ++it )
			if( it->second.is_dirty() )
				storage.put( it->second.page() );
	}

void
std_cache_t::clear_all_dirties()
	{
		for( page_map_t::iterator it = m_pages.begin(),
				it_end = m_pages.end();
				it != it_end; ++it )
			if( it->second.is_dirty() )
				it->second.make_clear();
	}

void
std_cache_t::throw_away_all_dirties()
	{
		for( page_map_t::iterator it = m_pages.begin();
				it != m_pages.end(); )
			if( it->second.is_dirty() )
				{
					if( it->first == m_last_accessed_page_index )
						{
							m_last_accessed_page_index = 0;
							m_last_accessed_page = 0;
						}

					m_pages.erase( it++ );
				}
			else
				++it;
	}

//! Вспомогательное пространство имен.
namespace std_cache_aux
{

//
// page_use_timer_t
//

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

		page_use_timer_t()
			:	m_page( 0 )
			,	m_access_time( 0 )
			{}
		//! Инициализирующий конструктор.
		page_use_timer_t(
			oess_1::uint_t page,
			unsigned long 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 );
			}
	};

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

	\return вектор номеров страниц, упорядоченный по возрастанию.
*/
static std::vector< oess_1::uint_t >
detect_pages_to_push_out(
	//! Статистика использования страниц.
	const std_cache_t::page_map_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_cache_t::page_map_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.last_access_time() ) );

		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() );

		return result;
	}

} /* namespace std_cache_aux */

void
std_cache_t::push_out_some_page(
	dirty_page_storage_t & storage )
	{
		// Выталкиваем из кэша сразу половину страниц.
		std::vector< oess_1::uint_t > pages =
				std_cache_aux::detect_pages_to_push_out(
						m_pages, 3 * (m_max_size >> 2) );

		for( std::vector< oess_1::uint_t >::iterator
				it = pages.begin(), it_end = pages.end();
				it != it_end;
				++it )
			{
				const page_t & outgoing = m_pages[ *it ];
				if( outgoing.is_dirty() )
					storage.put( outgoing.page() );
					
				m_pages.erase( *it );
			}
	}

//
// create_std_cache
//

std::auto_ptr< cache_t >
create_std_cache()
	{
		return std::auto_ptr< cache_t >( new std_cache_t() );
	}

} /* namespace impl */

} /* namespace storage */

} /* namespace db */

} /* namespace oess_1 */

