/*

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 <fstream>
#include <stdexcept>
#include <cerrno>
#include <cstring>

// Подключаем из-за проблем с Borland C++ 5.6.
#include <errno.h>

#include <cls_2/h/cls.hpp>
#include <cls_2/h/tag_no_value.hpp>
#include <cls_2/h/def_tag_formatter.hpp>
#include <cls_2/scalar/h/tag_scalar.hpp>
#include <cls_2/scalar/h/one_of.hpp>
#include <cls_2/std_cpp/h/utils.hpp>

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

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

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

namespace oess_1 {

namespace db {

namespace storage {

//
// hard_config_t
//

hard_config_t::hard_config_t()
	:	m_page_size( default_page_size )
	,	m_chain_link_size( default_chain_link_size )
	,	m_trx_delta_block_size( default_trx_delta_block_size )
	,	m_trace_file_page_size( default_trace_file_page_size )
	,	m_trace_mode( default_trace_mode )
	{}

hard_config_t::hard_config_t(
	oess_1::uint_t page_size,
	oess_1::uint_t chain_link_size,
	oess_1::uint_t trx_delta_block_size,
	oess_1::uint_t trace_file_page_size,
	trace_mode_t trace_mode )
	:	m_page_size( default_page_size )
	,	m_chain_link_size( default_chain_link_size )
	,	m_trx_delta_block_size( default_trx_delta_block_size )
	,	m_trace_file_page_size( default_trace_file_page_size )
	,	m_trace_mode( default_trace_mode )
	{
		// Проверку значений page_size и chain_link_size произведет
		// page_index_calculator.
		page_index_calculator_t page_size_checker(
				page_size, chain_link_size );

		/*!
			\par Ограничения на \a trx_delta_block_size.
			- должен быть больше нуля, но меньше размера страницы;
			- размер страницы должен делиться на \a trx_delta_block_size
			нацело;
			- в странице должно быть не более 8 delta-блоков.
		*/
		if( !trx_delta_block_size ||
				trx_delta_block_size > page_size )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					trx_delta_block_size << ": invalid trx_delta_block_size; "
					"must be > 0 and <= " << page_size )
		if( page_size % trx_delta_block_size )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					"page_size must be multiply of trx_delta_block_size; "
					"page_size: " << page_size << ", trx_delta_block_size: "
					<< trx_delta_block_size )
		if( page_size / trx_delta_block_size > 8 )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					"must be at most than 8 trx_delta_blocks on page; "
					"page_size: " << page_size << ", trx_delta_block_size: "
					<< trx_delta_block_size )

		/*!
			\par Ограничения на \a trace_file_page_size:
			- больше нуля и меньше или равно 64K;
			- кратно 128 байтам.
		*/
		if( !trace_file_page_size ||
				trace_file_page_size > 64u * 1024 )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					trace_file_page_size << ": invalid trace_file_page_size; "
					"must be > 0 and <= " << 64u * 1024 )
		if( trace_file_page_size % 128 )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					"trace_file_page_size must be multiply of 128; "
					"trace_file_page_size: " << trace_file_page_size )

		// Режим сохранения восстановочной информации должен быть
		// нам известен.
		if( trace_no != trace_mode &&
				trace_last != trace_mode &&
				trace_all != trace_mode )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_trace_mode,
					static_cast< unsigned int >( trace_mode )
					<< ": invalid or unsupported trace mode" )

		m_page_size = page_size;
		m_chain_link_size = chain_link_size;
		m_trx_delta_block_size = trx_delta_block_size;
		m_trace_file_page_size = trace_file_page_size;
		m_trace_mode = trace_mode;
	}
	
hard_config_t::hard_config_t(
	const hard_config_t & o )
	{
		(*this) = o;
	}

hard_config_t::~hard_config_t()
	{}

hard_config_t &
hard_config_t::operator=( const hard_config_t & o )
	{
		if( &o != this )
			{
				base_type_t::operator=( o );

				m_page_size = o.m_page_size;
				m_chain_link_size = o.m_chain_link_size;
				m_trx_delta_block_size = o.m_trx_delta_block_size;
				m_trace_file_page_size = o.m_trace_file_page_size;
				m_trace_mode = o.m_trace_mode;
			}

		return *this;
	}

oess_1::uint_t
hard_config_t::page_size() const
	{
		return m_page_size;
	}

oess_1::uint_t
hard_config_t::chain_link_size() const
	{
		return m_chain_link_size;
	}

oess_1::uint_t
hard_config_t::trx_delta_block_size() const
	{
		return m_trx_delta_block_size;
	}

oess_1::uint_t
hard_config_t::trace_file_page_size() const
	{
		return m_trace_file_page_size;
	}

trace_mode_t
hard_config_t::trace_mode() const
	{
		return m_trace_mode;
	}

//
// config_t
//

config_t::config_t()
	:	m_cache_size( default_cache_size )
	,	m_trace_file_size_limit( default_trace_file_size_limit )
	,	m_trace_stream_buffered_pages( default_trace_stream_buffered_pages )
	,	m_trace_compression( default_trace_stream_compression )
	{}

config_t::config_t(
	const hard_config_t & hard_config )
	:	m_hard_config( hard_config )
	,	m_cache_size( default_cache_size )
	,	m_trace_file_size_limit( default_trace_file_size_limit )
	,	m_trace_stream_buffered_pages( default_trace_stream_buffered_pages )
	,	m_trace_compression( default_trace_stream_compression )
	{}

config_t::~config_t()
	{}

void
config_t::set_hard_config(
	const hard_config_t & v )
	{
		m_hard_config = v;
	}

oess_1::uint_t
config_t::page_size() const
	{
		return m_hard_config.page_size();
	}

oess_1::uint_t
config_t::chain_link_size() const
	{
		return m_hard_config.chain_link_size();
	}

oess_1::uint_t
config_t::trx_delta_block_size() const
	{
		return m_hard_config.trx_delta_block_size();
	}

oess_1::uint_t
config_t::trace_file_page_size() const
	{
		return m_hard_config.trace_file_page_size();
	}

trace_mode_t
config_t::trace_mode() const
	{
		return m_hard_config.trace_mode();
	}

oess_1::uint_t
config_t::cache_size() const
	{
		return m_cache_size;
	}

void
config_t::set_cache_size( oess_1::uint_t v )
	{
		if( !v )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					"cache size cannot be 0" )

		m_cache_size = v;
	}

oess_1::uint_t
config_t::trace_file_size_limit() const
	{
		return m_trace_file_size_limit;
	}

void
config_t::set_trace_file_size_limit( oess_1::uint_t v )
	{
		if( !v )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					"trace_file_size_limit cannot be 0" )

		m_trace_file_size_limit = v;
	}

oess_1::uint_t
config_t::trace_stream_buffered_pages() const
	{
		return m_trace_stream_buffered_pages;
	}

void
config_t::set_trace_stream_buffered_pages( oess_1::uint_t v )
	{
		if( !v )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_sizes,
					"trace_stream_buffered_pages cannot be 0" )

		m_trace_stream_buffered_pages = v;
	}

trace_compression_mode_t
config_t::trace_compression() const
	{
		return m_trace_compression;
	}

void
config_t::set_trace_compression(
	const trace_compression_mode_t & v )
	{
		if( trace_no_compression != v &&
				trace_zlib_compression != v )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_invalid_trace_compression_mode,
					static_cast< oess_1::uint_t >( v )
					<< ": trace compression mode unknown" )

		m_trace_compression = v;
	}

std::string
config_t::make_cfg_file_name(
	const std::string & physic_name ) const
	{
		return physic_name + ".oess.cfg";
	}

std::string
config_t::make_main_file_name(
	const std::string & physic_name ) const
	{
		return physic_name + ".oess.dat";
	}

std::string
config_t::make_unclear_file_name(
	const std::string & physic_name ) const
	{
		return physic_name + ".oess.unclear";
	}

std::string
config_t::make_tmp_main_file_name(
	const std::string & physic_name ) const
	{
		return physic_name + ".oess.tmp";
	}

std::string
config_t::make_tmp_unclear_file_name(
	const std::string & physic_name ) const
	{
		return physic_name + ".oess.tmp.unclear";
	}

#include "config.ddl.cpp"

namespace impl
{

//
// tag_hard_config_t
//

/*!
	\since v.1.4.0
	\brief Класс тега {hard_config} конфигурации БД.

	Формат:
\verbatim
{hard-config*
  {page-size <uint>}
  {chain-link-size <uint>}
  {trx-delta-block-size <uint>}
  {trace-file-page-size <uint>}
  {trace-mode <str:"no"|"last"|"all">}
}
\endverbatim
*/
class	tag_hard_config_t
	:	public cls_2::tag_no_value_t
	{
		typedef cls_2::tag_no_value_t	base_type_t;
	public :
		//! Конструктор для случая загрузки конфигурации.
		tag_hard_config_t(
			cls_2::tag_t & owner )
			:	base_type_t( owner, "hard-config*", true, true )
			,	m_page_size( self_tag(), "page-size", true )
			,	m_chain_link_size( self_tag(), "chain-link-size", true )
			,	m_trx_delta_block_size(
					self_tag(), "trx-delta-block-size", true )
			,	m_trace_file_page_size(
					self_tag(), "trace-file-page-size", true )
			,	m_trace_mode(
					self_tag(), "trace-mode", true )
			{
				m_trace_mode_constraint.add( trace_mode_no_name() );
				m_trace_mode_constraint.add( trace_mode_last_name() );
				m_trace_mode_constraint.add( trace_mode_all_name() );
				m_trace_mode.set_constraint( &m_trace_mode_constraint );
			}
		//! Конструктор для случая сохранения конфигурации.
		/*!
			Сразу делает тег определенным.
		*/
		tag_hard_config_t(
			cls_2::tag_t & owner,
			const hard_config_t & cfg )
			:	base_type_t( owner, "hard-config*", true, true )
			,	m_page_size( self_tag(), "page-size", true )
			,	m_chain_link_size( self_tag(), "chain-link-size", true )
			,	m_trx_delta_block_size(
					self_tag(), "trx-delta-block-size", true )
			,	m_trace_file_page_size(
					self_tag(), "trace-file-page-size", true )
			,	m_trace_mode(
					self_tag(), "trace-mode", true )
			{
				m_page_size.make_defined( cfg.page_size() );
				m_chain_link_size.make_defined( cfg.chain_link_size() );
				m_trx_delta_block_size.make_defined(
						cfg.trx_delta_block_size() );
				m_trace_file_page_size.make_defined(
						cfg.trace_file_page_size() );

				const std::string * trace_mode = &trace_mode_no_name();
				if( trace_last == cfg.trace_mode() )
					trace_mode = &trace_mode_last_name();
				else if( trace_all == cfg.trace_mode() )
					trace_mode = &trace_mode_all_name();
				m_trace_mode.make_defined( *trace_mode );

				make_defined();
			}

		//! Получить разобранное значение.
		hard_config_t
		query_value() const
			{
				trace_mode_t trace_mode = trace_no;
				if( trace_mode_last_name() == m_trace_mode.query_value() )
					trace_mode = trace_last;
				else if( trace_mode_all_name() == m_trace_mode.query_value() )
					trace_mode = trace_all;

				return hard_config_t(
						m_page_size.query_value(),
						m_chain_link_size.query_value(),
						m_trx_delta_block_size.query_value(),
						m_trace_file_page_size.query_value(),
						trace_mode );
			}

	private :
		//! Значение тега {page-size}.
		cls_2::tag_scalar_t< oess_1::uint_t >	m_page_size;
		//! Значение тега {chain-link-size}.
		cls_2::tag_scalar_t< oess_1::uint_t >	m_chain_link_size;
		//! Значение тега {trx-delta-block-size}.
		cls_2::tag_scalar_t< oess_1::uint_t >	m_trx_delta_block_size;
		//! Значение тега {trace-file-page-size}.
		cls_2::tag_scalar_t< oess_1::uint_t >	m_trace_file_page_size;
		//! Значение тега {trace-mode}.
		cls_2::tag_scalar_t< std::string >	m_trace_mode;
		//! Ограничения для тега {trace-mode}.
		cls_2::scalar_constraint::one_of_t< std::string >
				m_trace_mode_constraint;

	//! \name Имена для режимов сохранения восстановочной информации.
	//! \{
		static const std::string &
		trace_mode_no_name()
			{
				static std::string name = "no";
				return name;
			}
		static const std::string &
		trace_mode_last_name()
			{
				static std::string name = "last";
				return name;
			}
		static const std::string &
		trace_mode_all_name()
			{
				static std::string name = "all";
				return name;
			}
	//! \}
	};

//
// tag_config_t
//

/*!
	\since v.1.4.0
	\brief Класс тега {storage-config} конфигурации БД.

	Формат:
\verbatim
{storage-config
	{hard-config* <tag_hard_config_t>}
	{cache-size <uint>}
	{trace-file-size-limit <uint>}
	{trace-stream-buffered-pages <uint>}
	{trace-compression <str:("no"|"zlib")>}
}
\endverbatim
*/
class	tag_config_t
	:	public cls_2::tag_no_value_t
	{
		typedef cls_2::tag_no_value_t	base_type_t;
	public :
		//! Конструктор для случая загрузки конфигурации.
		tag_config_t()
			:	base_type_t( "storage-config", true, true )
			,	m_hard_config( self_tag() )
			,	m_cache_size( self_tag(), "cache-size", true )
			,	m_trace_file_size_limit(
					self_tag(), "trace-file-size-limit", true )
			,	m_trace_stream_buffered_pages(
					self_tag(), "trace-stream-buffered-pages", true )
			,	m_trace_compression(
					self_tag(), "trace-compression", true )
			{
				m_trace_compression_constraint.add(
						trace_no_compression_name() );
				m_trace_compression_constraint.add(
						trace_zlib_compression_name() );
				m_trace_compression.set_constraint(
						&m_trace_compression_constraint );
			}

		//! Конструктор для случая записи конфигурации.
		/*!
			Сразу делает тег определенным.
		*/
		tag_config_t(
			const config_t & cfg )
			:	base_type_t( "storage-config", true, true )
			,	m_hard_config( self_tag(),
					hard_config_t(
							cfg.page_size(),
							cfg.chain_link_size(),
							cfg.trx_delta_block_size(),
							cfg.trace_file_page_size(),
							cfg.trace_mode() ) )
			,	m_cache_size( self_tag(), "cache-size", true )
			,	m_trace_file_size_limit(
					self_tag(), "trace-file-size-limit", true )
			,	m_trace_stream_buffered_pages(
					self_tag(), "trace-stream-buffered-pages", true )
			,	m_trace_compression(
					self_tag(), "trace-compression", true )
			{
				m_cache_size.make_defined(
						cfg.cache_size() );
				m_trace_file_size_limit.make_defined(
						cfg.trace_file_size_limit() );
				m_trace_stream_buffered_pages.make_defined(
						cfg.trace_stream_buffered_pages() );

				const std::string * trace_compression_name =
						&trace_no_compression_name();
				if( trace_zlib_compression == cfg.trace_compression() )
					trace_compression_name = &trace_zlib_compression_name();
				m_trace_compression.make_defined( *trace_compression_name );

				make_defined();
			}

		//! Получить разобранное значение.
		config_t
		query_value() const
			{
				config_t cfg( m_hard_config.query_value() );
				cfg.set_cache_size( m_cache_size.query_value() );
				cfg.set_trace_file_size_limit(
					m_trace_file_size_limit.query_value() );
				cfg.set_trace_stream_buffered_pages(
					m_trace_stream_buffered_pages.query_value() );

				trace_compression_mode_t trace_compression =
						trace_no_compression;
				if( trace_zlib_compression_name() ==
						m_trace_compression.query_value() )
					trace_compression = trace_zlib_compression;
				cfg.set_trace_compression( trace_compression );

				return cfg;
			}

	private :
		//! Значение тега {hard-config*}.
		tag_hard_config_t	m_hard_config;
		//! Значение тега {cache-size}.
		cls_2::tag_scalar_t< oess_1::uint_t >	m_cache_size;
		//! Значение тега {trace-file-size-limit}.
		cls_2::tag_scalar_t< oess_1::uint_t >	m_trace_file_size_limit;
		//! Значение тега {trace-stream-buffered-pages}.
		cls_2::tag_scalar_t< oess_1::uint_t >	m_trace_stream_buffered_pages;
		//! Значение тега {trace-compression}.
		cls_2::tag_scalar_t< std::string >	m_trace_compression;
		//! Ограничения для {trace-compression}.
		cls_2::scalar_constraint::one_of_t< std::string >
				m_trace_compression_constraint;

	//! \name Названия режимов компрессии trace-stream.
	//! \{
		static const std::string &
		trace_no_compression_name()
			{
				static std::string name( "no" );
				return name;
			}

		static const std::string &
		trace_zlib_compression_name()
			{
				static std::string name( "zlib" );
				return name;
			}
	//! \}
	};

} /* namespace impl */

//
// create_cfg_file
//

OESS_1__DB__FUNC(void)
create_cfg_file(
	const std::string & file_name,
	const config_t & config )
	{
		std::ofstream out_file( file_name.c_str(), std::ios::out );
		if( !out_file )
			OESS_THROW_LOGIC(
					oess_1::db::storage::err::c_unexpected,
					file_name << ": unable to create storage configuration file; "
					"errno: " << errno << ", strerror: "
					<< std::strerror( errno ) )

		impl::tag_config_t tag_cfg( config );

		cls_2::def_tag_formatter_t< std::ostream > formatter( out_file );
		tag_cfg.format( formatter );
	}

//
// load_cfg_file
//

OESS_1__DB__FUNC(config_t)
load_cfg_file(
	const std::string & file_name )
	{
		impl::tag_config_t tag_cfg;
		std::string error_stream;

		try
			{
				if( cls_2::c_ok != cls_2::std_cpp::parse_file(
						file_name, tag_cfg, error_stream ) )
					throw std::runtime_error( error_stream );

				return tag_cfg.query_value();
			}
		catch( const std::exception & x )
			{
				OESS_THROW_PHYSIC(
						oess_1::db::storage::err::c_unexpected,
						file_name << ": unable to parse configuration file; "
						"nested exception: '" << x.what() << "'" )
			}

		return config_t();
	}

} /* namespace storage */

} /* namespace db */

} /* namespace oess_1 */

