/*
 * $Id$
 *
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. 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.

    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.
 */

#ifndef _QSE_CMN_ARRAY_HPP_
#define _QSE_CMN_ARRAY_HPP_

/// \file
/// Provide the Array class.

#include <qse/Growable.hpp>
#include <qse/cmn/Mmged.hpp>

/////////////////////////////////
QSE_BEGIN_NAMESPACE(QSE)
/////////////////////////////////

template <typename T>
struct ArrayPositioner
{
	void operator() (T& v, qse_size_t index) const
	{
		// do nothing
	}
};

struct ArrayResizer
{
	qse_size_t operator() (qse_size_t current, const GrowthPolicy* gp) const
	{
		if (current <= 0) current = 1;

		if (gp)
		{
			return gp->getNewSize (current);
		}
		else
		{
			return (current < 5000)?   (current + current):
			       (current < 50000)?  (current + (current / 2)):
			       (current < 100000)? (current + (current / 4)):
			       (current < 150000)? (current + (current / 8)):
			                           (current + (current / 16));
		}
	}
};

///
/// The Array class provides a dynamically resized array.
///
/// With C++11, the Array class move-contructs values in various context.
/// The move constructor of the value must not raise an exception.
/// 
template <typename T, typename POSITIONER = ArrayPositioner<T>, typename RESIZER = ArrayResizer>
class Array: public Mmged, public Growable
{
public:
	typedef Array<T,POSITIONER,RESIZER> SelfType;

	typedef ArrayPositioner<T> DefaultPositioner;
	typedef ArrayResizer DefaultResizer;

	enum 
	{
		DEFAULT_CAPACITY = 128,
		INVALID_INDEX = ~(qse_size_t)0
	};

	void init_array (int capacity)
	{
		if (capacity <= 0) 
		{
			this->buffer = QSE_NULL;
			this->capacity = 0;
		}
		else 
		{
			//this->buffer = new T[capacity];
			this->buffer = (T*)::operator new (capacity * QSE_SIZEOF(*this->buffer), this->getMmgr());
			this->capacity = capacity;
		}

		this->count  = 0;
	}

public:
	Array (Mmgr* mmgr = QSE_NULL): Mmged(mmgr)
	{
		this->init_array (DEFAULT_CAPACITY);
	}

	Array (qse_size_t capacity, Mmgr* mmgr = QSE_NULL): Mmged(mmgr)
	{
		this->init_array (capacity);
	}

	Array (const SelfType& array): Mmged(array.getMmgr()), count(0), capacity(0), buffer(QSE_NULL)
	{
		if (array.buffer)
		{
			this->buffer = this->clone_buffer (array.buffer, array.capacity, array.count);
			this->count = array.count;
			this->capacity = array.capacity;
		}
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	Array (SelfType&& array): Mmged(array.getMmgr()), count(0), capacity(0), buffer(QSE_NULL)
	{
		if (array.buffer)
		{
			this->buffer = array.buffer;
			this->count = array.count;
			this->capacity = array.capacity;

			array.buffer = QSE_NULL;
			array.count = 0;
			array.capacity = 0;
		}
	}

#endif

	~Array ()
	{
		this->clear (true);
	}

	SelfType& operator= (const SelfType& array)
	{
		if (this != &array)
		{
			this->clear (true);
			if (array.buffer)
			{
				this->buffer = this->clone_buffer (array, array.capacity, array.count);
				this->count = array.count;
				this->capacity = array.capacity;
			}
		}
		return *this;
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	SelfType& operator= (SelfType&& array)
	{
		if (this != &array)
		{
			// since 'array' is an rvalue, i know it's going to be destroyed.
			// it should be safe to destroy all my items here first instead of
			// arranging to swap with items of 'array'.
			this->clear (true);

			if (array.buffer)
			{
				// TODO: should i swap items if mmgrs are differnt
				//       between *this and array?

				this->setMmgr (array.getMmgr());  // copy over mmgr.

				this->buffer = array.buffer;
				this->count = array.count;
				this->capacity = array.capacity;

				// since i cleared all items in the existing array,
				// i can simply do the followings.
				array.buffer = QSE_NULL;
				array.count = 0;
				array.capacity = 0;
			}
		}
		return *this;
	}
#endif

protected:
	T* clone_buffer (const T* srcbuf, qse_size_t capa, qse_size_t cnt)
	{
		QSE_ASSERT (capa > 0);
		QSE_ASSERT (cnt <= capa);

		qse_size_t index;

		//T* tmp = new T[capa];
		//T* tmp = (T*)::operator new (capa * QSE_SIZEOF(*tmp), this->getMmgr());
		T* tmp = (T*)this->getMmgr()->allocate(capa * QSE_SIZEOF(*tmp));

		try 
		{
			for (index = 0; index < cnt; index++) 
			{
				// copy-construct each element. 
				new((QSE::Mmgr*)QSE_NULL, &tmp[index]) T(srcbuf[index]);
				this->_positioner (tmp[index], index);
			}
		}
		catch (...) 
		{
			// in case copy-constructor raises an exception.
			QSE_ASSERT (tmp != QSE_NULL);
			while (index > 0)
			{
				--index;
				this->_positioner (tmp[index], INVALID_INDEX);
				tmp[index].T::~T ();
			}

			//::operator delete (tmp, this->getMmgr());
			this->getMmgr()->dispose (tmp);

			throw;
		}

		return tmp;
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	T* clone_buffer_by_moving (T* srcbuf, qse_size_t capa, qse_size_t cnt)
	{
		QSE_ASSERT (capa > 0);
		QSE_ASSERT (cnt <= capa);

		qse_size_t index;

		//T* tmp = new T[capa];
		//T* tmp = (T*)::operator new (capa * QSE_SIZEOF(*tmp), this->getMmgr());
		T* tmp = (T*)this->getMmgr()->allocate(capa * QSE_SIZEOF(*tmp));

		try 
		{
			for (index = 0; index < cnt; index++) 
			{
				// move-construct(or copy-construct) each element. 
				new((QSE::Mmgr*)QSE_NULL, &tmp[index]) T(QSE_CPP_RVREF(srcbuf[index]));
				this->_positioner (tmp[index], index);
			}
		}
		catch (...) 
		{
			// in case move-constructor(or copy-constructor) raises an exception.
			QSE_ASSERT (tmp != QSE_NULL);
			while (index > 0)
			{
				--index;

				// if move-contruction ended up with an exception,
				// the original array can get into an unknown state eventually.
				// i don't attempt to restore the moved object as an exception
				// may be raised during restoration. i don't implement noexcept
				// check yet.
				//
				// TODO: reconsider if this unwinding is needed
				//try { new((QSE::Mmgr*)QSE_NULL, &srcbuf[index]) T((T&&)tmp[index]); }
				//catch (...) {}

				this->_positioner (tmp[index], INVALID_INDEX);
				tmp[index].T::~T ();
			}

			//::operator delete (tmp, this->getMmgr());
			this->getMmgr()->dispose (tmp);

			throw;
		}

		return tmp;
	}
#endif

	void put_item (qse_size_t index, const T& value)
	{
		if (index >= this->count)
		{
			// no value exists in the given position.
			// i can copy-construct the value.
			new((QSE::Mmgr*)QSE_NULL, &this->buffer[index]) T(value);
			this->_positioner (this->buffer[index], index);
		}
		else
		{
			// there is an old value in the position. do classic-assignment
			this->buffer[index] = value;
			this->_positioner (this->buffer[index], index);
		}
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	void put_item_by_moving (qse_size_t index, T&& value)
	{
		if (index >= this->count)
		{
			// no value exists in the given position.
			// i can move-construct the value.
			new((QSE::Mmgr*)QSE_NULL, &this->buffer[index]) T(QSE_CPP_RVREF(value));
			this->_positioner (this->buffer[index], index);
		}
		else
		{
			// there is an old value in the position. do move-assignment.
			this->buffer[index] = QSE_CPP_RVREF(value);
			this->_positioner (this->buffer[index], index);
		}
	}
#endif

	void clear_all_items  ()
	{
		QSE_ASSERT (this->count <= 0 || (this->count >= 1 && this->buffer));

		for (qse_size_t i = this->count; i > 0; )
		{
			--i;
			this->_positioner (this->buffer[i], INVALID_INDEX);
			this->buffer[i].T::~T ();
		}
	
		this->count = 0;
	}

public:
	bool isEmpty () const QSE_CPP_NOEXCEPT
	{
		return this->count == 0;
	}

	qse_size_t getSize () const QSE_CPP_NOEXCEPT
	{
		return this->count;
	}

	qse_size_t getCapacity () const QSE_CPP_NOEXCEPT
	{
		return this->capacity;
	}
	
	operator T* () QSE_CPP_NOEXCEPT
	{
		return this->buffer;
	}

	operator const T* () const QSE_CPP_NOEXCEPT
	{
		return this->buffer;
	}

	T* getBuffer () QSE_CPP_NOEXCEPT
	{
		return this->buffer;
	}

	const T* getBuffer () const QSE_CPP_NOEXCEPT
	{
		return this->buffer;
	}

	/// The getIndex() function returns the index of the given value \a v 
	/// if it belongs to the array. It returns #INVALID_INDEX if not. 
	/// Note that this is not a search function.
	///
	/// \code
	///  QSE::Array<int> a;
	///  a.insert (0, 10);
	///  a.insert (0, 20);
	///  a.insert (0, 30);
	///  const int& t = a[2];
	///  printf ("%lu\n", (unsigned long int)a.getIndex(t)); // print 2
	/// \endcode
	qse_size_t getIndex (const T& v) const QSE_CPP_NOEXCEPT
	{
		if (&v >= &this->buffer[0] && &v < &this->buffer[this->count])
		{
			return &v - &this->buffer[0];
		}

		return INVALID_INDEX;
	}

	T& operator[] (qse_size_t index) QSE_CPP_NOEXCEPT
	{
		QSE_ASSERT (index < this->count);
		return this->buffer[index];
	}

	const T& operator[] (qse_size_t index) const QSE_CPP_NOEXCEPT
	{
		QSE_ASSERT (index < this->count);
		return this->buffer[index];
	}

	T& getValueAt (qse_size_t index) QSE_CPP_NOEXCEPT
	{
		QSE_ASSERT (index < this->count);
		return this->buffer[index];
	}

	const T& getValueAt (qse_size_t index) const QSE_CPP_NOEXCEPT
	{
		QSE_ASSERT (index < this->count);
		return this->buffer[index];
	}

	void setValueAt (qse_size_t index, const T& value)
	{
		this->update (index, value);
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	void setValueAt (qse_size_t index, T&& value)
	{
		this->update (index, QSE_CPP_RVREF(value));
	}
#endif

	const T& getFirst () const QSE_CPP_NOEXCEPT
	{
		return this->getValueAt(0);
	}

	const T& getLast () const QSE_CPP_NOEXCEPT
	{
		return this->getValueAt(this->getSize() - 1);
	}

protected:
	void secure_slot (qse_size_t index)
	{
		if (index >= this->capacity) 
		{
			// the position to add the element is beyond the
			// capacity. resize the buffer.
			qse_size_t new_capa = this->_resizer (this->capacity, this->getGrowthPolicy());

			if (index < new_capa)
				this->setCapacity (new_capa);
			else
				this->setCapacity (index + 1);
		}
		else if (this->count >= this->capacity) 
		{
			// the array is already full.
			// insertion requires at least one more slot
			qse_size_t new_capa = this->_resizer (this->capacity, this->getGrowthPolicy());
			this->setCapacity (new_capa);
		}

		if (index < this->count)
		{
			// shift the existing elements to the back by one slot.
			for (qse_size_t i = this->count; i > index; i--) 
			{
			#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
				this->put_item_by_moving (i, QSE_CPP_RVREF(this->buffer[i - 1])); 
			#else
				this->put_item (i, this->buffer[i - 1]); 
			#endif
			}
		}
		else if (index > this->count)
		{
			// the insertion position leaves some gaps in between.
			// fill the gap with a default value.
			for (qse_size_t i = this->count; i < index; i++)
			{
				new((QSE::Mmgr*)QSE_NULL, &this->buffer[i]) T();
				this->_positioner (this->buffer[i], i);
			}
		}
	}

public:
	qse_size_t insert (qse_size_t index, const T& value)
	{
		// Unlike insert() in RedBlackTree and HashList,
		// it inserts an item when index exists in the existing array.
		// It is because array allows duplicate items.
		// RedBlckTree::insert() and HashList::insert() return failure
		// if existing item exists.
		this->secure_slot (index);

		//this->buffer[index] = value;
		this->put_item (index, value);
		if (index > this->count) this->count = index + 1;
		else this->count++;

		return index;
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	qse_size_t insert (qse_size_t index, T&& value)
	{
		// Unlike insert() in RedBlackTree and HashList,
		// it inserts an item when index exists in the existing array.
		// It is because array allows duplicate items.
		// RedBlckTree::insert() and HashList::insert() return failure
		// if existing item exists.
		this->secure_slot (index);

		//this->buffer[index] = value;
		this->put_item_by_moving (index, QSE_CPP_RVREF(value));
		if (index > this->count) this->count = index + 1;
		else this->count++;

		return index;
	}
#endif

	qse_size_t update (qse_size_t index, const T& value)
	{
		QSE_ASSERT (index < this->count);
		this->buffer[index] = value;
		this->_positioner (this->buffer[index], index);
		return index;
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	qse_size_t update (qse_size_t index, T&& value)
	{
		QSE_ASSERT (index < this->count);
		this->buffer[index] = QSE_CPP_RVREF(value);
		this->_positioner (this->buffer[index], index);
		return index;
	}
#endif

	qse_size_t upsert (qse_size_t index, const T& value)
	{
		if (index < this->count)
			return this->update(index, value);
		else
			return this->insert(index, value);
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	qse_size_t upsert (qse_size_t index, T&& value)
	{
		if (index < this->count)
			return this->update(index, QSE_CPP_RVREF(value));
		else
			return this->insert(index, QSE_CPP_RVREF(value));
	}
#endif

	qse_size_t ensert (qse_size_t index, const T& value)
	{
		if (index < this->count)
			return index; // no update
		else
			return this->insert(index, value);
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	qse_size_t ensert (qse_size_t index, T&& value)
	{
		if (index < this->count)
			return index; // no update
		else
			return this->insert(index, QSE_CPP_RVREF(value));
	}
#endif

	qse_size_t insertFirst (const T& value)
	{
		return this->insert(0, value);
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	qse_size_t insertFirst (T&& value)
	{
		return this->insert(0, QSE_CPP_RVREF(value));
	}
#endif

	qse_size_t insertLast (const T& value)
	{
		return this->insert(this->getSize(), value);
	}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	qse_size_t insertLast (T&& value)
	{
		return this->insert(this->getSize(), QSE_CPP_RVREF(value));
	}
#endif

	void remove (qse_size_t index)
	{
		this->remove (index, 1);
	}

	void remove (qse_size_t from_index, qse_size_t size)
	{
		if (size <= 0 || this->count <= 0 || from_index >= this->count) return;

		qse_size_t to_index = from_index + size - 1;
		if (to_index >= this->count) to_index = this->count - 1;

		qse_size_t j = from_index;
		qse_size_t i = to_index + 1;

		// replace deleted elements by surviving elements at the back
		while (i < this->count) 
		{
			// which is better? 
			//  1. destruct followed by copy construct
			//  2. operator assignment.

			// 1. destruct followed by copy construct
			//this->_positioner (this->buffer[j], INVALID_INDEX);
			//this->buffer[j].T::~T();
			//new((QSE::Mmgr*)QSE_NULL, &this->buffer[j]) T(this->buffer[i]);
			//this->_positioner (this->buffer[j], j);

			// 2. operator assignment
			this->buffer[j] = QSE_CPP_RVREF(this->buffer[i]);
			this->_positioner (this->buffer[j], j);

			j++; i++;
		}

		// call the destructor of deleted elements.
		while (j < this->count)
		{
			this->_positioner (this->buffer[j], INVALID_INDEX);
			this->buffer[j].T::~T ();
			j++;
		}

		// recalculate the number of elements 
		this->count -= to_index - from_index + 1;
	}

	void removeFirst ()
	{
		this->remove(0);
	}

	void removeLast ()
	{
		this->remove(this->getSize() - 1);
	}

public:
	void clear (bool purge_buffer = false)
	{
		this->clear_all_items ();

		if (purge_buffer && this->buffer)
		{
			QSE_ASSERT (this->count <= 0);
			QSE_ASSERT (this->capacity > 0);

			//::operator delete (this->buffer, this->getMmgr());
			this->getMmgr()->dispose (this->buffer);
			this->buffer = QSE_NULL;
			this->capacity = 0;
		}
	}

	void setSize (qse_size_t size)
	{
		if (size < this->count)
		{
			for (qse_size_t i = size; i < this->count; ++i)
			{
				// call the destructor of the items 
				this->_positioner (this->buffer[i], INVALID_INDEX);
				this->buffer[i].T::~T ();
			}

			this->count = size;
		}
		else if (size > this->count)
		{
			if (size > this->capacity) this->setCapacity (size);
			for (qse_size_t i = this->count; i < size; ++i)
			{
				// use the default contructor to set the value.
				new((QSE::Mmgr*)QSE_NULL, &this->buffer[i]) T();
				this->_positioner (this->buffer[i], i);
			}

			this->count = size;
		}
	}

	void setCapacity (qse_size_t capa)
	{
		if (capa == this->capacity) return;

		if (capa <= 0) 
		{
			this->clear (true);
		}
		else if (this->buffer)
		{
			QSE_ASSERT (this->capacity > 0);

			qse_size_t cnt = this->count;
			if (cnt > capa) cnt = capa;

		#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
			T* tmp = this->clone_buffer_by_moving (this->buffer, capa, cnt);
		#else
			T* tmp = this->clone_buffer (this->buffer, capa, cnt);
		#endif
			this->clear (true);
			this->buffer = tmp;
			this->capacity = capa;
			this->count = cnt;
		}
		else
		{
			QSE_ASSERT (this->capacity <= 0);
			QSE_ASSERT (this->count <= 0);

			//this->buffer = (T*)::operator new (capa * QSE_SIZEOF(*this->buffer), this->getMmgr());
			this->buffer = (T*)this->getMmgr()->allocate(capa * QSE_SIZEOF(*this->buffer));
			this->capacity = capa;
		}
	}

	/// The compact() function removes the unused space in the buffer.
	void compact ()
	{
		this->setCapacity (this->size);
	}

	enum RotateDirection
	{
		ROTATE_LEFT,
		ROTATE_RIGHT
	};

	void rotate (RotateDirection dir, qse_size_t n)
	{
		qse_size_t first, last, cnt, index, nk;
		T c;

		if ((n %= this->count) == 0) return;

		if (dir == ROTATE_RIGHT) n = this->count - n;
		first = 0; nk = this->count - n; cnt = 0; 

		while (cnt < n) 
		{
			last = first + nk;
			index = first;
			c = QSE_CPP_RVREF(this->buffer[index]);
			while (1) 
			{
				cnt++;
				while (index < nk) 
				{
					this->buffer[index] = QSE_CPP_RVREF(this->buffer[index + n]);
					this->_positioner (this->buffer[index], index);
					index += n;
				}
				if (index == last) break;

				this->buffer[index] = QSE_CPP_RVREF(this->buffer[index - nk]);
				this->_positioner (this->buffer[index], index);
				index -= nk;
			}

			this->buffer[last] = QSE_CPP_RVREF(c);
			this->_positioner (this->buffer[last], last);
			first++;
		}
	}

protected:
	POSITIONER _positioner;
	RESIZER    _resizer;

	qse_size_t count;
	qse_size_t capacity;
	T*         buffer;
};

// euqal-to comparator
template <typename T>
struct SearchableArrayEqtester
{
	// this can be used to build a max heap
	bool operator() (const T& v1, const T& v2) const
	{
		return v1 == v2;
	}
};

template <typename T, typename EQTESTER = SearchableArrayEqtester<T>, typename POSITIONER = ArrayPositioner<T>, typename RESIZER = ArrayResizer>
class SearchableArray: public Array<T, POSITIONER, RESIZER>
{
public:
	typedef SearchableArray<T,EQTESTER,POSITIONER,RESIZER> SelfType;
	typedef Array<T,POSITIONER,RESIZER> ParentType;

	enum
	{
		DEFAULT_CAPACITY = ParentType::DEFAULT_CAPACITY,
		INVALID_INDEX = ParentType::INVALID_INDEX
	};

	SearchableArray (Mmgr* mmgr = QSE_NULL): ParentType(mmgr) {}

	SearchableArray (qse_size_t capacity, Mmgr* mmgr = QSE_NULL): ParentType(capacity, mmgr) {}

	SearchableArray (const SelfType& heap): ParentType(heap) {}

#if defined(QSE_CPP_ENABLE_CPP11_MOVE)
	SearchableArray (SelfType&& heap): ParentType(QSE_CPP_RVREF(heap)) {}
#endif
	~SearchableArray () { }

	/// \return the number of items deleted.
	int removeByValue (const T& value)
	{
		qse_size_t index = this->findFirstIndex (value);
		if (index == INVALID_INDEX) return 0;
		this->remove (index);
		return 1; 
	}

	qse_size_t removeAllByValue (const T& value)
	{
		qse_size_t cnt = 0;
		while (this->removeByValue(value) > 0) cnt++;
		return cnt;
	}

	qse_size_t findFirstIndex (const T& value) const
	{
		for (qse_size_t i = 0; i < this->count; i++) 
		{
			if (this->_eqtester(this->buffer[i], value)) return i;
		}
		return INVALID_INDEX;
	}
	
	qse_size_t findFirstIndex (const T& value, qse_size_t index) const
	{
		for (qse_size_t i = index; i < this->count; i++) 
		{
			if (this->_eqtester(this->buffer[i], value)) return i;
		}
		return INVALID_INDEX;
	}

	qse_size_t findLastIndex (const T& value) const
	{
		for (qse_size_t i = this->count; i > 0; ) 
		{
			if (this->_eqtester(this->buffer[--i], value)) return i;
		}
		return INVALID_INDEX;
	}
	
	qse_size_t findLastIndex (const T& value, qse_size_t index) const
	{
		for (qse_size_t i = index + 1; i > 0; ) 
		{
			if (this->_eqtester(this->buffer[--i], value)) return i;
		}
		return INVALID_INDEX;
	}

protected:
	EQTESTER _eqtester;
};

/////////////////////////////////
QSE_END_NAMESPACE(QSE)
/////////////////////////////////

#endif


