#pragma once

namespace Mix{

	template<typename T>
	class Queue
	{
	private:
		T* m_pBuffer;
		UInt32 m_Capacity;
		UInt32 m_ResizeStep;
		UInt32 m_HeadIndex;
		UInt32 m_Count;

#ifdef _DEBUG
		Mix::StringW m_DebugName;
#endif //_DEBUG

	public:
		Queue( void ) :
		m_pBuffer( NULL ),
		m_Capacity( 0 ),
		m_ResizeStep( 0 ),
		m_HeadIndex( 0 ),
		m_Count( 0 )
		{
#ifdef _DEBUG
			m_DebugName = L"";
#endif //_DEBUG
		}

		Queue( UInt32 defCapacity, UInt32 resizeStep, const wchar_t* pDebugName = NULL ) :
		m_pBuffer( NULL ),
		m_Capacity( 0 ),
		m_ResizeStep( 0 ),
		m_HeadIndex( 0 ),
		m_Count( 0 )
		{
#ifdef _DEBUG
			m_DebugName = L"";
#endif //_DEBUG

			Initialize( defCapacity, resizeStep, pDebugName );
		}

		~Queue( void )
		{
#ifdef _DEBUG
			MIX_LOG_DEV_INFO( L"Mix::Queue : Release : Name[%s] Capacity[%u]", m_DebugName.GetConstPtr(), m_Capacity );
#endif //_DEBUG

			MIX_LIB_DELETE_ARRAY( m_pBuffer );
		}

		void Initialize( UInt32 defCapacity, UInt32 resizeStep, const wchar_t* pDebugName = NULL )
		{
			MIX_ASSERT( m_pBuffer == NULL );
			MIX_ASSERT( defCapacity >= 2 );

#ifdef _DEBUG
			m_DebugName = MIX_SAFE_NAME( pDebugName );
#endif //_DEBUG

			m_Capacity = defCapacity;
			m_ResizeStep = resizeStep;

			m_pBuffer = MIX_LIB_NEW T[m_Capacity];
			MIX_ASSERT( m_pBuffer != NULL );

#ifdef _DEBUG

			MIX_LOG_INFO( L"Mix::Queue : Initialize : Name[%s] Capacity[%u] ResizeStep[%u]",
				m_DebugName.GetConstPtr(),
				m_Capacity,
				m_ResizeStep );
#endif //_DEBUG
		}

		void Enqueue( const T& data )
		{
			MIX_ASSERT( m_pBuffer != NULL );

			if( m_Count == m_Capacity )
			{
				MIX_ASSERT_EX( m_ResizeStep > 0, L"Mix::Queue : キャパシティを超えました : Name[%s]", m_DebugName.GetConstPtr() );
				void Resize();
			}

			m_pBuffer[( m_HeadIndex + m_Count ) % m_Capacity] = data;
			m_Count++;
		}

		const T& Dequeue( void )
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			UInt32 preHeadIndex = m_HeadIndex;

			m_HeadIndex = ( m_HeadIndex + 1 ) % m_Capacity;
			m_Count--;

			return m_pBuffer[preHeadIndex];
		}

		void Drain( void )
		{
			m_Count = 0;
		}

		const T& GetHead( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return m_pBuffer[m_HeadIndex];
		}

		const T& GetTail( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return m_pBuffer[( m_HeadIndex + m_Count - 1 ) % m_Capacity];
		}

		UInt32 GetHeadIndex( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return m_HeadIndex;
		}

		UInt32 GetTailIndex( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return ( m_HeadIndex + m_Count - 1 ) % m_Capacity;
		}

		UInt32 GetCount( void ) const
		{
			return m_Count;
		}

		Boolean IsEmpty( void ) const
		{
			return ( m_Count == 0 );
		}

		T& operator [] ( UInt32 index )
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Capacity > index );

			return m_pBuffer[index];
		}

	private:
		void Resize( void )
		{
			MIX_ASSERT( m_Count == m_Capacity );

			UInt32 nextCapacity = m_Capacity + m_ResizeStep;
			T* pNextBuffer = MIX_LIB_NEW T[nextCapacity];

			MIX_ASSERT( pNextBuffer != NULL );

			UInt32 srcIndex = m_HeadIndex;
			UInt32 dstIndex = m_HeadIndex;

			for( UInt32 i = 0; i < m_Count; i++ )
			{
				pNextBuffer[dstIndex] = m_pBuffer[srcIndex];

				srcIndex = ( srcIndex + 1 ) % m_Capacity;
				dstIndex = ( dstIndex + 1 ) % nextCapacity;
			}

			MIX_LIB_DELETE_ARRAY( m_pBuffer );
			m_pBuffer = pNextBuffer;
			m_Capacity = nextCapacity;

#ifdef _DEBUG
			MIX_LOG_INFO( L"Mix::Queue : Resize : Name[%s] Capacity[%u]", m_DebugName.GetConstPtr(), m_Capacity );
#endif //_DEBUG
		}
	};

	template<Mix::Memory::SECTION_TYPE ST, typename T>
	class QueueT
	{
	private:
		T* m_pBuffer;
		UInt32 m_Capacity;
		UInt32 m_ResizeStep;
		UInt32 m_HeadIndex;
		UInt32 m_Count;

#ifdef _DEBUG
		Mix::StringW m_DebugName;
#endif //_DEBUG

	public:
		QueueT( void ) :
		m_pBuffer( NULL ),
		m_Capacity( 0 ),
		m_ResizeStep( 0 ),
		m_HeadIndex( 0 ),
		m_Count( 0 )
		{
#ifdef _DEBUG
			m_DebugName = L"";
#endif //_DEBUG
		}

		QueueT( UInt32 defCapacity, UInt32 resizeStep, const wchar_t* pDebugName = NULL ) :
		m_pBuffer( NULL ),
		m_Capacity( 0 ),
		m_ResizeStep( 0 ),
		m_HeadIndex( 0 ),
		m_Count( 0 )
		{
#ifdef _DEBUG
			m_DebugName = L"";
#endif //_DEBUG

			Initialize( defCapacity, resizeStep, pDebugName );
		}

		~QueueT( void )
		{
#ifdef _DEBUG
			MIX_LOG_DEV_INFO( L"Mix::QueueT : Release : Name[%s] Capacity[%u]", m_DebugName.GetConstPtr(), m_Capacity );
#endif //_DEBUG

			MIX_LIB_DELETE_ARRAY_T( T, m_pBuffer );
		}

		void Initialize( UInt32 defCapacity, UInt32 resizeStep, const wchar_t* pDebugName = NULL )
		{
			MIX_ASSERT( m_pBuffer == NULL );
			MIX_ASSERT( defCapacity >= 2 );

#ifdef _DEBUG
			m_DebugName = MIX_SAFE_NAME( pDebugName );
#endif //_DEBUG

			m_Capacity = defCapacity;
			m_ResizeStep = resizeStep;

			m_pBuffer = MIX_LIB_NEW_ARRAY_T( ST, T, m_Capacity );
			MIX_ASSERT( m_pBuffer != NULL );

#ifdef _DEBUG

			MIX_LOG_INFO( L"Mix::QueueT : Initialize : Name[%s] Capacity[%u] ResizeStep[%u]",
				m_DebugName.GetConstPtr(),
				m_Capacity,
				m_ResizeStep );
#endif //_DEBUG
		}

		void Enqueue( const T& data )
		{
			MIX_ASSERT( m_pBuffer != NULL );

			if( m_Count == m_Capacity )
			{
				MIX_ASSERT_EX( m_ResizeStep > 0, L"Mix::QueueT : キャパシティを超えました : Name[%s]", m_DebugName.GetConstPtr() );
				void Resize();
			}

			m_pBuffer[( m_HeadIndex + m_Count ) % m_Capacity] = data;
			m_Count++;
		}

		const T& Dequeue( void )
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			UInt32 preHeadIndex = m_HeadIndex;

			m_HeadIndex = ( m_HeadIndex + 1 ) % m_Capacity;
			m_Count--;

			return m_pBuffer[preHeadIndex];
		}

		void Drain( void )
		{
			m_Count = 0;
		}

		const T& GetHead( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return m_pBuffer[m_HeadIndex];
		}

		const T& GetTail( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return m_pBuffer[( m_HeadIndex + m_Count - 1 ) % m_Capacity];
		}

		UInt32 GetHeadIndex( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return m_HeadIndex;
		}

		UInt32 GetTailIndex( void ) const
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Count > 0 );

			return ( m_HeadIndex + m_Count - 1 ) % m_Capacity;
		}

		UInt32 GetCount( void ) const
		{
			return m_Count;
		}

		Boolean IsEmpty( void ) const
		{
			return ( m_Count == 0 );
		}

		T& operator [] ( UInt32 index )
		{
			MIX_ASSERT( m_pBuffer != NULL );
			MIX_ASSERT( m_Capacity > index );

			return m_pBuffer[index];
		}

	private:
		void Resize( void )
		{
			MIX_ASSERT( m_Count == m_Capacity );

			UInt32 nextCapacity = m_Capacity + m_ResizeStep;
			T* pNextBuffer = MIX_LIB_NEW_ARRAY_T( ST, T, nextCapacity );

			MIX_ASSERT( pNextBuffer != NULL );

			UInt32 srcIndex = m_HeadIndex;
			UInt32 dstIndex = m_HeadIndex;

			for( UInt32 i = 0; i < m_Count; i++ )
			{
				pNextBuffer[dstIndex] = m_pBuffer[srcIndex];

				srcIndex = ( srcIndex + 1 ) % m_Capacity;
				dstIndex = ( dstIndex + 1 ) % nextCapacity;
			}

			MIX_LIB_DELETE_ARRAY_T( T, m_pBuffer );
			m_pBuffer = pNextBuffer;
			m_Capacity = nextCapacity;

#ifdef _DEBUG
			MIX_LOG_INFO( L"Mix::QueueT : Resize : Name[%s] Capacity[%u]", m_DebugName.GetConstPtr(), m_Capacity );
#endif //_DEBUG
		}
	};

}
