
#include "Gwen/Renderers/DirectX9.h"
#include "Gwen/Utility.h"
#include "Gwen/Font.h"
#include "Gwen/Texture.h"
#include "Gwen/WindowProvider.h"

#define D3DFVF_VERTEXFORMAT2D ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 )

struct FontData
{
	ID3DXFont*	pFont;
	int			iSpaceWidth;
};

namespace Gwen
{
	namespace Renderer
	{
		DirectX9::DirectX9( IDirect3DDevice9* pDevice )
		{
			m_pD3D = NULL;
			m_pDevice = pDevice;
			m_iVertNum = 0;

			for ( int i=0; i<MaxVerts; i++ )
			{
				m_pVerts[ i ].z = 0.5f;
				m_pVerts[ i ].rhw = 1.0f;
			}
		}

		DirectX9::~DirectX9()
		{

		}

		void DirectX9::Begin()
		{
			m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); 
			m_pDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
			m_pDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

			m_pDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
			m_pDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
			m_pDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_NONE );

			m_pDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
			m_pDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );

			m_pDevice->SetTextureStageState( 0, D3DTSS_COLOROP,		D3DTOP_MODULATE );
			m_pDevice->SetTextureStageState( 0, D3DTSS_COLORARG1,	D3DTA_TEXTURE );
			m_pDevice->SetTextureStageState( 0, D3DTSS_COLORARG2,	D3DTA_CURRENT );

			m_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,		D3DTOP_MODULATE );
			m_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1,	D3DTA_TEXTURE );
			m_pDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2,	D3DTA_CURRENT );

			m_pDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE );
			m_pDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE );
		}

		void DirectX9::End()
		{
			Flush();
		}

		void DirectX9::Flush()
		{
			if ( m_iVertNum > 0 )
			{
				m_pDevice->SetFVF( D3DFVF_VERTEXFORMAT2D );
				m_pDevice->DrawPrimitiveUP( D3DPT_TRIANGLELIST, m_iVertNum/3, &m_pVerts[0], sizeof(VertexFormat) );

				m_iVertNum = 0;
			}
		}

		void DirectX9::AddVert( int x, int y )
		{
			if ( m_iVertNum >= MaxVerts-1 )
			{
				Flush();
			}

			m_pVerts[ m_iVertNum ].x = (float)x;
			m_pVerts[ m_iVertNum ].y = (float)y;
			m_pVerts[ m_iVertNum ].color = m_Color;

			m_iVertNum++;
		}

		void DirectX9::AddVert( int x, int y, float u, float v )
		{
			if ( m_iVertNum >= MaxVerts-1 )
			{
				Flush();
			}

			m_pVerts[ m_iVertNum ].x = -0.5f + (float)x;
			m_pVerts[ m_iVertNum ].y = -0.5f + (float)y;
			m_pVerts[ m_iVertNum ].u = u;
			m_pVerts[ m_iVertNum ].v = v;

			m_pVerts[ m_iVertNum ].color = m_Color;

			m_iVertNum++;
		}

		void DirectX9::DrawFilledRect( Gwen::Rect rect )
		{
			if ( m_pCurrentTexture != NULL )
			{
				Flush();
				m_pDevice->SetTexture( 0, NULL );
				m_pCurrentTexture = NULL;
			}	

			Translate( rect );

			AddVert( rect.x, rect.y );
			AddVert( rect.x+rect.w, rect.y );
			AddVert( rect.x, rect.y + rect.h );

			AddVert( rect.x+rect.w, rect.y );
			AddVert( rect.x+rect.w, rect.y+rect.h );
			AddVert( rect.x, rect.y + rect.h );
		}

		void DirectX9::SetDrawColor(Gwen::Color color)
		{
			m_Color = D3DCOLOR_ARGB( color.a, color.r, color.g, color.b );
		}

		void DirectX9::LoadFont( Gwen::Font* font )
		{
			m_FontList.push_back( font );
			// Scale the font according to canvas
			font->realsize = font->size * Scale();

			D3DXFONT_DESC fd;

			memset( &fd, 0, sizeof(fd) );

			wcscpy_s( fd.FaceName, LF_FACESIZE, font->facename.c_str() );

			fd.Width = 0;
			fd.MipLevels = 1;
			fd.CharSet = DEFAULT_CHARSET;
			fd.Height = font->realsize * -1.0f;
			fd.OutputPrecision = OUT_DEFAULT_PRECIS;
			fd.Italic = 0;
			fd.Weight = font->bold ? FW_BOLD : FW_NORMAL;
#ifdef CLEARTYPE_QUALITY
			fd.Quality = font->realsize < 14 ? DEFAULT_QUALITY : CLEARTYPE_QUALITY;
#else 
			fd.Quality = PROOF_QUALITY;
#endif 
			fd.PitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;

			LPD3DXFONT pD3DXFont;
			HRESULT hr = D3DXCreateFontIndirect( m_pDevice, &fd, &pD3DXFont );

			FontData*	pFontData = new FontData();
			pFontData->pFont = pD3DXFont;

			// ID3DXFont doesn't measure trailing spaces, so we measure the width of a space here and store it
			// in the font data - then we can add it to the width when we measure text with trailing spaces.
			{
				RECT rctA = {0,0,0,0};
				pFontData->pFont->DrawTextW( NULL, L"A", -1, &rctA, DT_CALCRECT | DT_LEFT | DT_TOP | DT_SINGLELINE, 0 );

				RECT rctSpc = {0,0,0,0};
				pFontData->pFont->DrawTextW( NULL, L"A A", -1, &rctSpc, DT_CALCRECT | DT_LEFT | DT_TOP | DT_SINGLELINE, 0 );

				pFontData->iSpaceWidth = rctSpc.right - rctA.right * 2;
			}

			font->data = pFontData;
		}

		void DirectX9::FreeFont( Gwen::Font* pFont )
		{
			m_FontList.remove( pFont );

			if ( !pFont->data ) return;

			FontData* pFontData = (FontData*) pFont->data;

			if ( pFontData->pFont )
			{
				pFontData->pFont->Release();
				pFontData->pFont = NULL;
			}

			delete pFontData;
			pFont->data = NULL;

		}

		void DirectX9::RenderText( Gwen::Font* pFont, Gwen::Point pos, const Gwen::UnicodeString& text )
		{
			Flush();

			// If the font doesn't exist, or the font size should be changed
			if ( !pFont->data || fabs( pFont->realsize - pFont->size * Scale() ) > 2 )
			{
				FreeFont( pFont );
				LoadFont( pFont );
			}

			FontData* pFontData = (FontData*) pFont->data;
			
			Translate( pos.x, pos.y );

			RECT ClipRect = { pos.x, pos.y, 0, 0 };
			pFontData->pFont->DrawTextW( NULL, text.c_str(), -1, &ClipRect, DT_LEFT | DT_TOP | DT_NOCLIP | DT_SINGLELINE, m_Color );
		}

		Gwen::Point DirectX9::MeasureText( Gwen::Font* pFont, const Gwen::UnicodeString& text )
		{
			// If the font doesn't exist, or the font size should be changed
			if ( !pFont->data || fabs( pFont->realsize - pFont->size * Scale() ) > 2 )
			{
				FreeFont( pFont );
				LoadFont( pFont );
			}

			FontData* pFontData = (FontData*) pFont->data;

			Gwen::Point size;

			if ( text.empty() )
			{
				RECT rct = {0,0,0,0};
				pFontData->pFont->DrawTextW( NULL, L"W", -1, &rct, DT_CALCRECT, 0 );

				return Gwen::Point( 0, rct.bottom );
			}

			RECT rct = {0,0,0,0};
			pFontData->pFont->DrawTextW( NULL, text.c_str(), -1, &rct, DT_CALCRECT | DT_LEFT | DT_TOP | DT_SINGLELINE, 0 );

			for (int i=text.length()-1; i>=0 && text[i] == L' '; i-- )
			{
				rct.right += pFontData->iSpaceWidth;
			}

			return Gwen::Point( rct.right / Scale(), rct.bottom / Scale() );

		}

		void DirectX9::StartClip()
		{
			Flush();

			m_pDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, TRUE );

			const Gwen::Rect& rect = ClipRegion();

			RECT r;

			r.left = ceil( ((float)rect.x) * Scale() );
			r.right = ceil(((float)(rect.x + rect.w)) * Scale());
			r.top = ceil( (float)rect.y * Scale() );
			r.bottom = ceil( ((float)(rect.y + rect.h)) * Scale() );

			m_pDevice->SetScissorRect( &r );
		}

		void DirectX9::EndClip()
		{
			Flush();
			m_pDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, FALSE );
		}

		void DirectX9::DrawTexturedRect( Gwen::Texture* pTexture, Gwen::Rect rect, float u1, float v1, float u2, float v2 )
		{

			IDirect3DTexture9* pImage = (IDirect3DTexture9*) pTexture->data;

			// Missing image, not loaded properly?
			if ( !pImage )
			{
				return DrawMissingImage( rect );
			}

			Translate( rect );

			if ( m_pCurrentTexture != pImage )
			{
				Flush();
				m_pDevice->SetTexture( 0, pImage );
				m_pCurrentTexture = pImage;
			}		

			AddVert( rect.x, rect.y,			u1, v1 );
			AddVert( rect.x+rect.w, rect.y,		u2, v1 );
			AddVert( rect.x, rect.y + rect.h,	u1, v2 );

			AddVert( rect.x+rect.w, rect.y,		u2, v1 );
			AddVert( rect.x+rect.w, rect.y+rect.h, u2, v2 );
			AddVert( rect.x, rect.y + rect.h, u1, v2 );
			
		}

		void DirectX9::LoadTexture( Gwen::Texture* pTexture )
		{
			IDirect3DTexture9* ptr = NULL;
			D3DXIMAGE_INFO ImageInfo;
			HRESULT hr = D3DXCreateTextureFromFileExW( m_pDevice, pTexture->name.GetUnicode().c_str(), 0, 0, D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, &ImageInfo, NULL, &ptr );
			if ( hr != S_OK )
			{
				return;
			}

			pTexture->data = ptr;
			pTexture->width = ImageInfo.Width;
			pTexture->height = ImageInfo.Height;
		}

		void DirectX9::FreeTexture( Gwen::Texture* pTexture )
		{

			IDirect3DTexture9* pImage = (IDirect3DTexture9*) pTexture->data;
			if ( !pImage ) return;

			pImage->Release();
			pTexture->data = NULL;

			return;
		}

		Gwen::Color DirectX9::PixelColour( Gwen::Texture* pTexture, unsigned int x, unsigned int y, const Gwen::Color& col_default )
		{
			IDirect3DTexture9* pImage = (IDirect3DTexture9*) pTexture->data;
			if ( !pImage ) return col_default;

			IDirect3DSurface9* pSurface = NULL;

			if ( pImage->GetSurfaceLevel( 0, &pSurface ) != S_OK ) return col_default;
			if ( !pSurface ) return col_default;

			D3DLOCKED_RECT lockedRect;
			pSurface->LockRect( &lockedRect, NULL, D3DLOCK_READONLY );
			DWORD* pixels = (DWORD*)lockedRect.pBits;
			D3DXCOLOR color = pixels[lockedRect.Pitch / sizeof(DWORD) * y + x];
			pSurface->UnlockRect();

			pSurface->Release();

			return Gwen::Color( color.r*255, color.g*255, color.b*255, color.a*255 );
		}

		void DirectX9::Release()
		{
			Font::List::iterator it = m_FontList.begin();

			while ( it != m_FontList.end() )
			{
				FreeFont( *it );
				it = m_FontList.begin();
			}
		}

		void DirectX9::FillPresentParameters( Gwen::WindowProvider* pWindow, D3DPRESENT_PARAMETERS& Params )
		{
			HWND pHWND = (HWND)pWindow->GetWindow();

			RECT ClientRect;
			GetClientRect( pHWND, &ClientRect );

			ZeroMemory( &Params, sizeof( Params ) );

			Params.Windowed						= true;
			Params.SwapEffect					= D3DSWAPEFFECT_DISCARD;
			Params.BackBufferWidth				= ClientRect.right;
			Params.BackBufferHeight				= ClientRect.bottom;
			Params.FullScreen_RefreshRateInHz	= D3DPRESENT_RATE_DEFAULT;
			Params.BackBufferFormat				= D3DFMT_X8R8G8B8;
			Params.PresentationInterval			= D3DPRESENT_INTERVAL_IMMEDIATE;
		}

		bool DirectX9::InitializeContext( Gwen::WindowProvider* pWindow )
		{
			HWND pHWND = (HWND)pWindow->GetWindow();

			m_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
			if ( !m_pD3D ) return false;

			

			D3DCAPS9 D3DCaps;
			m_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps );

			DWORD BehaviourFlags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
			if( D3DCaps.VertexProcessingCaps != 0 ) BehaviourFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING;

			D3DPRESENT_PARAMETERS Params;
			FillPresentParameters( pWindow, Params );

			HRESULT hr = m_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, pHWND, D3DCREATE_HARDWARE_VERTEXPROCESSING, &Params, &m_pDevice );
			if ( FAILED(hr) )
			{
				return false;
			}

			return true;
		}

		bool DirectX9::ShutdownContext( Gwen::WindowProvider* pWindow )
		{
			if ( m_pDevice )
			{
				m_pDevice->Release();
				m_pDevice = NULL;
			}

			if ( m_pD3D )
			{
				m_pD3D->Release();
				m_pD3D = NULL;
			}

			return true;
		}

		bool DirectX9::PresentContext( Gwen::WindowProvider* pWindow )
		{
			m_pDevice->Present( NULL, NULL, NULL, NULL );
			return true;
		}

		bool DirectX9::ResizedContext( Gwen::WindowProvider* pWindow, int w, int h )
		{
			// Force setting the current texture again
			m_pCurrentTexture = NULL;

			// Free any unmanaged resources (fonts)
			Release();

			// Get the new window size from the HWND
			D3DPRESENT_PARAMETERS Params;
			FillPresentParameters( pWindow, Params );

			// And reset the device!
			m_pDevice->Reset( &Params );

			return true;
		}

		bool DirectX9::BeginContext( Gwen::WindowProvider* pWindow )
		{
			m_pDevice->BeginScene();
			m_pDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB( 128, 128, 128 ), 1, 0 );

			return true;
		}

		bool DirectX9::EndContext( Gwen::WindowProvider* pWindow )
		{
			m_pDevice->EndScene();
			return true;
		}

	}
}