#include "Mix/Private/Graphics/Common/Device.h"

#include "Mix/Memory/IBuffer.h"
#include "Mix/Graphics/ITexture.h"

namespace Mix{ namespace Graphics{ namespace Common{

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Graphics::Common::Device : 定数
////////////////////////////////////////////////////////////////////////////////////////////////////

const wchar_t* Device::FAILED_INITIALIZE = L"グラフィックデバイスの初期化に失敗";
const wchar_t* Device::FAILED_CREATE_HULL_SHADER = L"ハルシェーダーの作成に失敗";
const wchar_t* Device::FAILED_CREATE_DOMAIN_SHADER = L"ドメインシェーダーの作成に失敗";
const wchar_t* Device::FAILED_CREATE_GEOMETRY_SHADER = L"ジオメトリシェーダーの作成に失敗";
const wchar_t* Device::FAILED_CREATE_VERTEX_SHADER = L"バーテックスバッファの作成に失敗";
const wchar_t* Device::FAILED_CREATE_PIXEL_SHADER = L"ピクセルシェーダーの作成に失敗";
const wchar_t* Device::FAILED_CREATE_SHADER_CONSTANT = L"シェーダーコンスタントの作成に失敗";
const wchar_t* Device::FAILED_CREATE_VERTEX_LAYOUT = L"バーテックスレイアウトの作成に失敗";
const wchar_t* Device::FAILED_CREATE_VERTEX_BUFFER = L"バーテックスバッファの作成に失敗";
const wchar_t* Device::FAILED_CREATE_INDEX_BUFFER = L"インデックスバッファの作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE = L"テクスチャの作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_PLANE = L"テクスチャ ( Plane ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_VOLUME = L"テクスチャ ( Volume ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_CUBE = L"テクスチャ ( Cube ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_DYNAMIC_PLANE = L"テクスチャ ( Dynamic Plane ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_TARGET_PLANE = L"テクスチャ ( Target Plane ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_LOCKABLE_TARGET_PLANE = L"テクスチャ ( Lockable Target Plane ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_TARGET_CUBE = L"テクスチャ ( Target Cube ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_TEXTURE_DEPTH = L"テクスチャ ( Depth ) の作成に失敗";
const wchar_t* Device::FAILED_CREATE_QUERY = L"クエリの作成に失敗";
const wchar_t* Device::FAILED_RESIZE_BACKBUFFER = L"バックバッファのリサイズに失敗";
const wchar_t* Device::FAILED_SET_TARGET = L"ターゲットの設定に失敗";
const wchar_t* Device::FAILED_SET_TEXTURE = L"テクスチャの設定に失敗";
const wchar_t* Device::FAILED_RESET_TEXTURE = L"テクスチャのリセットに失敗";
const wchar_t* Device::FAILED_DRAW = L"描画 ( Draw ) に失敗";
const wchar_t* Device::FAILED_DRAW_INDEXED = L"描画 ( DrawIndexed ) に失敗";
const wchar_t* Device::FAILED_SAVE_SCREEN_SHOT = L"スクリーンショットの保存に失敗";
const wchar_t* Device::MSG_NOT_SET_DEVOBJ = L"必要なデバイスオブジェクトが設定されていません";
const wchar_t* Device::MSG_MISSING_PT_VE = L"プリミティブタイプと頂点数が一致しません";
const wchar_t* Device::SAVE_SCREEN_SHOT = L"スクリーンショットを保存";

// フォーマットテキストテーブル
const wchar_t* Device::FORMAT_TEXT_TABLE[Mix::Graphics::FMT_MAX] =
{
	L"UNKNOWN",
	L"D16",
	L"D32",
	L"D24S8",
	L"R8G8B8A8",
	L"A8",
	L"R16F",
	L"R32F",
	L"R16G16F",
	L"R32G32F",
	L"R16G16B16A16F",
	L"R32G32B32A32F",
	L"BC1",
	L"BC2",
	L"BC3",
};

// バーテックスエレメントのサイズテーブル
UInt32 Device::VERTEX_ELEMENT_SIZE_TABLE[Mix::Graphics::VERTEX_ELEMENT_FORMAT_MAX] =
{
	4,	//VLF_FLOAT32_1
	8,	//VLF_FLOAT32_2
	12,	//VLF_FLOAT32_3
	16,	//VLF_FLOAT32_4
	4,	//VLF_UINT8_4
	4,	//VLF_INT16_2
	8,	//VLF_INT16_4
	4,	//VLF_UINT8_4N
	4,	//VLF_INT16_2N
	8,	//VLF_INT16_4N
	4,	//VLF_UINT16_2N
	8,	//VLF_UINT16_4N
	4,	//VLF_FLOAT16_2
	8,	//VLF_FLOAT16_4
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Graphics::Common::Device::TexturePlugin
////////////////////////////////////////////////////////////////////////////////////////////////////

const char* Device::TextureLoaderPlugin::FN_LOAD = "Load";

Device::TextureLoaderPlugin* Device::TextureLoaderPlugin::CreateInstance( HMODULE hModule )
{
	MIX_ASSERT( hModule != NULL );

	if( ::GetProcAddress( hModule, Device::TextureLoaderPlugin::FN_LOAD ) == NULL )
	{
		return NULL;
	}

	return MIX_LIB_NEW_T( Mix::Memory::SECTION_GRAPHICS, TextureLoaderPlugin, hModule );
}

void Device::TextureLoaderPlugin::Destroy( void )
{
	MIX_LIB_DELETE_THIS_T( TextureLoaderPlugin, this );
}

Device::TextureLoaderPlugin::TextureLoaderPlugin( HMODULE hModule ) :
m_hModule( hModule )
{
}

Device::TextureLoaderPlugin::~TextureLoaderPlugin( void )
{
	if ( m_hModule != NULL )
	{
		::FreeLibrary( m_hModule );
		m_hModule = NULL;
	}
}

int Device::TextureLoaderPlugin::Load( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst )
{
	return reinterpret_cast<TextureLoaderPlugin::_Load>( ::GetProcAddress( m_hModule, Device::TextureLoaderPlugin::FN_LOAD ) )( pSrc, pDst );
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Graphics::Common::Device::TextureKit
////////////////////////////////////////////////////////////////////////////////////////////////////

Device::TextureKit::TextureKit( void )
{
	m_Desc.type = Mix::Plugin::Graphics::ITextureKit::PLANE;
	m_Desc.format = Mix::Graphics::FMT_UNKNOWN;
	m_Desc.width = 0;
	m_Desc.height = 0;

	m_BuffPos = 0;
	m_BuffSize = 1024 * 1024;
	m_pBuff = reinterpret_cast<UInt8*>( MIX_LIB_MALLOC( Mix::Memory::SECTION_GRAPHICS, m_BuffSize ) );
}

Device::TextureKit::~TextureKit( void )
{
	if( m_pBuff != NULL )
	{
		MIX_LIB_FREE( m_pBuff );
	}
}

void Device::TextureKit::Reset( void )
{
	m_Desc.type = Mix::Plugin::Graphics::ITextureKit::PLANE;
	m_Desc.format = Mix::Graphics::FMT_UNKNOWN;
	m_Desc.width = 0;
	m_Desc.height = 0;
	m_Desc.depth = 0;
	m_Desc.mipLevels = 0;
	m_Desc.flags = 0;

	m_SubResDatList.clear();

	m_BuffPos = 0;
}

const Device::TEXTURE_DESC& Device::TextureKit::GetDesc( void ) const
{
	return m_Desc;
}

unsigned int Device::TextureKit::GetSubResourceCount( void ) const
{
	return MIX_UIT_TO_I32( m_SubResDatList.size() );
}

const Device::TEXTURE_SUBRESOURCE_DATA* Device::TextureKit::GetSubResources( void ) const
{
	return &( m_SubResDatList[0] );
}

void Device::TextureKit::SetType( ITextureKit::TYPE type )
{
	m_Desc.type = type;
}

void Device::TextureKit::SetFormat( ITextureKit::FORMAT format )
{
	switch( format )
	{
	case Mix::Plugin::Graphics::ITextureKit::R8G8B8A8:
		m_Desc.format = Mix::Graphics::FMT_R8G8B8A8;
		break;
	case Mix::Plugin::Graphics::ITextureKit::BC1:
		m_Desc.format = Mix::Graphics::FMT_BC1;
		break;
	case Mix::Plugin::Graphics::ITextureKit::BC2:
		m_Desc.format = Mix::Graphics::FMT_BC2;
		break;
	case Mix::Plugin::Graphics::ITextureKit::BC3:
		m_Desc.format = Mix::Graphics::FMT_BC3;
		break;

	default:
		m_Desc.format = Mix::Graphics::FMT_UNKNOWN;
	}
}

void Device::TextureKit::SetSize( unsigned int width, unsigned int height )
{
	m_Desc.width = width;
	m_Desc.height = height;
}

void Device::TextureKit::SetDepth( unsigned int depth )
{
	m_Desc.depth = depth;
}

void Device::TextureKit::SetMipLevels( unsigned int mipLevels )
{
	m_Desc.mipLevels = mipLevels;
}

void Device::TextureKit::SetFlags( unsigned int flags )
{
	m_Desc.flags = flags;
}

void* Device::TextureKit::AddSubResourceData( unsigned int memSize, unsigned int memRowPitch, UInt32 memSlicePitch )
{
	UInt32 nextBuffPos = m_BuffPos + memSize;
	void* pMem;
	Device::TEXTURE_SUBRESOURCE_DATA dat;

	if( nextBuffPos > m_BuffSize )
	{
		void* pTemp = MIX_LIB_REALLOC( m_pBuff, nextBuffPos );

		if( pTemp != NULL )
		{
			m_pBuff = reinterpret_cast<UInt8*>( pTemp );
		}
		else
		{
			return NULL;
		}
	}

	pMem = &( m_pBuff[m_BuffPos] );
	m_BuffPos = nextBuffPos;

	dat.pMem = pMem;
	dat.memSize = memSize;
	dat.memRowPitch = memRowPitch;
	dat.memSlicePitch = memSlicePitch;

	m_SubResDatList.push_back( dat );

	return pMem;
}

void Device::TextureKit::AddSubResourceData( const void* pMem, unsigned int memSize, unsigned int memRowPitch, UInt32 memSlicePitch )
{
	Device::TEXTURE_SUBRESOURCE_DATA dat;

	dat.pMem = pMem;
	dat.memSize = memSize;
	dat.memRowPitch = memRowPitch;
	dat.memSlicePitch = memSlicePitch;

	m_SubResDatList.push_back( dat );
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Graphics::Common::Device::TextureSource
////////////////////////////////////////////////////////////////////////////////////////////////////

Device::TextureSource::TextureSource( const void* pMem, unsigned int memSize ) :
m_pMem( NULL ),
m_pBegin( NULL ),
m_Size( memSize ),
m_Pos( 0 )
{
	MIX_ASSERT( pMem != NULL );
	MIX_ASSERT( memSize > 0 );

	m_pMem = reinterpret_cast<const UInt8*>( pMem );
	m_pBegin = m_pMem;
	m_Pos = 0;
	m_Size = memSize;
}

unsigned long long Device::TextureSource::GetSize( void ) const
{
	return m_Size;
}

unsigned long long Device::TextureSource::GetPosition( void ) const
{
	return m_Pos;
}

unsigned int Device::TextureSource::Read( void* pBuffer, unsigned int readSize )
{
	if( ( pBuffer == NULL ) ||
		( readSize == 0 ) ||
		( m_Pos >= m_Size ) )
	{
		return 0;
	}

	unsigned int size;

	if( ( m_Pos + readSize ) >= m_Size )
	{
		size = static_cast<unsigned int>( m_Size - m_Pos );
	}
	else
	{
		size = readSize;
	}

	Mix::Memory::Copy( pBuffer, ( m_pBegin + m_Pos ), size );

	m_Pos += size;

	return size;
}

const void* Device::TextureSource::Read( unsigned int readSize, unsigned int& readResult )
{
	if( ( readSize == 0 ) || ( m_Pos >= m_Size ) )
	{
		readResult = 0;
		return NULL;
	}

	const unsigned char* pRet;

	if( ( m_Pos + readSize ) >= m_Size )
	{
		readResult = static_cast<unsigned int>( m_Size - m_Pos );
	}
	else
	{
		readResult = readSize;
	}

	pRet = m_pBegin + m_Pos;

	m_Pos += readResult;

	return pRet;
}

unsigned long long Device::TextureSource::Seek( Mix::Plugin::IReader::SEEK_METHOD method, unsigned long long distance )
{
	switch( method )
	{
	case Mix::Plugin::IReader::BEGIN:
		if( distance > 0 )
		{
			if( m_Size > distance )
			{
				m_Pos = distance;
			}
			else
			{
				m_Pos = m_Size;
			}
		}
		else
		{
			m_Pos = 0;
		}
		break;

	case Mix::Plugin::IReader::CURRENT:
		if( distance < 0 )
		{
			if( m_Pos > distance )
			{
				m_Pos -= distance;
			}
			else
			{
				m_Pos = 0;
			}
		}
		else if( distance > 0 )
		{
			if( ( m_Size - m_Pos ) > distance )
			{
				m_Pos += distance;
			}
			else
			{
				m_Pos = m_Size;
			}
		}
		break;

	case Mix::Plugin::IReader::END:
		if( distance < 0 )
		{
			if( m_Size > distance )
			{
				m_Pos = ( m_Size - distance );
			}
			else
			{
				m_Pos = 0;
			}
		}
		else
		{
			m_Pos = m_Size;
		}
		break;
	}

	return m_Pos;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Graphics::Common::Device
////////////////////////////////////////////////////////////////////////////////////////////////////

Device::Device( Boolean bWaitVSync ) :
m_pEngine( NULL ),
m_pFileMgr( NULL ),
m_bWaitVSync( bWaitVSync )
{
	MIX_ASSERT( Mix::IO::GetManagerPtr() != NULL );

	Mix::Engine* pEngine = Mix::GetInternalEnginePtr();
	Mix::IO::IManager* pFileMgr = Mix::IO::GetManagerPtr();

	MIX_ADD_REF( pEngine );
	m_pEngine = pEngine;

	MIX_ADD_REF( pFileMgr );
	m_pFileMgr = pFileMgr;
}

Device::~Device( void )
{
}

Mix::Graphics::ITexture* Device::CreateTextureFromPlugin( const wchar_t* pFilePath, const void* pSrc, UInt32 srcSize )
{
	MIX_ASSERT( pFilePath != NULL );
	MIX_ASSERT( pSrc != NULL );
	MIX_ASSERT( srcSize != 0 );

	Device::TextureLoaderPluginList::iterator it_begin = m_TexLoaderPluginList.begin();
	Device::TextureLoaderPluginList::iterator it_end = m_TexLoaderPluginList.end();
	Device::TextureLoaderPluginList::iterator it;

	Device::TextureSource texSrc( pSrc, srcSize );
	Boolean bContinue = True;

	Mix::Graphics::ITexture* pTexture = NULL;

	for( it = it_begin; ( it != it_end ) && ( bContinue == True ); ++it )
	{
		int err = ( *it )->Load( &texSrc, &m_TexKit );

		if( err == 0 )
		{
			const Device::TEXTURE_DESC& desc = m_TexKit.GetDesc();

			if( ( desc.format == Mix::Graphics::FMT_UNKNOWN ) ||
				( desc.width == 0 ) ||
				( desc.height == 0 ) )
			{
				bContinue = False;
			}
			else
			{
				bContinue = False;

				pTexture = OnCreateTexture( pFilePath, desc, m_TexKit.GetSubResourceCount(), m_TexKit.GetSubResources() );
			}
		}
		else if( err < 0 )
		{
			bContinue = False;
		}
		else
		{
			//継続して次のプラグインを探すため、ポインタを先頭に戻す
			texSrc.Seek( Mix::Plugin::IReader::BEGIN, 0 );
		}

		m_TexKit.Reset();
	}

	return pTexture;
}

UInt32 Device::GetTextureDimension( const void* pSrc, UInt32 srcSize )
{
	if( ( 4 + sizeof( DDSURFACEDESC2 ) ) > srcSize )
	{
		return 2;
	}

	const UInt8* pAddr = static_cast<const UInt8*>( pSrc );
	UInt32 magicNumber = *reinterpret_cast<const UInt32*>( &( pAddr[0] ) );
	UInt32 dim = 2;

	if( magicNumber == 0x20534444/*DDS*/ )
	{
		const DDSURFACEDESC2* pDesc = reinterpret_cast<const DDSURFACEDESC2*>( &( pAddr[4] ) );

		if( pDesc->dwSize == 124 )
		{
			if( MIX_TESTBIT( pDesc->dwFlags, DDSD_DEPTH ) == DDSD_DEPTH )
			{
				//3Dテクスチャ
				dim = 3;
			}
			else
			{
				//2Dテクスチャ
				;
			}
		}
	}

	return dim;
}

UInt32 Device::GetVertexElementSize( UInt32 format )
{
	MIX_ASSERT( ( sizeof( Device::VERTEX_ELEMENT_SIZE_TABLE ) / sizeof( UInt32 ) ) > format );
	return Device::VERTEX_ELEMENT_SIZE_TABLE[format];
}

Boolean Device::LoadPlugin( Mix::Plugin::TYPE type, HMODULE hModule )
{
	MIX_ASSERT( type == Mix::Plugin::TEXTURE_LOADER );
	MIX_ASSERT( hModule != NULL );

	Device::TextureLoaderPlugin* pPlugin = Device::TextureLoaderPlugin::CreateInstance( hModule );
	if( pPlugin == NULL )
	{
		return False;
	}

	m_TexLoaderPluginList.push_back( pPlugin );

	return True;
}

void Device::Dispose( void )
{
	OnDispose();

	if( m_TexLoaderPluginList.size() > 0 )
	{
		for( Device::TextureLoaderPluginList::iterator it = m_TexLoaderPluginList.begin(); it != m_TexLoaderPluginList.end(); ++it )
		{
			( *it )->Destroy();
		}

		m_TexLoaderPluginList.clear();
	}

	MIX_RELEASE( m_pFileMgr );
	MIX_RELEASE( m_pEngine );
}

void Device::AddDeviceObject( Mix::Graphics::Common::DeviceObject* pDeviceObject )
{
	m_List.push_back( pDeviceObject );
}

void Device::RemoveDeviceObject( Mix::Graphics::Common::DeviceObject* pDeviceObject )
{
	m_List.remove( pDeviceObject );
}

void Device::InvalidateDeviceObject( void )
{
	for( DeviceObjectList::iterator it = m_List.begin(); it != m_List.end(); ++it )
	{
		( *it )->Invalidate();
	}
}

void Device::ValidateDeviceObject( void )
{
	for( DeviceObjectList::iterator it = m_List.begin(); it != m_List.end(); ++it )
	{
		( *it )->Validate();
	}
}

const wchar_t* Device::GetFormatText( Mix::Graphics::FORMAT format )
{
	MIX_ASSERT( ( sizeof( Device::FORMAT_TEXT_TABLE ) / sizeof( wchar_t* ) ) > format );
	return Device::FORMAT_TEXT_TABLE[format];
}

}}}
