#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib,"d3dxof.lib")
#pragma comment(lib,"d3dx9d.lib")
#pragma comment(lib,"dxguid.lib")

#include "mof/private/GraphicsDeviceImpl.hpp"
#include "mof/ConsoleIO.hpp"
#include <list>
#include <stdexcept>
#include "mof/private/VertexFVF.hpp"
#include "mof/Material.hpp"
#include "mof/private/TextureImpl.hpp"

namespace 
{
	
	HWND m_hWnd = NULL;
	int m_width;
	int m_height;
	LPDIRECT3DDEVICE9	m_pDevice = NULL;
	LPDIRECT3D9	m_pD3D = NULL;
	D3DPRESENT_PARAMETERS* m_pParam = NULL;
	IDirect3DStateBlock9* m_pNormalBlendingBlock = NULL;
	IDirect3DStateBlock9* m_pAddBlendingBlock = NULL;
	IDirect3DStateBlock9* m_pAlphaBlendingBlock = NULL;
	int m_currentStateBlock;
	mof::Matrix3D m_worldTransform;
	mof::Matrix3D m_viewTransform;
	mof::Matrix3D m_projectionTransform;
	bool m_flagActive;
	bool m_flagDeviceLost;
	bool ignore_drawing_ = false;///< viewportが制御
}


inline bool isOK(HRESULT hr){
	return hr == S_OK;
}


namespace mof
{
    template void GraphicsDevice::drawVertexArray( const VertexXYZRHWCUV& , const VertexXYZRHWCUV& , PRIMITIVE_TYPE );
    template void GraphicsDevice::drawVertexArray( const VertexXYZRHWC& , const VertexXYZRHWC& , PRIMITIVE_TYPE);
    template void GraphicsDevice::drawVertexArray( const VertexXYZCUV& , const VertexXYZCUV& , PRIMITIVE_TYPE);
    template void GraphicsDevice::drawVertexArray( const VertexXYZNUV& , const VertexXYZNUV& , PRIMITIVE_TYPE);
    template void GraphicsDevice::drawVertexArray( const VertexXYZC& , const VertexXYZC& , PRIMITIVE_TYPE);


//{{{ initialize
    //TODO 関数分割
    void GraphicsDevice::initialize( HWND hWnd, int adapter_id, int width , int height , bool fullscreen )
    {
    
	    HRESULT	hr;
	    m_hWnd = hWnd;
	    m_width = width;
	    m_height = height;

	    if( NULL == ( m_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	    {
		    throw std::runtime_error( "Failed --- Direct3DCreate9" );
    	}

	    m_pParam = new D3DPRESENT_PARAMETERS;
        ZeroMemory( m_pParam, sizeof(D3DPRESENT_PARAMETERS) );
	    m_pParam->Windowed = (fullscreen)? FALSE : TRUE;		//TRUE=Window,FALSE=FULLSCREEN
	    m_pParam->BackBufferCount = 1;
	    m_pParam->PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	    if( fullscreen )
	    {
		    //フルスクリーン時はバックバッファの幅、高さを再指定
		    m_pParam->BackBufferWidth = width;		//幅
		    m_pParam->BackBufferHeight = height;		//高さ
		    m_pParam->BackBufferFormat = D3DFMT_X8R8G8B8;	//16bit
		    //m_pParam->BackBufferFormat = D3DFMT_R5G6B5;	//16bit
		    m_pParam->FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	    }
	    else
	    {
		    m_pParam->BackBufferFormat = D3DFMT_UNKNOWN;	//現在の画面モードを利用
	    }
        m_pParam->SwapEffect = D3DSWAPEFFECT_DISCARD;
	    // Z バッファの自動作成
	    m_pParam->EnableAutoDepthStencil = 1;
	    m_pParam->AutoDepthStencilFormat = D3DFMT_D16;
    

	    

	    //HAL(pure vp)
	    if
	    ( 
	        FAILED
	        ( 
	            hr = 
	            m_pD3D->CreateDevice
	                (
	                   adapter_id, D3DDEVTYPE_HAL , m_hWnd ,
		               D3DCREATE_HARDWARE_VERTEXPROCESSING , m_pParam , &m_pDevice
		            )
		    )
		)
	    {
		//HAL(soft vp)
	    	if
	    	(
	    	    FAILED
	    	    ( 
	    	        hr =
	    	        m_pD3D->CreateDevice
		            (
		                adapter_id, D3DDEVTYPE_HAL , m_hWnd ,
		    	        D3DCREATE_SOFTWARE_VERTEXPROCESSING , m_pParam , &(m_pDevice) 
		    	    )
		    	)
		    )
		    {
			    //REF
		    	if
		    	( 
		    	    FAILED(
		    	        hr = 
		    	        m_pD3D->CreateDevice
		    	        (
		    	            adapter_id, D3DDEVTYPE_REF , m_hWnd ,
		    		        D3DCREATE_HARDWARE_VERTEXPROCESSING , m_pParam , &m_pDevice
		    		    )
		    		)
		        )
		    	{
			    	delete m_pParam;
			    	m_pD3D->Release();
			    	throw std::runtime_error(std::string("Failed --- CreateDevice"));
			    }
		    } // if
	    } // if
    
	    setViewport( mof::Rectangle<int>(0 , 0 , width , height) );
	
        //
        m_pDevice->SetSamplerState(0 , D3DSAMP_MAGFILTER , D3DTEXF_LINEAR ); //D3DTEXF_POINT 
        m_pDevice->SetSamplerState(0 , D3DSAMP_MINFILTER , D3DTEXF_LINEAR ); //D3DTEXF_POINT 
	
	
		m_pDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
	    m_pDevice->BeginStateBlock( );
	    m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE , TRUE );
	    m_pDevice->SetRenderState( D3DRS_SRCBLEND,D3DBLEND_SRCALPHA );
	    m_pDevice->SetRenderState( D3DRS_DESTBLEND,D3DBLEND_ONE );
	    m_pDevice->SetTextureStageState( 0 , D3DTSS_ALPHAOP,D3DTOP_MODULATE ); //ARG1とARG2のα値を乗算してα値を取得します。
	    m_pDevice->SetTextureStageState( 0 , D3DTSS_ALPHAARG1,D3DTA_DIFFUSE ); //テクスチャのα値
	    m_pDevice->SetTextureStageState( 0 , D3DTSS_ALPHAARG2,D3DTA_TEXTURE ); //頂点のα
	    m_pDevice->SetTextureStageState( 0 , D3DTSS_COLOROP,D3DTOP_MODULATE ); //ARG1とARG2のカラーの値を乗算します。
	    m_pDevice->SetTextureStageState( 0 , D3DTSS_COLORARG1,D3DTA_TEXTURE ); //テクスチャのカラー
	    m_pDevice->SetTextureStageState( 0 , D3DTSS_COLORARG2,D3DTA_DIFFUSE ); //頂点のカラー
	    m_pDevice->EndStateBlock( &m_pAddBlendingBlock );

	    m_pDevice->BeginStateBlock( );
	    m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE , TRUE );
	    m_pDevice->SetRenderState( D3DRS_SRCBLEND , D3DBLEND_SRCALPHA );
	    m_pDevice->SetRenderState( D3DRS_DESTBLEND , D3DBLEND_INVSRCALPHA );
    	m_pDevice->SetTextureStageState( 0 ,D3DTSS_ALPHAOP , D3DTOP_SELECTARG2 ); //ARG1とARG2のα値を乗算してα値を取得します。
    	m_pDevice->SetTextureStageState( 0 ,D3DTSS_ALPHAARG1 , D3DTA_DIFFUSE ); //テクスチャのα値
    	m_pDevice->SetTextureStageState( 0 ,D3DTSS_ALPHAARG2 , D3DTA_TEXTURE ); //頂点のα値
    	m_pDevice->SetTextureStageState( 0 ,D3DTSS_COLOROP , D3DTOP_MODULATE ); //ARG1とARG2のカラーの値を乗算します。
    	m_pDevice->SetTextureStageState( 0 ,D3DTSS_COLORARG1 , D3DTA_DIFFUSE ); //テクスチャのカラー
    	m_pDevice->SetTextureStageState( 0 ,D3DTSS_COLORARG2 , D3DTA_TEXTURE ); //頂点のカラー
    	m_pDevice->EndStateBlock( &m_pNormalBlendingBlock );

	    m_pDevice->BeginStateBlock( );
	    m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE , TRUE );
	    m_pDevice->SetRenderState( D3DRS_SRCBLEND , D3DBLEND_SRCALPHA );
	    m_pDevice->SetRenderState( D3DRS_DESTBLEND , D3DBLEND_INVSRCALPHA );
	    m_pDevice->SetTextureStageState( 0,D3DTSS_ALPHAOP , D3DTOP_MODULATE ); //ARG1とARG2のα値を乗算してα値を取得します。
	    m_pDevice->SetTextureStageState( 0,D3DTSS_ALPHAARG1 , D3DTA_DIFFUSE ); //テクスチャのα値
	    m_pDevice->SetTextureStageState( 0,D3DTSS_ALPHAARG2 , D3DTA_TEXTURE ); //頂点のα値
	    m_pDevice->SetTextureStageState( 0,D3DTSS_COLOROP , D3DTOP_MODULATE ); //ARG1とARG2のカラーの値を乗算します。
	    m_pDevice->SetTextureStageState( 0,D3DTSS_COLORARG1 , D3DTA_DIFFUSE ); //テクスチャのカラー
	    m_pDevice->SetTextureStageState( 0,D3DTSS_COLORARG2 , D3DTA_TEXTURE ); //頂点のカラー
	    m_pDevice->EndStateBlock( &m_pAlphaBlendingBlock );
	
	    setProjectionTransform(0.1f , 100);
		setWorldTransform(m_worldTransform);
    } // function initialize
//}}}
//{{{ finalize
    void GraphicsDevice::finalize( )
    {
	    m_pAddBlendingBlock->Release();
	    m_pNormalBlendingBlock->Release();
	    m_pAlphaBlendingBlock->Release();
	    if(m_pDevice != NULL)m_pDevice->Release();
	    if(m_pD3D != NULL)m_pD3D->Release();
	    delete m_pParam;
    }
//}}}
//{{{ beginScene
    void GraphicsDevice::beginScene( )
    {
	

	    if(true == m_flagDeviceLost)
	    { 
	    	Sleep(100);	// 0.1秒待つ
	    	if(/*m_flagActive == true &&*/ m_pDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET )
	    	{
			    //---デバイス復帰を試みる
		
		    	if( FAILED( m_pDevice->Reset( m_pParam) ) )return;
				
		    	//デバイスの復帰に成功
		    	m_flagDeviceLost = false;
		    	//---ステートを復帰
		    	DEBUG_PRINT( "DeviceRecovered!" );
		    }
		    else return;
		}
	
	

	    if( !isOK( m_pDevice->BeginScene( ) ) )
	    {
	    	throw std::runtime_error("Failed --- BeginScene");
	    }


    } // function beginScene
//}}}	
//{{{ endScene
    void GraphicsDevice::endScene( )
    {
		
	    if( !isOK( m_pDevice->EndScene() ) )
	    {
		    throw std::runtime_error("Failed --- EndScene");
	    }

	    //バックバッファからプライマリバッファへ転送
	    HRESULT hr = m_pDevice->Present( NULL, NULL, NULL, NULL );
	
	    if( hr == D3DERR_DEVICELOST && false == m_flagDeviceLost )
	    {
			DEBUG_PRINT( "DeviceLost!" );
			m_flagDeviceLost = true;
			//リソースの解放処理

		}

	
	    if( !isOK( hr ) )throw std::runtime_error("Present");
	

    } // function endScene
//}}}
//{{{ setWorldTransform
    void GraphicsDevice::setWorldTransform( const mof::Matrix3D& matrix )
    {
    	m_worldTransform = matrix;
    	D3DXMATRIX mat;
    	for(int i = 0 ; i < 4 ; ++i ){
    		for(int j = 0 ; j < 4 ; ++j){
    			mat(i , j) = matrix.at(i , j);
    		}
    	}
    	m_pDevice->SetTransform( D3DTS_WORLD, &mat );
    }
//}}}
//{{{ setProjectionTransform
    void GraphicsDevice::setProjectionTransform( real min , real max )
    {
	    D3DXMATRIX matProj;
	    D3DXMatrixPerspectiveFovLH
	    ( 
	        &matProj, D3DX_PI / 4.0f, 
	    	static_cast<float>(getViewportWidth()) / static_cast<float>(getViewportHeight()) , min, max
	    );

	    mof::Matrix3D::Array arry;
	    for(int i = 0 ; i < 4 ; ++i ){
	    	for(int j = 0 ; j < 4 ; ++j){
	    		arry.elements[i][j] = matProj( i , j );
	    	}
	    }
	    m_projectionTransform = mof::Matrix3D( arry );
	    m_pDevice->SetTransform( D3DTS_PROJECTION , &matProj );

    } 
//}}}
//{{{ setViewTransform
    void GraphicsDevice::setViewTransform( const mof::Matrix3D& matrix )
    {
    	m_viewTransform = matrix;
    	D3DXMATRIX mat;
	    for(int i = 0 ; i < 4 ; ++i ){
		    for(int j = 0 ; j < 4 ; ++j){
		    	mat(i , j) = matrix.at(i , j);
		    }
	    }
	    m_pDevice->SetTransform( D3DTS_VIEW , &mat );
    }
//}}}
//{{{ getWorldTransform
    const mof::Matrix3D& GraphicsDevice::getWorldTransform( ) 
    {
	    return m_worldTransform;
    }
//}}}
//{{{ getProjectionTransform
    const mof::Matrix3D& GraphicsDevice::getProjectionTransform() 
    {
	    return m_projectionTransform;
    }
//}}}	
//{{{ getViewTransform
    const mof::Matrix3D& GraphicsDevice::getViewTransform()
    {
    	return m_viewTransform;
    }
//}}}
//{{{ get_num_of_displays
	int GraphicsDevice::get_num_of_displays()
	{
		// TODO
		return 0;
	}
//}}}
//{{{ get_caps
	std::vector<mof::Vector2D> GraphicsDevice::get_caps(int adapter_id)
	{

		LPDIRECT3D9	tmp_pD3D = NULL;
		if (m_pD3D != NULL) tmp_pD3D = m_pD3D;
		else tmp_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
		
		std::vector<mof::Vector2D> resolutions;	
		int num = tmp_pD3D->GetAdapterModeCount(adapter_id, D3DFMT_X8R8G8B8);
		D3DDISPLAYMODE mode = {0, 0, 0, D3DFMT_UNKNOWN};
		for (int i = 0; i < num; i++) {
			tmp_pD3D->EnumAdapterModes(adapter_id, D3DFMT_X8R8G8B8, i, &mode);
			if (mode.RefreshRate == 60) {
				resolutions.push_back(mof::Vector2D(static_cast<float>(mode.Width), static_cast<float>(mode.Height)));
			}
		}
		if (m_pD3D == NULL) tmp_pD3D->Release();
		return resolutions;
	}
//}}}
//{{{ setZBuffer	
    void GraphicsDevice::setZBuffer( bool available )
    {
	    if( available )m_pDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE );
	    else m_pDevice->SetRenderState( D3DRS_ZENABLE , D3DZB_FALSE );	    
    }
//}}}
//{{{ lightEnable
    void GraphicsDevice::lightEnable( bool available )
    {
	    m_pDevice->SetRenderState( D3DRS_LIGHTING , available );
    }
//}}}
//{{{ clearZBuffer
    void GraphicsDevice::clearZBuffer()
    {
	    m_pDevice->Clear( 0 , NULL , D3DCLEAR_ZBUFFER , 0 , 1.0f, 0 );
    }
//}}}
//{{{ clearRenderTarget
    void GraphicsDevice::clearRenderTarget( mof::Color color )
    {
	    m_pDevice->Clear( 0 , 0 , D3DCLEAR_TARGET , color ,  0 , 0 );
    }
//}}}
//{{{ setAlphaBlendingMode
    void GraphicsDevice::setAlphaBlendingMode( const int flag )
    {
    	if(flag == m_currentStateBlock )return;

    	if( flag == mof::GraphicsDevice::BLENDING_MODE_ADD ){
    		m_pAddBlendingBlock->Apply();
    	}
	    else if(flag == mof::GraphicsDevice::BLENDING_MODE_BASIC)
	    {
		    m_pNormalBlendingBlock->Apply();
	    }
	    else if( flag == mof::GraphicsDevice::BLENDING_MODE_ALPHA )
	    {
		    m_pAlphaBlendingBlock->Apply();
	    }

	    m_currentStateBlock = flag;
    }
//}}}
//{{{ to2DPosition
    Vector2D GraphicsDevice::to2DPosition( const mof::Vector3D& position )
    {
		mof::Matrix3D matrix = m_viewTransform * m_projectionTransform;
	    int hWidth = getViewportWidth() / 2;
	    int hHeight = getViewportHeight() / 2;
	    mof::Vector3D tmpPosition = position * matrix;
		tmpPosition = tmpPosition / tmpPosition.z;
	 
	    return mof::Vector2D
	    (
	        static_cast<int>(tmpPosition.x * hWidth + hWidth ) , 
			static_cast<int>(-tmpPosition.y * hHeight + hHeight)
		);
    }
//}}}
//{{{ setViewport
    void GraphicsDevice::setViewport( const mof::Rectangle<int>& area )
    {
		Rectangle<int> client_region = getClientRegion();
		client_region = Rectangle<int>(0, 0, client_region.getWidth(), client_region.getHeight());
		Rectangle<int> required_region = area;

		ignore_drawing_ = true;

		// viewport が完全にクライアント領域外なら、この関数は何もしない
		if (required_region.beginX > client_region.endX) return;
		if (required_region.beginY > client_region.endY) return;
		if (required_region.endX < client_region.beginX) return;
		if (required_region.endY < client_region.beginY) return;


		// viewport がクライアント領域をはみ出さないように調整する
		if (required_region.beginX < client_region.beginX) required_region.beginX = client_region.beginX;
		if (required_region.beginY < client_region.beginY) required_region.beginY = client_region.beginY;
		if (required_region.endX   > client_region.endX)   required_region.endX   = client_region.endX;
		if (required_region.endY   > client_region.endY)   required_region.endY   = client_region.endY;
    	
		if (0 == required_region.getWidth() || 0 == required_region.getHeight()) return;

		ignore_drawing_ = false;
    	D3DVIEWPORT9 vp;
		vp.X = required_region.beginX;
    	vp.Y = required_region.beginY;
    	vp.Width = required_region.getWidth( );
    	vp.Height = required_region.getHeight( );
    	vp.MinZ = 0.0f;
    	vp.MaxZ = 1.0f;
    	if( FAILED( m_pDevice->SetViewport( &vp ) ) )
    	{
		    throw std::runtime_error( "Failed --- SetViewport" );
	    }
    }
//}}}
//{{{ getViewportWidth
    int GraphicsDevice::getViewportWidth( ) 
    {
	    D3DVIEWPORT9 vp;
	    if( FAILED(m_pDevice->GetViewport( &vp ) ) )
	    {
	    	throw std::runtime_error("Failed --- getViewportWidth");
	    }
	    return vp.Width;
    }
//}}}
//{{{ getViewportHeight
    int GraphicsDevice::getViewportHeight() {
	    D3DVIEWPORT9 vp;
	    if( FAILED( m_pDevice->GetViewport( &vp ) ) )
	    {
	    	throw std::runtime_error("Failed --- getViewportHeight");
	    }
	    return vp.Height;
    }
//}}}
//{{{ drawVertexArray
    template <class T>
    void GraphicsDevice::drawVertexArray( const T& front , const T& last , mof::PRIMITIVE_TYPE type )
    {
	    HRESULT hr = E_FAIL;
	    if (&last < &front) return;
	    int length = &last - &front + 1; //頂点数
		if (ignore_drawing_) return;

	    m_pDevice->SetFVF( mof::getFVF<T>() );

	    if(type == mof::PRIMITIVE_TYPE_TRIANGLESTRIP)
	    {
	    	hr = m_pDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP , length > 3 ? length - 2 : 1 , (const void*)&front , sizeof(T) );
	    }
    	else if( type == PRIMITIVE_TYPE_TRIANGLELIST )
    	{
	    	hr = m_pDevice->DrawPrimitiveUP( D3DPT_TRIANGLELIST , length / 3 , (const void*)&front , sizeof(T) );
	    }
	    else if( type == mof::PRIMITIVE_TYPE_LINELIST )
	    {
	    	hr = m_pDevice->DrawPrimitiveUP(D3DPT_LINELIST , length / 2 , (const void*)&front , sizeof(T) );
    	}
	
	    if( FAILED( hr ) )
	    {
		    throw std::runtime_error("Failed -- DrawPrimitiveUP");
	    }

    } // function drawVertexArray
//}}}
//{{{ setMaterial
    void GraphicsDevice::setMaterial(const mof::Material& material){
	    D3DMATERIAL9 mat;

	    memcpy(static_cast<void*>(&mat) , static_cast<const void*>(&material) , sizeof(D3DMATERIAL9));

	    if( FAILED( m_pDevice->SetMaterial( &mat ) ) )
	    {
		    throw std::runtime_error("Faild --- SetMaterial");
	    }
    }
//}}}
//{{{ setTexture
    void GraphicsDevice::setTexture( const mof::Texture* pTexture )
    {
	    if( pTexture == NULL )m_pDevice->SetTexture( 0 , NULL );
	    else m_pDevice->SetTexture( 0 , pTexture->m_pImpl->pTexture );
    }
//}}}
//{{{ getClientRegion
    mof::Rectangle<int> GraphicsDevice::getClientRegion() 
    {
	    RECT rect;
	    GetWindowRect(m_hWnd , &rect);
	    POINT point;
	    point.x = rect.left;
	    point.y = rect.top;
	    ScreenToClient(m_hWnd , &point);
	    int beginX = rect.left - point.x;
	    int beginY = rect.top - point.y;
	    return mof::Rectangle<int>(beginX , beginY , beginX + m_width , beginY + m_height);
    }
//}}} 
//{{{ getRawDevice
    LPDIRECT3DDEVICE9 GraphicsDevice::getRawDevice( )
    {
        return m_pDevice;
    }
//}}} 
} // namespace GraphicsDevice
