#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <d3d8.h>
#include <d3dx8math.h>
#include <assert.h>

#include "PajaTypes.h"
#include "BBox2C.h"
#include "ColorC.h"
#include "Vector2C.h"
#include "DeviceInterfaceI.h"
#include "GUIDrawInterfaceI.h"
#include "DX8ViewportC.h"
#include "DX8GUIDrawInterfaceC.h"

using namespace PajaTypes;
using namespace PajaSystem;
using namespace PluginClass;
using namespace Edit;


struct XYZCOLOR_VERTEX {
	float	x, y, z;
	DWORD	color;
};

struct XYZWCOLOR_VERTEX {
	float	x, y, z, w;
	DWORD	color;
};

struct XYZPSIZECOLOR_VERTEX {
	float	x, y, z;
	float	pSize;
	DWORD	color;
};

#define FVF_XYZCOLOR_VERTEX			D3DFVF_XYZ | D3DFVF_DIFFUSE
#define FVF_XYZWCOLOR_VERTEX			D3DFVF_XYZRHW | D3DFVF_DIFFUSE
#define FVF_XYZPSIZECOLOR_VERTEX			D3DFVF_XYZ | D3DFVF_PSIZE | D3DFVF_DIFFUSE

// Helper function to stuff a FLOAT into a DWORD argument
inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }


DX8GUIDrawInterfaceC::DX8GUIDrawInterfaceC() :
	m_pDevice( 0 ),
	m_dwCurColor( 0xffffffff )
{
}

DX8GUIDrawInterfaceC::DX8GUIDrawInterfaceC( DX8DeviceC* pDevice ) :
	m_pDevice( pDevice ),
	m_dwCurColor( 0xffffffff ),
	m_pFont( 0 )
{
}

DX8GUIDrawInterfaceC::~DX8GUIDrawInterfaceC()
{
	delete m_pFont;
}


DataBlockI*
DX8GUIDrawInterfaceC::create()
{
	return new DX8GUIDrawInterfaceC;
}

DX8GUIDrawInterfaceC*
DX8GUIDrawInterfaceC::create_new( DX8DeviceC* pDevice )
{
	return new DX8GUIDrawInterfaceC( pDevice );
}

ClassIdC
DX8GUIDrawInterfaceC::get_class_id() const
{
	return CLASS_DX8_GUIINTERFACE;
}

const char*
DX8GUIDrawInterfaceC::get_class_name()
{
	return "DirectX 8 GUIDI";
}

void
DX8GUIDrawInterfaceC::draw_text( const PajaTypes::Vector2C& rPos, const char* szStr )
{
	if( m_pFont ) {
		DX8ViewportC*	pIface = (DX8ViewportC*)m_pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
		Vector2C	rClientPos = pIface->layout_to_client( rPos );
		m_pFont->DrawText( rClientPos[0], (float32)pIface->get_height() - rClientPos[1], m_dwCurColor, szStr );
	}
}

void
DX8GUIDrawInterfaceC::use_font( HFONT hFont )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;
	delete m_pFont;
	m_pFont = new CD3DFont( hFont );
	m_pFont->InitDeviceObjects( pD3DDevice );
}

void
DX8GUIDrawInterfaceC::draw_marker( const Vector2C& rPos, float32 f32Size )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	XYZCOLOR_VERTEX	rLoop[17];

	// Draw circle
	float32	f32Angle = 0;
	float32	f32DeltaAngle = (2.0f * (float32)M_PI) / 16.0f;

	for( uint32 i = 0; i < 16; i++ ) {
		rLoop[i].x = rPos[0] + (float32)sin( f32Angle ) * f32Size;;
		rLoop[i].y = rPos[1] + (float32)cos( f32Angle ) * f32Size;
		rLoop[i].z = 0;
		rLoop[i].color = m_dwCurColor;

		f32Angle += f32DeltaAngle;
	}
	rLoop[16] = rLoop[0];

	pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_LINESTRIP, 16, rLoop, sizeof( XYZCOLOR_VERTEX ) );


	// Draw cross
	rLoop[0] = rLoop[2];
	rLoop[1] = rLoop[10];

	rLoop[2] = rLoop[6];
	rLoop[3] = rLoop[14];

	pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_LINELIST, 2, rLoop, sizeof( XYZCOLOR_VERTEX ) );

}

void
DX8GUIDrawInterfaceC::draw_box( const Vector2C& rMin, const Vector2C& rMax )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	XYZCOLOR_VERTEX	rLoop[5];
	rLoop[0].x = rMin[0];
	rLoop[0].y = rMin[1];
	rLoop[0].z = 0;
	rLoop[0].color = m_dwCurColor;

	rLoop[1].x = rMax[0];
	rLoop[1].y = rMin[1];
	rLoop[1].z = 0;
	rLoop[1].color = m_dwCurColor;

	rLoop[2].x = rMax[0];
	rLoop[2].y = rMax[1];
	rLoop[2].z = 0;
	rLoop[2].color = m_dwCurColor;

	rLoop[3].x = rMin[0];
	rLoop[3].y = rMax[1];
	rLoop[3].z = 0;
	rLoop[3].color = m_dwCurColor;

	rLoop[4] = rLoop[0];

	pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_LINESTRIP, 4, rLoop, sizeof( XYZCOLOR_VERTEX ) );
}

void
DX8GUIDrawInterfaceC::begin_layout()
{
	DX8ViewportC*	pIface = (DX8ViewportC*)m_pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	BBox2C	rViewport = pIface->get_viewport();
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	pD3DDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
	pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );


	D3DMATRIX	rIdentity;
	ZeroMemory( &rIdentity, sizeof( D3DMATRIX ) );
	rIdentity._11 = 1.0f;
	rIdentity._22 = 1.0f;
	rIdentity._33 = 1.0f;
	rIdentity._44 = 1.0f;


	D3DMATRIX	rOrtho;
	float32		f32Left, f32Right, f32Top, f32Bottom, f32Near, f32Far;

	f32Left = rViewport[0][0];
	f32Right = rViewport[1][0];
	f32Bottom = rViewport[0][1];
	f32Top = rViewport[1][1];
	f32Near = -1.0f;
	f32Far = 1.0f;

	rOrtho._11 = 2.0f / (f32Right - f32Left);
	rOrtho._12 = 0;
	rOrtho._13 = 0;
	rOrtho._14 = 0;
	rOrtho._21 = 0;
	rOrtho._22 = 2.0f / (f32Top - f32Bottom);
	rOrtho._23 = 0;
	rOrtho._24 = 0;
	rOrtho._31 = 0;
	rOrtho._32 = 0;
	rOrtho._33 = 2.0f / (f32Far - f32Near);
	rOrtho._34 = 0;
	rOrtho._41 = -((f32Right + f32Left) / (f32Right - f32Left));
	rOrtho._42 = -((f32Top + f32Bottom) / (f32Top - f32Bottom));
	rOrtho._43 = -((f32Far + f32Near) / (f32Far - f32Near));
	rOrtho._44 = 1.0f;

	pD3DDevice->SetTransform( D3DTS_PROJECTION, &rOrtho );
	pD3DDevice->SetTransform( D3DTS_VIEW, &rIdentity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &rIdentity );

	pD3DDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE );
}

void
DX8GUIDrawInterfaceC::end_layout()
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	pD3DDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE );
}

void
DX8GUIDrawInterfaceC::draw_layout( const ColorC& rLayoutCol )
{
	DX8ViewportC*	pIface = (DX8ViewportC*)m_pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	BBox2C	rLayout = pIface->get_layout();
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	DWORD	dwCol = D3DCOLOR_ARGB( (int32)(rLayoutCol[3] * 255), (int32)(rLayoutCol[0] * 255), (int32)(rLayoutCol[1] * 255), (int32)(rLayoutCol[2] * 255) );

	XYZCOLOR_VERTEX	rQuad[4];
	rQuad[0].x = rLayout[0][0];
	rQuad[0].y = rLayout[0][1];
	rQuad[0].z = 0;
	rQuad[0].color = dwCol;

	rQuad[1].x = rLayout[1][0];
	rQuad[1].y = rLayout[0][1];
	rQuad[1].z = 0;
	rQuad[1].color = dwCol;

	rQuad[2].x = rLayout[1][0];
	rQuad[2].y = rLayout[1][1];
	rQuad[2].z = 0;
	rQuad[2].color = dwCol;

	rQuad[3].x = rLayout[0][0];
	rQuad[3].y = rLayout[1][1];
	rQuad[3].z = 0;
	rQuad[3].color = dwCol;

	pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 2, rQuad, sizeof( XYZCOLOR_VERTEX ) );
}

void
DX8GUIDrawInterfaceC::set_color( const ColorC& rColor )
{
	m_dwCurColor = D3DCOLOR_ARGB( (int32)(rColor[3] * 255), (int32)(rColor[0] * 255), (int32)(rColor[1] * 255), (int32)(rColor[2] * 255) );
}

void
DX8GUIDrawInterfaceC::set_point_size( float32 f32Size )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

//	DX8ViewportC*	pIface = (DX8ViewportC*)m_pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
//	Vector2C	rSize = pIface->delta_client_to_layout( Vector2C( f32Size, f32Size ) );
//	f32Size = rSize[0];

//	pD3DDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW( 0 ) );
//	pD3DDevice->SetRenderState( D3DRS_POINTSIZE_MAX, FtoDW( 100 ) );
//	pD3DDevice->SetRenderState( D3DRS_POINTSIZE, FtoDW( 10 ) );

	m_f32PointSize = f32Size;
}

void
DX8GUIDrawInterfaceC::draw_line( const Vector2C& rFrom, const Vector2C& rTo )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	XYZCOLOR_VERTEX	rLine[2];
	rLine[0].x = rFrom[0];
	rLine[0].y = rFrom[1];
	rLine[0].z = 0;
	rLine[0].color = m_dwCurColor;

	rLine[1].x = rTo[0];
	rLine[1].y = rTo[1];
	rLine[1].z = 0;
	rLine[1].color = m_dwCurColor;

	pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_LINELIST, 1, rLine, sizeof( XYZCOLOR_VERTEX ) );
}

void
DX8GUIDrawInterfaceC::draw_point( const Vector2C& rPos )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	DX8ViewportC*	pIface = (DX8ViewportC*)m_pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	Vector2C	rConvPos = pIface->layout_to_client( rPos );
	rConvPos[1] = pIface->get_height() - rConvPos[1];


	float32	f32PtSize = m_f32PointSize / 2.0f;

	XYZWCOLOR_VERTEX	rQuad[4];

	rQuad[0].x = rConvPos[0] - f32PtSize;
	rQuad[0].y = rConvPos[1] - f32PtSize;
	rQuad[0].z = 0;
	rQuad[0].w = 1.0f;
	rQuad[0].color = m_dwCurColor;

	rQuad[1].x = rConvPos[0] + f32PtSize;
	rQuad[1].y = rConvPos[1] - f32PtSize;
	rQuad[1].z = 0;
	rQuad[1].w = 1.0f;
	rQuad[1].color = m_dwCurColor;

	rQuad[2].x = rConvPos[0] + f32PtSize;
	rQuad[2].y = rConvPos[1] + f32PtSize;
	rQuad[2].z = 0;
	rQuad[2].w = 1.0f;
	rQuad[2].color = m_dwCurColor;

	rQuad[3].x = rConvPos[0] - f32PtSize;
	rQuad[3].y = rConvPos[1] + f32PtSize;
	rQuad[3].z = 0;
	rQuad[3].w = 1.0f;
	rQuad[3].color = m_dwCurColor;

	pD3DDevice->SetVertexShader( FVF_XYZWCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 2, rQuad, sizeof( XYZWCOLOR_VERTEX ) );

}

void
DX8GUIDrawInterfaceC::draw_grid( float32 f32Width, float32 f32Height, float32 f32GridSize )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	pD3DDevice->SetRenderState( D3DRS_LOCALVIEWER, FALSE );

	pD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
	pD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
	pD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );

	pD3DDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT );
	pD3DDevice->SetRenderState( D3DRS_POINTSCALEENABLE, FALSE );
//	pD3DDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW( 0 ) );
//	pD3DDevice->SetRenderState( D3DRS_POINTSIZE_MAX, FtoDW( 10 ) );
	pD3DDevice->SetRenderState( D3DRS_POINTSIZE, FtoDW( 1 ) );

	int32	i32GSizeX = (int32)(f32Width / f32GridSize) - 1;
	int32	i32GSizeY = (int32)(f32Height / f32GridSize) - 1;

	XYZCOLOR_VERTEX*	pVerts = new XYZCOLOR_VERTEX[i32GSizeX * i32GSizeY];

	float32	f32Y = f32GridSize;
	int32	i, j;

	//--------------------------------------------------------------------
	// copy particle data to vertex buffer
	//--------------------------------------------------------------------

	for( i = 0; i < i32GSizeY; i++ ) {
		float32	f32X = f32GridSize;
		for( j = 0; j < i32GSizeX; j++ ) {

			XYZCOLOR_VERTEX*	pVert = &pVerts[i * i32GSizeX + j];
			pVert->x = f32X;
			pVert->y = f32Y;
			pVert->z = 0;
			pVert->color = m_dwCurColor;

			f32X += f32GridSize;
		}
		f32Y += f32GridSize;
	}

	pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_POINTLIST, i32GSizeX * i32GSizeY, pVerts, sizeof( XYZCOLOR_VERTEX ) );

	delete [] pVerts;

	pD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
	pD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO );
	pD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

}

void
DX8GUIDrawInterfaceC::draw_selection_box( const Vector2C& rStartPos, const Vector2C& rEndPos )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	D3DLINEPATTERN	rLinePattern;
	rLinePattern.wRepeatFactor = 1;
	rLinePattern.wLinePattern = 0x5555;
	pD3DDevice->SetRenderState( D3DRS_LINEPATTERN, *((DWORD*)&rLinePattern) );


	pD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_INVDESTCOLOR );
	pD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO );
	pD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );


	XYZCOLOR_VERTEX	rLoop[5];
	rLoop[0].x = rStartPos[0];
	rLoop[0].y = rStartPos[1];
	rLoop[0].z = 0;
	rLoop[0].color = 0xffffffff;

	rLoop[1].x = rEndPos[0];
	rLoop[1].y = rStartPos[1];
	rLoop[1].z = 0;
	rLoop[1].color = 0xffffffff;

	rLoop[2].x = rEndPos[0];
	rLoop[2].y = rEndPos[1];
	rLoop[2].z = 0;
	rLoop[2].color = 0xffffffff;

	rLoop[3].x = rStartPos[0];
	rLoop[3].y = rEndPos[1];
	rLoop[3].z = 0;
	rLoop[3].color = 0xffffffff;

	rLoop[4] = rLoop[0];

	pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->DrawPrimitiveUP( D3DPT_LINESTRIP, 4, rLoop, sizeof( XYZCOLOR_VERTEX ) );

	pD3DDevice->SetRenderState( D3DRS_LINEPATTERN, 0 );

	pD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
	pD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO );
	pD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
}

void
DX8GUIDrawInterfaceC::init_invalidate()
{
	if( m_pFont )
		m_pFont->InvalidateDeviceObjects();
}

void
DX8GUIDrawInterfaceC::init_validate()
{
	if( m_pFont )
		m_pFont->RestoreDeviceObjects();
}
