/*

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 <cstdio>
#include <cstring>
#include <numeric>

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

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

namespace oess_1 {

namespace db {

namespace storage {

namespace trx {

//
// delta_info_t
//

delta_info_t::delta_info_t()
	:	m_index( 0 )
	{}

delta_info_t::delta_info_t(
	oess_1::uint_t index,
	const std::string & value )
	:	m_index( index )
	,	m_value( value )
	{}

delta_info_t::~delta_info_t()
	{}

oess_1::uint_t
delta_info_t::index() const
	{
		return m_index;
	}

const std::string &
delta_info_t::value() const
	{
		return m_value;
	}

// Этот оператор необходим для корректной работы
// page_info_t::summary_delta_length.
oess_1::uint_t
operator+( oess_1::uint_t a, const delta_info_shptr_t & b )
	{
		return ( a + b->value().length() );
	}

//
// page_info_t
//

page_info_t::page_info_t()
	:	m_ordinal( 0 )
	{}

page_info_t::page_info_t(
	oess_1::uint_t ordinal )
	:	m_ordinal( ordinal )
	{}

page_info_t::~page_info_t()
	{}

oess_1::uint_t
page_info_t::ordinal() const
	{
		return m_ordinal;
	}

void
page_info_t::add(
	const delta_info_shptr_t & delta )
	{
		m_deltas.push_back( delta );
	}

const delta_info_list_t &
page_info_t::deltas() const
	{
		return m_deltas;
	}

oess_1::uint_t
page_info_t::summary_delta_length() const
	{
		oess_1::uint_t sum = 0;
		return std::accumulate( m_deltas.begin(), m_deltas.end(), sum );
	}

// Этот оператор необходим для корректной работы
// trace_stream_item_t::summary_length().
oess_1::uint_t
operator+( oess_1::uint_t a, const page_info_t & b )
	{
		return ( a + b.summary_delta_length() );
	}

//
// trace_stream_item_t
//

trace_stream_item_t::trace_stream_item_t()
	{}

trace_stream_item_t::trace_stream_item_t(
	oess_1::uint_t main_file_length )
	:	m_main_file_length( main_file_length )
	{}

trace_stream_item_t::~trace_stream_item_t()
	{}

oess_1::uint_t
trace_stream_item_t::main_file_length() const
	{
		return m_main_file_length;
	}

void
trace_stream_item_t::add(
	const page_info_t & page )
	{
		m_pages.push_back( page );
	}

const page_info_list_t &
trace_stream_item_t::pages() const
	{
		return m_pages;
	}

oess_1::uint_t
trace_stream_item_t::summary_length() const
	{
		oess_1::uint_t sum = 0;
		return std::accumulate( m_pages.begin(), m_pages.end(), sum );
	}

//
// image_header_t
//

image_header_t::image_header_t()
	:	m_compression_mode( trace_no_compression )
	,	m_uncompressed_size( 0 )
	,	m_compressed_size( 0 )
	{}

image_header_t::image_header_t(
	const compression_mode_t & compression,
	oess_1::uint_t uncompressed_size,
	oess_1::uint_t compressed_size )
	:	m_compression_mode( compression )
	,	m_uncompressed_size( uncompressed_size )
	,	m_compressed_size( compressed_size )
	{}

image_header_t::~image_header_t()
	{}

const image_header_t::compression_mode_t &
image_header_t::compression_mode() const
	{
		return m_compression_mode;
	}

oess_1::uint_t
image_header_t::uncompressed_size() const
	{
		return m_uncompressed_size;
	}

oess_1::uint_t
image_header_t::compressed_size() const
	{
		return m_compressed_size;
	}

#include "types.ddl.cpp"

//
// make_item.
//

/*!
	\since v.1.4.0
	\brief Функтор, который вычисляет изменения на
		одной странице.
*/
class	page_delta_calculator_t
	:	public oess_1::db::storage::dirty_page_actor_t
	{
	private :
		//! Приемник формируемых описаний.
		trace_stream_item_t &	m_receiver;
		//! Размер блока для поиска изменений.
		const oess_1::uint_t	m_block_size;

		//! Поиск изменений на данной странице.
		void
		search_deltas(
			const oess_1::char_t * current,
			//! Не может быть 0.
			const oess_1::char_t * previous,
			oess_1::uint_t page_size,
			oess_1::uint_t block_size,
			//! Приемник найденых изменений.
			page_info_t & page_info );

	public :
		page_delta_calculator_t(
			trace_stream_item_t & receiver,
			oess_1::uint_t block_size )
			:	m_receiver( receiver )
			,	m_block_size( block_size )
			{}
		virtual ~page_delta_calculator_t()
			{}

		virtual void
		operator()(
			oess_1::uint_t page_ordinal,
			const oess_1::char_t * page,
			const oess_1::char_t * source,
			oess_1::uint_t page_size );
	};

void
page_delta_calculator_t::search_deltas(
	const oess_1::char_t * current,
	const oess_1::char_t * previous,
	oess_1::uint_t page_size,
	oess_1::uint_t block_size,
	page_info_t & page_info )
	{
		oess_1::uint_t block_count = page_size / block_size;
		oess_1::uint_t block_index = 0;

		// Позиция, на которой было найдено последнее несовпадение.
		oess_1::uint_t last_mismatch_index = block_count;
		// Размер последнего несовпадающего фрагмента.
		oess_1::uint_t last_mismatch_size = 0;

		for( ; block_index != block_count;
				++block_index,
				previous += block_size )
			{
				if( std::memcmp(
						current + block_index * block_size,
						previous, block_size ) )
					{
						// Несовпадение найдено. Может быть это
						// продолжение уже найденого несовпадения?
						if( !last_mismatch_size )
							// Нет, это новое несовпадение.
							last_mismatch_index = block_index;

						last_mismatch_size += block_size;
					}
				else
					{
						// Этот фрагмент совпал. Если до этого был незавершенный
						// несовпадающий фрагмент, то его нужно завершить.
						if( last_mismatch_size )
							{
								page_info.add(
										delta_info_shptr_t( new delta_info_t(
												last_mismatch_index,
												std::string(
														current +
																last_mismatch_index *
																block_size,
														last_mismatch_size ) ) ) );
							}

						last_mismatch_index = block_count;
						last_mismatch_size = 0;
					}
			}

		// Если остался фрагмет, то его нужно не забыть.
		if( last_mismatch_size )
			{
				page_info.add(
						delta_info_shptr_t( new delta_info_t(
								last_mismatch_index,
								std::string(
										current + last_mismatch_index *
												block_size,
										last_mismatch_size ) ) ) );
			}
	}

void
page_delta_calculator_t::operator()(
	oess_1::uint_t page_ordinal,
	const oess_1::char_t * page,
	const oess_1::char_t * source,
	oess_1::uint_t page_size )
	{
		if( page_size % m_block_size )
			// Нельзя искать несовпадения, т.к. размер страницы
			// не кратен размеру одного блока.
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::
							c_trx_invalid_mismatch_block_size,
					"page size must be multiple of block size; "
					"page_size: " << page_size
					<< ", block_size: " << m_block_size )

		page_info_t page_info( page_ordinal );

		// Если предыдущего значения у страницы не было...
		// ... или размер дельты равен размеру страницы...
		if( !source || m_block_size == page_size )
			{
				// То тогда вся страница является delt-ой
				page_info.add(
						delta_info_shptr_t( new delta_info_t( 0,
								std::string( page, page_size ) ) ) );
			}
		else
			// В противном случае изменения нужно искать.
			search_deltas( page, source, page_size,
					m_block_size, page_info );

		// Если были изменения, то страница должна быть записана.
		// В противном случае она игнорируется.
		if( page_info.deltas().size() )
			m_receiver.add( page_info );
	}

OESS_1__DB__FUNC(void)
make_item(
	dirty_page_storage_t & storage,
	const config_t & config,
	trace_stream_item_t & receiver )
	{
		page_delta_calculator_t calculator( receiver,
				config.trx_delta_block_size() );
		storage.for_each( calculator );
	}

//
// snapshot_collection_t
//

snapshot_collection_t::~snapshot_collection_t()
	{}

//
// committer_t
//

committer_t::~committer_t()
	{}

//
// trace_stream_t
//

trace_stream_t::~trace_stream_t()
	{}

//
// trace_file_extension
//

OESS_1__DB__FUNC(std::string)
trace_file_extension()
	{
		return ".trace";
	}

//
// create_trace_file_name
//

OESS_1__DB__FUNC(std::string)
create_trace_file_name(
	const std::string & physic_name,
	trace_file_number_t trace_file_number )
	{
		char sz[ 32 ];
		std::sprintf( sz, "%08x", trace_file_number );

		return physic_name + "." + sz + ".trace";
	}

//
// snapshot_file_extension
//

OESS_1__DB__FUNC(std::string)
snapshot_file_extension()
	{
		return ".snapshot";
	}

//
// create_trace_file_name
//

OESS_1__DB__FUNC(std::string)
create_snapshot_file_name(
	const std::string & physic_name,
	trace_file_number_t snapshot_file_number )
	{
		char sz[ 32 ];
		std::sprintf( sz, "%08x", snapshot_file_number );

		return physic_name + "." + sz + ".snapshot";
	}

} /* namespace trx */

} /* namespace storage */

} /* namespace db */

} /* namespace oess */

