#pragma once

#include <vector>
#include <cassert>
#include <limits>


#include "GlInclude.h"


namespace lib_gl
{


class GlPickedObject;
class GlPicker;

typedef std::vector<GlPickedObject> GlPickedObjectAry;


// ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//! OpenGLのピッキング処理をサポートするクラス
class GlPicker
{
public:
	GlPicker(void)
	{}

	GlPicker(size_t buf_size)
		: m_SelBuffer(buf_size)
	{}

	// ピッキング処理の開始, 終了
	void BeginPick(void);
	bool EndPick(void);

	// ピッキングバッファのサイズ設定
	void SetPickBufferSize(size_t buf_size);
	size_t GetPickBufferSize(void) const;
	void ClearBuffer(void);


	// ピッキング結果を表すデータ列を返す.
	GlPickedObjectAry& GetPickedObjects(void);
	const GlPickedObjectAry& GetPickedObjects(void) const;

	// ピッキングされたオブジェクト数を返す.
	size_t GetNumPickedObjects(void) const;

	GlPickedObject& GetPickedObject(size_t idx);
	const GlPickedObject& GetPickedObject(size_t idx) const;

	// 最も浅い位置でピックされたオブジェクトを返す
	GlPickedObject* GetMinDepthObject(void);
	const GlPickedObject* GetMinDepthObject(void) const;

	// 最も深い位置でピックされたオブジェクトを返す
	GlPickedObject* GetMaxDepthObject(void);
	const GlPickedObject* GetMaxDepthObject(void) const;

protected:
	void CreatePickedObjecsInfo( GLint num_hits );

protected:
	std::vector<GLuint> m_SelBuffer;     //!< セレクションバッファ
	GlPickedObjectAry   m_PickedObjects; //!< ピッキング情報
};


// ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//! ピッキング結果を表すクラス
class GlPickedObject
{
public:
	GlPickedObject( GLuint* buffer_head_pointer )
		: m_head( buffer_head_pointer )
	{}

	GLuint GetNumLayer(void) const { return m_head[0]; }
	GLuint GetMinDepth(void) const { return m_head[1]; }
	GLuint GetMaxDepth(void) const { return m_head[2]; }

	GLuint GetLayerName(size_t layer_idx) const
	{
		GLuint num_layer = GetNumLayer();
		assert( layer_idx < num_layer );
		return m_head[3+layer_idx];
	}

	GLuint GetTopName(void) const
	{
		assert( GetNumLayer() != 0 );
		return GetLayerName(0);
	}

	GLuint GetBottomName(void) const
	{
		assert( GetNumLayer() != 0 );
		return GetLayerName(GetNumLayer()-1);
	}

protected:
	GLuint* m_head;  //!< OpenGLのピッキングバッファ内での1オブジェクトをあらわす領域の先頭
};
typedef std::vector<GlPickedObject> GlPickedObjectAry;



// ------------------------------------------------------------------------------------------------

//! ピッキング開始
inline
void GlPicker::BeginPick(void)
{
	assert( !m_SelBuffer.empty() );

	glSelectBuffer( m_SelBuffer.size() , &m_SelBuffer.front() );
	glRenderMode(GL_SELECT);

	glInitNames();
}

//! ピッキング終了とピックしたオブジェクトの情報を生成
//! ピックに失敗した場合はオブジェクトの情報を生成せずにfalseを返すだけ
inline
bool GlPicker::EndPick(void)
{
	GLint num_hits = glRenderMode(GL_RENDER);
	bool bSuccessed = ( num_hits > 0 );
	if( !bSuccessed ) return false;

	CreatePickedObjecsInfo( num_hits );

	return true;
}


//! ピッキングバッファのサイズを指定する.
inline
void GlPicker::SetPickBufferSize(size_t buf_size)
{
	m_SelBuffer.clear();
	m_SelBuffer.resize(buf_size);
}

//! ピッキングバッファのサイズを変更する.
inline
size_t GlPicker::GetPickBufferSize(void) const
{
	return m_SelBuffer.size();
}

//! バッファをクリア.
inline
void GlPicker::ClearBuffer(void)
{
	m_SelBuffer.clear();
	m_PickedObjects.clear();
}


inline
GlPickedObjectAry& GlPicker::GetPickedObjects(void)
{
	return m_PickedObjects;
}

inline
const GlPickedObjectAry& GlPicker::GetPickedObjects(void) const
{
	return m_PickedObjects;
}


inline
size_t GlPicker::GetNumPickedObjects(void) const
{
	return m_PickedObjects.size();
}


inline
GlPickedObject& GlPicker::GetPickedObject(size_t idx)
{
	return m_PickedObjects[idx];
}

inline
const GlPickedObject& GlPicker::GetPickedObject(size_t idx) const
{
	return m_PickedObjects[idx];
}


inline
GlPickedObject* GlPicker::GetMinDepthObject(void)
{
	GlPickedObject* obj = NULL;
	GLuint min_depth = (std::numeric_limits<GLuint>::max)();
	for( size_t i = 0 ; i < m_PickedObjects.size() ; i++ )
	{
		if( min_depth > m_PickedObjects[i].GetMinDepth() )
		{
			min_depth = m_PickedObjects[i].GetMinDepth();
			obj = &m_PickedObjects[i];
		}
	}

	return obj;
}

inline
const GlPickedObject* GlPicker::GetMinDepthObject(void) const
{
	const GlPickedObject* obj = NULL;
	GLuint min_depth = (std::numeric_limits<GLuint>::max)();
	for( size_t i = 0 ; i < m_PickedObjects.size() ; i++ )
	{
		if( min_depth > m_PickedObjects[i].GetMinDepth() )
		{
			min_depth = m_PickedObjects[i].GetMinDepth();
			obj = &m_PickedObjects[i];
		}
	}

	return obj;
}


inline
GlPickedObject* GlPicker::GetMaxDepthObject(void)
{
	GlPickedObject* obj = NULL;
	GLuint max_depth = (std::numeric_limits<GLuint>::min)();
	for( size_t i = 0 ; i < m_PickedObjects.size() ; i++ )
	{
		if( max_depth < m_PickedObjects[i].GetMaxDepth() )
		{
			max_depth = m_PickedObjects[i].GetMaxDepth();
			obj = &m_PickedObjects[i];
		}
	}

	return obj;
}

inline
const GlPickedObject* GlPicker::GetMaxDepthObject(void) const
{
	const GlPickedObject* obj = NULL;
	GLuint max_depth = (std::numeric_limits<GLuint>::min)();
	for( GLuint i = 0 ; i < m_PickedObjects.size() ; i++ )
	{
		if( max_depth < m_PickedObjects[i].GetMaxDepth() )
		{
			max_depth = m_PickedObjects[i].GetMaxDepth();
			obj = &m_PickedObjects[i];
		}
	}

	return obj;
}


inline
void GlPicker::CreatePickedObjecsInfo( GLint num_hits )
{
	m_PickedObjects.clear();
	m_PickedObjects.reserve(num_hits);

	GLuint* next_head = &m_SelBuffer.front();
	for( GLint i = 0 ; i < num_hits ; i++ )
	{
		m_PickedObjects.push_back( GlPickedObject( next_head ) );

		next_head += m_PickedObjects.back().GetNumLayer() + 3;
	}
}


}
