/*

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.

*/

/*
	ObjESSty-1
*/

/*!
	\file
	\since v.1.4.0
	\brief Индекс не уникальных элементов загруженного в ОП раздела БД.
*/

#if !defined( _OESS_1__DB__CLN__SLICE_NONUNIQUE_INDEX_HPP_ )
#define _OESS_1__DB__CLN__SLICE_NONUNIQUE_INDEX_HPP_

#include <map>
#include <iterator>

#include <oess_1/db/h/declspec.hpp>

#include <oess_1/db/cln/h/slice_index_base.hpp>

namespace oess_1
{

namespace db
{

namespace cln
{

/*!
	\since v.1.4.0
	\brief Индекс не уникальных элементов загруженного в ОП раздела БД.
*/
template<
	// Класс элемента раздела.
	class Item,
	// Класс ключа элемента.
	class Key,
	// Класс извлекателя ключа.
	class Key_extractor >
class    slice_nonunique_index_t :
	public slice_index_base_t
{
private :
	//! Тип карты соотвествия между ключем объекта и
	//! идентификатором объекта в БД.
	typedef std::multimap< Key, oess_1::ent_id_t >
		key_map_t;

	//! Тип карты соответствия между идентификатором объектов
	//! и ключами объектов.
	typedef std::map< oess_1::ent_id_t, Key >
		id_map_t;

	//! Образ раздела, с которым производится работа.
	slice_image_iface_t< Item > *	m_slice;

	//! Карта соответствия значений ключей и идентификаторов объектов.
	key_map_t	m_keys;
	//! Карта соответствия идентификаторов объектов ключам.
	id_map_t	m_ids;

	//! Извлекатель ключей из объектов.
	Key_extractor	m_key_extractor;

public :
	//! Класс итератора.
	class	iterator
		:	public std::iterator<
				std::forward_iterator_tag,
				Item >
	{
		private :
			//! Псевдоним типа используемого итератора.
			typedef typename key_map_t::iterator it_t;

			//! Итератор в карте ключей.
			it_t	m_it;

			//! Интерфейс образа раздела, через который
			//! будут извлекаться значения объектов.
			slice_image_iface_t< Item > *	m_slice;

			//! Значение текущего объекта.
			/*! Имеет корректное значение только, если
				было хотя бы одно обращение к
				operator*() или operator->() и после этого
				итератор не перемещался. */
			auto_ptr_3::soft_obj_ptr_t< Item >	m_value;

		public :
			iterator()
			:
				m_slice( 0 )
			{}
			iterator(
				slice_image_iface_t< Item > * slice,
				const it_t & it )
			:
				m_it( it )
			,	m_slice( slice )
			{}
			iterator(
				const iterator & o )
			:	m_it( o.m_it )
			,	m_slice( o.m_slice )
			{}
			~iterator()
			{}

			iterator &
			operator=(
				const iterator & o )
			{
				iterator t( o );
				swap( t );
				return *this;
			}

			void
			swap( iterator & o )
			{
				std::swap( m_it, o.m_it );
				std::swap( m_slice, o.m_slice );
				m_value.swap( o.m_value );
			}

			reference
			operator*()
			{
				if( !m_value.get() )
					// Нужно загружать объект.
					m_slice->get( m_it->second, m_value );
				return *m_value;
			}

			pointer
			operator->()
			{
				if( !m_value.get() )
					// Нужно загружать объект.
					m_slice->get( m_it->second, m_value );
				return m_value.get();
			}

			iterator &
			operator++()
			{
				m_value.reset();
				++m_it;
				return *this;
			}

			iterator
			operator++(int)
			{
				m_value.reset();
				return iterator( m_slice, m_it++ );
			}

			bool
			operator==( const iterator & o ) const
			{
				return ( m_it == o.m_it );
			}

			bool
			operator!=( const iterator & o ) const
			{
				return ( m_it != o.m_it );
			}

			const oess_1::ent_id_t &
			id() const
			{
				return m_it->second;
			}
	};

	//! Класс константного итератора.
	class	const_iterator
		:	public std::iterator<
				std::forward_iterator_tag,
				Item,
				ptrdiff_t,
				const Item *,
				const Item & >
	{
		private :
			//! Псевдоним типа используемого итератора.
			typedef typename key_map_t::const_iterator it_t;

			//! Итератор в карте ключей.
			it_t	m_it;

			//! Интерфейс образа раздела, через который
			//! будут извлекаться значения объектов.
			slice_image_iface_t< Item > *	m_slice;

			//! Значение текущего объекта.
			/*! Имеет корректное значение только, если
				было хотя бы одно обращение к
				operator*() или operator->() и после этого
				итератор не перемещался. */
			auto_ptr_3::soft_obj_ptr_t< Item >	m_value;

		public :
			typedef const Item *	pointer;
			typedef const Item &	reference;

			const_iterator()
			:
				m_slice( 0 )
			{}
			const_iterator(
				slice_image_iface_t< Item > * slice,
				const it_t & it )
			:
				m_it( it )
			,	m_slice( slice )
			{}
			const_iterator(
				const const_iterator & o )
			:	m_it( o.m_it )
			,	m_slice( o.m_slice )
			{}
			~const_iterator()
			{}

			const_iterator &
			operator=(
				const const_iterator & o )
			{
				const_iterator t( o );
				swap( t );
				return *this;
			}

			void
			swap( const_iterator & o )
			{
				std::swap( m_it, o.m_it );
				std::swap( m_slice, o.m_slice );
				m_value.swap( o.m_value );
			}

			reference
			operator*()
			{
				if( !m_value.get() )
					// Нужно загружать объект.
					m_slice->get( m_it->second, m_value );
				return *m_value;
			}

			pointer
			operator->()
			{
				if( !m_value.get() )
					// Нужно загружать объект.
					m_slice->get( m_it->second, m_value );
				return m_value.get();
			}

			const_iterator &
			operator++()
			{
				m_value.reset();
				++m_it;
				return *this;
			}

			const_iterator
			operator++(int)
			{
				m_value.reset();
				return const_iterator( m_slice, m_it++ );
			}

			bool
			operator==( const const_iterator & o ) const
			{
				return ( m_it == o.m_it );
			}

			bool
			operator!=( const const_iterator & o ) const
			{
				return ( m_it != o.m_it );
			}

			const oess_1::ent_id_t &
			id() const
			{
				return m_it->second;
			}
	};

public:
	//! Инициализирующий конструктор, который
	//! сразу привязывает индекс к образу раздела.
	slice_nonunique_index_t(
		slice_image_iface_t< Item > & slice,
		//! Извлекатель ключей из объекта.
		const Key_extractor & key_extractor,
		//! Набор битовых флагов, которые будут
		//! переданы в метод slice_image_base_t::attach().
		unsigned int attach_flags = 0 )
	:
		m_slice( &slice )
	,	m_key_extractor( key_extractor )
	{
		m_slice->attach( *this, attach_flags );
	}
	virtual ~slice_nonunique_index_t()
	{}

	//! Первый ключ индекса.
	iterator
	begin()
	{
		return iterator( m_slice, m_keys.begin() );
	}

	//! Первый ключ индекса.
	const_iterator
	begin() const
	{
		return const_iterator( m_slice, m_keys.begin() );
	}

	//! Последний ключ индекса.
	iterator
	end()
	{
		return iterator( m_slice, m_keys.end() );
	}

	//! Последний ключ индекса.
	const_iterator
	end() const
	{
		return const_iterator( m_slice, m_keys.end() );
	}

	//! Найти объект по ключу.
	/*!
		Возвращает указатель за последний элемент, если
		искомый элемент не найден.
	*/
	iterator
	find( const Key & o )
	{
		return iterator( m_slice, m_keys.find( o ) );
	}

	//! Найти объект по ключу.
	/*!
		Возвращает указатель за последний элемент, если
		искомый элемент не найден.
	*/
	const_iterator
	find( const Key & o ) const
	{
		return const_iterator( m_slice, m_keys.find( o ) );
	}

	//! Найти нижнюю границу.
	/*! Возвращается итератор на первый элемент индекса,
		который не меньше указанного ключа.

		Если такого элемента нет, возвращается end(). */
	iterator
	lower_bound( const Key & o )
	{
		return iterator( m_slice, m_keys.lower_bound( o ) );
	}

	//! Найти нижнюю границу.
	/*! Возвращается итератор на первый элемент индекса,
		который не меньше указанного ключа.

		Если такого элемента нет, возвращается end(). */
	const_iterator
	lower_bound( const Key & o ) const
	{
		return const_iterator( m_slice, m_keys.lower_bound( o ) );
	}

	//! Найти верхнюю границу.
	/*! Возвращается итератор на первый элемент индекса,
		который строго больше указанного ключа.

		Если такого элемента нет, возвращается end(). */
	iterator
	upper_bound( const Key & o )
	{
		return iterator( m_slice, m_keys.upper_bound( o ) );
	}

	//! Найти нижнюю границу.
	/*! Возвращается итератор на первый элемент индекса,
		который строго больше указанного ключа.

		Если такого элемента нет, возвращается end(). */
	const_iterator
	upper_bound( const Key & o ) const
	{
		return const_iterator( m_slice, m_keys.upper_bound( o ) );
	}

	//! Удаление объекта из индекса и из раздела.
	/*!
		Удаление объекта оказывает воздействие на
		все активные индексы текущего раздела.
	*/
	void
	erase( const iterator & it )
	{
		if( it != end() )
		{
			// Здесь нужно обязательно сохранить копию id.
			// Поскольку далее везде используется ссылка на
			// id, то нельзя передавать ссылку, возвращаемую
			// методом it.id(). Ведь итератор указывает на
			// объект, который будет удален!
			oess_1::ent_id_t id_for_erasing = it.id();
			m_slice->erase( id_for_erasing );
		}
	}

	//! Удаление объекта из индекса и из раздела.
	/*!
		Удаляются все объекты, владеющие данным ключем.

		Удаление объекта оказывает воздействие на
		все активные индексы текущего раздела.
	*/
	void
	erase( const Key & o )
	{
		// Используется самая простая и, вероятно, не эффективная
		// реализация: метод erase(it) вызывается до тех пор, пока
		// в индексе есть элементы с указанным ключем.
		iterator it = find( o );
		while( it != end() )
			{
				erase( it );
				it = find( o );
			}
	}

	//! Возвращает количество элементов в индексе.
	unsigned int
	size() const
	{
		return m_keys.size();
	}

/*! \name Методы, предназначенные для slice_image_base_t. */
/*! \{ */

	/*! Обработчик очистки содержимого индекса в ОП. */
	virtual void
	on_clear()
	{
		m_keys.clear();
		m_ids.clear();
	}

	/*! Обработчик добавления объекта в раздел.
		Так же вызывается для каждого объекта раздела при
		связывании индекса с образом раздела.
	*/
	virtual void
	on_insert(
		const oess_1::ent_id_t & id,
		const oess_1::stdsn::serializable_t & o )
	{
		const Item & real_obj = dynamic_cast< const Item & >( o );
		Key k( m_key_extractor( real_obj ) );

		insert_pair( k, id );
		m_ids[ id ] = k;
	}

	/*! Обработчик удаления объекта из раздела. */
	virtual void
	on_erase(
		const oess_1::ent_id_t & id )
	{
		typename id_map_t::iterator it_id( m_ids.find( id ) );
		if( it_id != m_ids.end() )
		{
			remove_pair( it_id->second, id );
			m_ids.erase( it_id );
		}
	}

	/*! Обработчик изменения значения объекта в разделе. */
	virtual void
	on_update(
		const oess_1::ent_id_t & id,
		const oess_1::stdsn::serializable_t & o )
	{
		typename id_map_t::iterator it_id( m_ids.find( id ) );
		if( it_id != m_ids.end() )
		{
			const Item & real_obj = dynamic_cast< const Item & >( o );
			Key k( m_key_extractor( real_obj ) );

			if( k < it_id->second || k > it_id->second )
				{
					remove_pair( it_id->second, id );
					insert_pair( k, id );
					it_id->second = k;
				}
		}
	}

	/*! Обработчик связывания индекса с образом раздела. */
	virtual void
	on_attach()
	{
		// Ничего не нужно делать.
	}

	/*! Обработчик отвязывания индекса от образа раздела. */
	virtual void
	on_detach()
	{
		// Очищаем всю информацию.
		m_keys.clear();
		m_ids.clear();
		m_slice = 0;
	}

/*! \} */

private :
	//! Добавить значение в карту значений.
	void
	insert_pair(
		const Key & k,
		const oess_1::ent_id_t & id )
		{
			m_keys.insert( key_map_t::value_type( k, id ) );
		}

	//! Удалить значение из карты значений с учетом идентификатора объекта.
	void
	remove_pair(
		const Key & k,
		const oess_1::ent_id_t & id )
		{
			for( key_map_t::iterator it = m_keys.lower_bound( k ),
					it_end = m_keys.upper_bound( k );
					it != it_end; ++it )
				if( it->second == id )
					{
						m_keys.erase( it );
						return;
					}
		}
};

} /* namespace cln */

} /* namespace db */

} /* namespace oess_1 */

#endif

