
#include "PajaTypes.h"
#include "DeviceInterfaceI.h"
#include "Vector2C.h"
#include "BBox2C.h"
#include <math.h>
#include <windows.h>
#include <d3d8.h>
#include <stdio.h>
#include "DX8ViewportC.h"
#include "DX8DeviceC.h"

static
void
TRACE( const char* szFormat, ...  )
{
	char	szMsg[256];
	va_list	rList;
	va_start( rList, szFormat );
	_vsnprintf( szMsg, 255, szFormat, rList );
	va_end( rList );
	OutputDebugString( szMsg );
}


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


DX8ViewportC::DX8ViewportC() :
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_i32PosX( 0 ),
	m_i32PosY( 0 ),
	m_f64ClientAspectX( 0 ),
	m_f64ClientAspectY( 0 ),
	m_f64LayoutAspectX( 0 ),
	m_f64LayoutAspectY( 0 ),
	m_pDevice( 0 )
{
}

DX8ViewportC::DX8ViewportC( DX8DeviceC* pDevice ) :
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_i32PosX( 0 ),
	m_i32PosY( 0 ),
	m_f64ClientAspectX( 0 ),
	m_f64ClientAspectY( 0 ),
	m_f64LayoutAspectX( 0 ),
	m_f64LayoutAspectY( 0 ),
	m_pDevice( pDevice )
{
}

DX8ViewportC::~DX8ViewportC()
{
	// empty
}

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

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

PluginClass::ClassIdC
DX8ViewportC::get_class_id() const
{
	return CLASS_DX8_VIEWPORT;
}

const char*
DX8ViewportC::get_class_name()
{
	return "DirectX 8 Viewport";
}

void
DX8ViewportC::recalc_mapping()
{
	if( m_i32Width <= 1 || m_i32Height <= 1 ||
		m_rViewport.width() == 0.0f || m_rViewport.height() == 0.0f ) {
		// layout to client
		m_f64ClientAspectX = 0;
		m_f64ClientAspectY = 0;
		// client to layout
		m_f64LayoutAspectX = 0;
		m_f64LayoutAspectY = 0;
	}
	else {
		// layout to client
		m_f64ClientAspectX = (float64)m_i32Width / (float64)m_rViewport.width();
		m_f64ClientAspectY = (float64)m_i32Height / (float64)m_rViewport.height();
		// client to layout
		m_f64LayoutAspectX = (float64)m_rViewport.width() / (float64)m_i32Width;
		m_f64LayoutAspectY = (float64)m_rViewport.height() / (float64)m_i32Height;
	}
}


void
DX8ViewportC::set_dimension( int32 i32PosX, int32 i32PosY, int32 i32Width, int32 i32Height )
{
	m_i32PosX = i32PosX;
	m_i32PosY = i32PosY;
	m_i32Width = i32Width;
	m_i32Height = i32Height;

	recalc_mapping();
}

void
DX8ViewportC::set_viewport( const BBox2C& rViewport )
{
	m_rViewport = rViewport;

	recalc_mapping();
}

void
DX8ViewportC::set_layout( const BBox2C& rLayout )
{
	m_rLayout = rLayout;
}

void
DX8ViewportC::set_perspective( const BBox2C& rBBox, float32 f32FOV, float32 f32Aspect, float32 f32ZNear, float32 f32ZFar )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;


	// Calc layout viewport coordinates
	Vector2C	rViewMin = layout_to_client( m_rLayout[0] );
	Vector2C	rViewMax = layout_to_client( m_rLayout[1] );
	int32		i32ViewMinX = (int32)(rViewMin[0]/* - 0.5f*/);
	int32		i32ViewMinY = (int32)(rViewMin[1]/* - 0.5f*/);
	int32		i32ViewMaxX = (int32)(rViewMax[0]/* + 0.5f*/);
	int32		i32ViewMaxY = (int32)(rViewMax[1]/* + 0.5f*/);

	if( i32ViewMinX < 0 )
		i32ViewMinX = 0;
	if( i32ViewMaxX >= m_i32Width )
		i32ViewMaxX = m_i32Width - 1;
	if( i32ViewMinX >= m_i32Width || i32ViewMaxX < 0 ) {
		i32ViewMinX = 0;
		i32ViewMaxX = 1;
	}

	if( i32ViewMinY < 0 )
		i32ViewMinY = 0;
	if( i32ViewMaxY >= m_i32Height )
		i32ViewMaxY = m_i32Height - 1;
	if( i32ViewMinY >= m_i32Height || i32ViewMaxY < 0 ) {
		i32ViewMinY = 0;
		i32ViewMaxY = 1;
	}


	Vector2C	rMin = layout_to_client( rBBox[0] );
	Vector2C	rMax = layout_to_client( rBBox[1] );

	int32	i32MinX = (int32)(rMin[0]/* - 0.5f*/);
	int32	i32MinY = (int32)(rMin[1]/* - 0.5f*/);
	int32	i32MaxX = (int32)(rMax[0]/* + 0.5f*/);
	int32	i32MaxY = (int32)(rMax[1]/* + 0.5f*/);
	int32	i32DeltaX = i32MaxX - i32MinX;
	int32	i32DeltaY = i32MaxY - i32MinY;

	Vector2C	rCenter = rBBox.center();
	Vector2C	rSize = rBBox.size();
	rCenter = layout_to_client( rCenter );
	rCenter -= Vector2C( i32MinX, i32MinY );
	rSize = delta_layout_to_client( rSize );

	float64	f64Scale = (float64)i32DeltaX / (float64)rSize[0];
	float64	f64Aspect = (float64)i32DeltaY / (float64)i32DeltaX * f32Aspect;
	float64	f64CenterX = 1.0 - (rCenter[0] / (float64)i32DeltaX) * 2.0;
	float64	f64CenterY = 1.0 - (rCenter[1] / (float64)i32DeltaY) * 2.0;
	float64	f64MinX, f64MaxX, f64MinY, f64MaxY;

	f64MaxX = f32ZNear * tan( (f32FOV / 2.0) * (M_PI / 180.0) ) * f64Scale;
	f64MinX = -f64MaxX;

	f64MinY = f64MinX * f64Aspect;
	f64MaxY = f64MaxX * f64Aspect;

	f64CenterX *= f64MaxX;
	f64CenterY *= f64MaxY;

	float32 f32Left		= f64MinX + f64CenterX;
	float32 f32Right	= f64MaxX + f64CenterX;
	float32 f32Bottom	= f64MinY + f64CenterY;
	float32 f32Top		= f64MaxY + f64CenterY;
	float32 f32Near		= f32ZNear;
	float32 f32Far		= f32ZFar;
	float32	f32Width	= f32Right - f32Left;
	float32	f32Height	= f32Top - f32Bottom;



	// clip
	if( i32MinX < i32ViewMinX ) {
		f32Left += (float32)(i32ViewMinX - i32MinX) * f32Width / (float32)i32DeltaX;
		i32MinX = i32ViewMinX;
	}
	if( i32MaxX >= i32ViewMaxX ) {
		f32Right -= (float32)(i32MaxX - (i32ViewMaxX/* - 1*/)) * f32Width / (float32)i32DeltaX;
		i32MaxX = i32ViewMaxX/* - 1*/;
	}
	if( i32MinX >= i32ViewMaxX || i32MaxX < i32ViewMinX ) {
		i32MinX = 0;
		i32MaxX = 1;
	}


	if( i32MinY < i32ViewMinY ) {
		f32Bottom += (float32)(i32ViewMinY - i32MinY) * f32Height / (float32)i32DeltaY;
		i32MinY = i32ViewMinY;
	}
	if( i32MaxY >= i32ViewMaxY ) {
		f32Top -= (float32)(i32MaxY - (i32ViewMaxY/* - 1*/)) * f32Height / (float32)i32DeltaY;
		i32MaxY = i32ViewMaxY/* - 1*/;
	}
	if( i32MinY >= i32ViewMaxY || i32MaxY < i32ViewMinY ) {
		i32MinY = 0;
		i32MaxY = 1;
	}

/*	// clip
	if( i32MinX < 0 ) {
		f32Left += -(float32)i32MinX * f32Width / (float32)i32DeltaX;
		i32MinX = 0;
	}
	if( i32MaxX >= m_i32Width ) {
		f32Right -= (float32)(i32MaxX - (m_i32Width - 1)) * f32Width / (float32)i32DeltaX;
		i32MaxX = m_i32Width - 1;
	}

	if( i32MinX >= m_i32Width || i32MaxX < 0 ) {
		i32MinX = 0;
		i32MaxX = 1;
	}

	if( i32MinY < 0 ) {
		f32Bottom += -(float32)i32MinY * f32Height / (float32)i32DeltaY;
		i32MinY = 0;
	}
	if( i32MaxY >= m_i32Height ) {
		f32Top -= (float32)(i32MaxY - (m_i32Height - 1)) * f32Height / (float32)i32DeltaY;
		i32MaxY = m_i32Height - 1;
	}

	if( i32MinY >= m_i32Height || i32MaxY < 0 ) {
		i32MinY = 0;
		i32MaxY = 1;
	}
*/

	// set viewport
	D3DVIEWPORT8	rViewport;
	rViewport.X = i32MinX;
	rViewport.Y = (m_i32Height - 1) - i32MaxY;
	rViewport.Width = i32MaxX - i32MinX;
	rViewport.Height = i32MaxY - i32MinY;
	rViewport.MinZ = 0.0f;
	rViewport.MaxZ = 1.0f;
	pD3DDevice->SetViewport( &rViewport );

	// projection matrix ála OpenGL
	D3DMATRIX	rProj;
    rProj._11 = (f32Near) / (f32Right - f32Left);
    rProj._12 = 0;
    rProj._13 = 0;
    rProj._14 = 0;
    rProj._21 = 0;
    rProj._22 = (f32Near) / (f32Top - f32Bottom);
    rProj._23 = 0;
    rProj._24 = 0;
    rProj._31 = -(f32Right + f32Left) / (f32Right - f32Left);
    rProj._32 = -(f32Top + f32Bottom) / (f32Top - f32Bottom);
    rProj._33 = (f32Far + f32Near) / (f32Far - f32Near);
    rProj._34 = 1.0f;
    rProj._41 = 0;
    rProj._42 = 0;
    rProj._43 = -(2 * f32Far * f32Near) / (f32Far - f32Near);
    rProj._44 = 0;
	pD3DDevice->SetTransform( D3DTS_PROJECTION, &rProj );


	D3DMATRIX	rIdentity;
	ZeroMemory( &rIdentity, sizeof( D3DMATRIX ) );
	rIdentity._11 = 1.0f;
	rIdentity._22 = 1.0f;
	rIdentity._33 = 1.0f;
	rIdentity._44 = 1.0f;
	pD3DDevice->SetTransform( D3DTS_VIEW, &rIdentity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &rIdentity );
}


void
DX8ViewportC::set_ortho( const BBox2C& rBBox,
							 float32 f32Left, float32 f32Right, float32 f32Top, float32 f32Bottom,
							 float32 f32ZNear, float32 f32ZFar )
{
	LPDIRECT3DDEVICE8	pD3DDevice = m_pDevice->get_d3ddevice();
	if( !pD3DDevice )
		return;

	// Calc layout viewport coordinates
	Vector2C	rViewMin = layout_to_client( m_rLayout[0] );
	Vector2C	rViewMax = layout_to_client( m_rLayout[1] );
	int32		i32ViewMinX = (int32)(rViewMin[0]/* - 0.5f*/);
	int32		i32ViewMinY = (int32)(rViewMin[1]/* - 0.5f*/);
	int32		i32ViewMaxX = (int32)(rViewMax[0]/* + 0.5f*/);
	int32		i32ViewMaxY = (int32)(rViewMax[1]/* + 0.5f*/);

	if( i32ViewMinX < 0 )
		i32ViewMinX = 0;
	if( i32ViewMaxX >= m_i32Width )
		i32ViewMaxX = m_i32Width - 1;
	if( i32ViewMinX >= m_i32Width || i32ViewMaxX < 0 ) {
		i32ViewMinX = 0;
		i32ViewMaxX = 1;
	}

	if( i32ViewMinY < 0 )
		i32ViewMinY = 0;
	if( i32ViewMaxY >= m_i32Height )
		i32ViewMaxY = m_i32Height - 1;
	if( i32ViewMinY >= m_i32Height || i32ViewMaxY < 0 ) {
		i32ViewMinY = 0;
		i32ViewMaxY = 1;
	}


	// calc pixel coordinates
	Vector2C	rMin = layout_to_client( rBBox[0] );
	Vector2C	rMax = layout_to_client( rBBox[1] );

	int32	i32MinX = (int32)(rMin[0]/* - 0.5f*/);
	int32	i32MinY = (int32)(rMin[1]/* - 0.5f*/);
	int32	i32MaxX = (int32)(rMax[0]/* + 0.5f*/);
	int32	i32MaxY = (int32)(rMax[1]/* + 0.5f*/);
	int32	i32DeltaX = i32MaxX - i32MinX;
	int32	i32DeltaY = i32MaxY - i32MinY;

	float32	f32Width = f32Right - f32Left;
	float32	f32Height = f32Top - f32Bottom;

	f32Left += ((float32)i32MinX - rMin[0]) * f32Width / (rMax[0] - rMin[0]);
	f32Right += ((float32)i32MaxX - rMax[0]) * f32Width / (rMax[0] - rMin[0]);
	f32Bottom += ((float32)i32MinY - rMin[1]) * f32Height / (rMax[1] - rMin[1]);
	f32Top += ((float32)i32MaxY - rMax[1]) * f32Height / (rMax[1] - rMin[1]);

	f32Width = f32Right - f32Left;
	f32Height = f32Top - f32Bottom;

	// clip
	if( i32MinX < i32ViewMinX ) {
		f32Left += (float32)(i32ViewMinX - i32MinX) * f32Width / (float32)i32DeltaX;
		i32MinX = i32ViewMinX;
	}
	if( i32MaxX >= i32ViewMaxX ) {
		f32Right -= (float32)(i32MaxX - (i32ViewMaxX/* - 1*/)) * f32Width / (float32)i32DeltaX;
		i32MaxX = i32ViewMaxX/* - 1*/;
	}
	if( i32MinX >= i32ViewMaxX || i32MaxX < i32ViewMinX ) {
		i32MinX = 0;
		i32MaxX = 1;
	}


	if( i32MinY < i32ViewMinY ) {
		f32Bottom += (float32)(i32ViewMinY - i32MinY) * f32Height / (float32)i32DeltaY;
		i32MinY = i32ViewMinY;
	}
	if( i32MaxY >= i32ViewMaxY ) {
		f32Top -= (float32)(i32MaxY - (i32ViewMaxY/* - 1*/)) * f32Height / (float32)i32DeltaY;
		i32MaxY = i32ViewMaxY/* - 1*/;
	}
	if( i32MinY >= i32ViewMaxY || i32MaxY < i32ViewMinY ) {
		i32MinY = 0;
		i32MaxY = 1;
	}


	D3DVIEWPORT8	rViewport;
	rViewport.X = i32MinX;
	rViewport.Y = (m_i32Height - 1) - i32MaxY;
	rViewport.Width = i32MaxX - i32MinX;
	rViewport.Height = i32MaxY - i32MinY;
	rViewport.MinZ = 0.0f;
	rViewport.MaxZ = 1.0f;
	pD3DDevice->SetViewport( &rViewport );

	D3DMATRIX	rOrtho;
	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 / (f32ZFar - f32ZNear);
	rOrtho._34 = 0;
	rOrtho._41 = -((f32Right + f32Left) / (f32Right - f32Left));
	rOrtho._42 = -((f32Top + f32Bottom) / (f32Top - f32Bottom));
	rOrtho._43 = -((f32ZFar + f32ZNear) / (f32ZFar - f32ZNear));
	rOrtho._44 = 1.0f;
	pD3DDevice->SetTransform( D3DTS_PROJECTION, &rOrtho );

	D3DMATRIX	rIdentity;
	ZeroMemory( &rIdentity, sizeof( D3DMATRIX ) );
	rIdentity._11 = 1.0f;
	rIdentity._22 = 1.0f;
	rIdentity._33 = 1.0f;
	rIdentity._44 = 1.0f;
	pD3DDevice->SetTransform( D3DTS_VIEW, &rIdentity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &rIdentity );
}

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

	D3DVIEWPORT8	rViewport;
	rViewport.X = 0;
	rViewport.Y = 0;
	rViewport.Width = m_i32Width;
	rViewport.Height = m_i32Height;
	rViewport.MinZ = 0.0f;
	rViewport.MaxZ = 1.0f;
	pD3DDevice->SetViewport( &rViewport );

	D3DMATRIX	rIdentity;
	ZeroMemory( &rIdentity, sizeof( D3DMATRIX ) );
	rIdentity._11 = 1.0f;
	rIdentity._22 = 1.0f;
	rIdentity._33 = 1.0f;
	rIdentity._44 = 1.0f;
	pD3DDevice->SetTransform( D3DTS_PROJECTION, &rIdentity );
	pD3DDevice->SetTransform( D3DTS_VIEW, &rIdentity );
	pD3DDevice->SetTransform( D3DTS_WORLD, &rIdentity );
}


Vector2C
DX8ViewportC::client_to_layout( const Vector2C& rVec )
{
	return Vector2C( m_rViewport[0][0] + (rVec[0] * (float32)m_f64LayoutAspectX), m_rViewport[0][1] + (rVec[1] * (float32)m_f64LayoutAspectY) );
}

Vector2C
DX8ViewportC::layout_to_client( const Vector2C& rVec )
{
	return Vector2C( (rVec[0] - m_rViewport[0][0]) *  (float32)m_f64ClientAspectX, (rVec[1] - m_rViewport[0][1]) * (float32)m_f64ClientAspectY );
}

Vector2C
DX8ViewportC::delta_client_to_layout( const Vector2C& rVec )
{
	return Vector2C( rVec[0] * (float32)m_f64LayoutAspectX, rVec[1] * (float32)m_f64LayoutAspectY );
}

Vector2C
DX8ViewportC::delta_layout_to_client( const Vector2C& rVec )
{
	return Vector2C( rVec[0] *  (float32)m_f64ClientAspectX, rVec[1] * (float32)m_f64ClientAspectY );
}

const BBox2C&
DX8ViewportC::get_viewport()
{
	return m_rViewport;
}

const BBox2C&
DX8ViewportC::get_layout()
{
	return m_rLayout;
}

int32
DX8ViewportC::get_width()
{
	return m_i32Width;
}

int32
DX8ViewportC::get_height()
{
	return m_i32Height;
}
