//
// AVIPlugin.cpp
//
// AVI Plugin
//
// Copyright (c) 2000 memon/moppi productions
//

//#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <d3d8.h>
#include <D3DX8.h>
#include <stdio.h>

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableI.h"
#include "DX8DeviceC.h"
#include "DX8ViewportC.h"
#include "test.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ImportableImageI.h"

using namespace PajaTypes;
using namespace PluginClass;
using namespace PajaSystem;
using namespace Edit;
using namespace Composition;
using namespace Import;
using namespace FileIO;

using namespace TestPlugin;


//////////////////////////////////////////////////////////////////////////
//
//  test effect class descriptor.
//

TestDescC::TestDescC()
{
	// empty
}

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

void*
TestDescC::create()
{
	return (void*)TestEffectC::create_new();
}

int32
TestDescC::get_classtype() const
{
	return CLASS_TYPE_EFFECT;
}

SuperClassIdC
TestDescC::get_super_class_id() const
{
	return SUPERCLASS_EFFECT;
}

ClassIdC
TestDescC::get_class_id() const
{
	return CLASS_TEST_EFFECT;
};

const char*
TestDescC::get_name() const
{
	return "DX8 Test";
}

const char*
TestDescC::get_desc() const
{
	return "Test Effect";
}

const char*
TestDescC::get_author_name() const
{
	return "Mikko \"memon\" Mononen";
}

const char*
TestDescC::get_copyright_message() const
{
	return "Copyright (c) 2000 Moppi Productions";
}

const char*
TestDescC::get_url() const
{
	return "http://moppi.inside.org/demopaja/";
}

const char*
TestDescC::get_help_filename() const
{
	return "res://BlurHelp.html";
}

uint32
TestDescC::get_required_device_driver_count() const
{
	return 1;
}

const ClassIdC&
TestDescC::get_required_device_driver( uint32 ui32Idx )
{
	return PajaSystem::CLASS_DX8_DEVICEDRIVER;
}


uint32
TestDescC::get_ext_count() const
{
	return 0;
}

const char*
TestDescC::get_ext( uint32 ui32Index ) const
{
	return 0;
}


//////////////////////////////////////////////////////////////////////////
//
// Global descriptors, which will be returned to the Demopaja.
//

TestDescC	g_rTestDesc;

#ifndef DEMOPAJAPLAYER

//////////////////////////////////////////////////////////////////////////
//
// The DLL
//

BOOL APIENTRY
DllMain( HANDLE hModule, DWORD ulReasonForCall, LPVOID lpReserved )
{
    switch( ulReasonForCall )
	{
		case DLL_PROCESS_ATTACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH:
			break;
    }
    return TRUE;
}


//
// Returns number of classes inside this plugin DLL.
//

__declspec( dllexport )
int32
get_classdesc_count()
{
	return 1;
}


//
// Returns class descriptors of the plugin classes.
//

__declspec( dllexport )
ClassDescC*
get_classdesc( int32 i )
{
	if( i == 0 )
		return &g_rTestDesc;
	return 0;
}


//
// Returns the API version this DLL was made with.
//

__declspec( dllexport )
int32
get_api_version()
{
	return DEMOPAJA_VERSION;
}

//
// Returns the DLL name.
//

__declspec( dllexport )
char*
get_dll_name()
{
	return "dx8TestPlugin.dll - test Effect plugin (c)2000 memon/moppi productions";
}

#endif



//////////////////////////////////////////////////////////////////////////
//
// The effect
//

TestEffectC::TestEffectC()
{
	//
	// Create Transform gizmo.
	//
	m_pTraGizmo = AutoGizmoC::create_new( this, "Transform", ID_GIZMO_TRANS );

	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Position", Vector2C(), ID_TRANSFORM_POS,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ABS_POSITION, PARAM_ANIMATABLE ) );
	
	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Pivot", Vector2C(), ID_TRANSFORM_PIVOT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_WORLD_SPACE, PARAM_ANIMATABLE ) );
	
	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Scale", Vector2C( 1, 1 ), ID_TRANSFORM_SCALE,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f ) );


	//
	// Create Attributes gizmo.
	//
	m_pAttGizmo = AutoGizmoC::create_new( this, "Attributes", ID_GIZMO_ATTRIB );

	// Blur Amount
	m_pAttGizmo->add_parameter(	ParamColorC::create_new( m_pAttGizmo, "Color", ColorC(), ID_ATTRIBUTE_COLOR,
						PARAM_STYLE_COLORPICKER, PARAM_ANIMATABLE ) );

	// Render mode
	ParamIntC*	pRenderMode = ParamIntC::create_new( m_pAttGizmo, "Render mode", 0, ID_ATTRIBUTE_RENDERMODE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 2 );
	pRenderMode->add_label( 0, "Ortho" );
	pRenderMode->add_label( 1, "Perspective" );
	pRenderMode->add_label( 2, "Normalized" );
	m_pAttGizmo->add_parameter( pRenderMode );
}

TestEffectC::TestEffectC( EditableI* pOriginal ) :
	EffectI( pOriginal ),
	m_pTraGizmo( 0 ),
	m_pAttGizmo( 0 )
{
	// Empty. The parameters are not created in the clone constructor.
}

TestEffectC::~TestEffectC()
{
	// Return if this is a clone.
	if( get_original() )
		return;

	// Release gizmos.
	m_pTraGizmo->release();
	m_pAttGizmo->release();
}

TestEffectC*
TestEffectC::create_new()
{
	return new TestEffectC;
}

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

DataBlockI*
TestEffectC::create( EditableI* pOriginal )
{
	return new TestEffectC( pOriginal );
}

void
TestEffectC::copy( EditableI* pEditable )
{
	EffectI::copy( pEditable );

	// Make deep copy.
	TestEffectC*	pEffect = (TestEffectC*)pEditable;
	m_pTraGizmo->copy( pEffect->m_pTraGizmo );
	m_pAttGizmo->copy( pEffect->m_pAttGizmo );
}

void
TestEffectC::restore( EditableI* pEditable )
{
	EffectI::restore( pEditable );

	// Make shallow copy.
	TestEffectC*	pEffect = (TestEffectC*)pEditable;
	m_pTraGizmo = pEffect->m_pTraGizmo;
	m_pAttGizmo = pEffect->m_pAttGizmo;
}

int32
TestEffectC::get_gizmo_count()
{
	// Return number of gizmos inside this effect.
	return GIZMO_COUNT;
}

GizmoI*
TestEffectC::get_gizmo( PajaTypes::int32 i32Index )
{
	// Returns specified gizmo.
	// Since the ID's are zero based, we can use them as indices.
	switch( i32Index ) {
	case ID_GIZMO_TRANS:
		return m_pTraGizmo;
	case ID_GIZMO_ATTRIB:
		return m_pAttGizmo;
	}

	return 0;
}

ClassIdC
TestEffectC::get_class_id()
{
	// Return the class ID. Should be same as in the class descriptor.
	return CLASS_TEST_EFFECT;
}

const char*
TestEffectC::get_class_name()
{
	// Return the class name. Should be same as in the class descriptor.
	return "DX8 Test";
}

void
TestEffectC::set_default_file( FileHandleC* pHandle )
{
	// empty
}

ParamI*
TestEffectC::get_default_param( int32 i32Param )
{
	// Return specified default parameter.
	if( i32Param == DEFAULT_PARAM_POSITION )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_POS );
	else if( i32Param == DEFAULT_PARAM_SCALE )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE );
	else if( i32Param == DEFAULT_PARAM_PIVOT )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT );
	return 0;
}

void
TestEffectC::initialize( uint32 ui32Reason, DeviceContextC* pContext, TimeContextC* pTimeContext )
{
	DX8DeviceC*	pDevice = (DX8DeviceC*)pContext->query_interface( CLASS_DX8_DEVICEDRIVER );
	if( !pDevice )
		return;

	if( ui32Reason == INIT_INITIAL_UPDATE ) {
		// nothing to intialize
	}
	else if( ui32Reason == INIT_DEVICE_CHANGED ) {
		if( pDevice->get_state() == DEVICE_STATE_SHUTTINGDOWN ) {
			// the device is begin shut down...
		}
	}
	else if( ui32Reason == INIT_DEVICE_INVALIDATE ) {
		if( pDevice->get_state() == DEVICE_STATE_LOST ) {
			// the device lost
			OutputDebugString( "TestEffectC::initialize:  Device lost\n" );
		}
	}
	else if( ui32Reason == INIT_DEVICE_VALIDATE ) {
		if( pDevice->get_state() == DEVICE_STATE_OK ) {
			// the device lost
			OutputDebugString( "TestEffectC::initialize:  Device back\n" );
		}
	}

}



struct XYZCOLOR_VERTEX {
	float	x, y, z;
	DWORD	color;
};
#define FVF_XYZCOLOR_VERTEX			D3DFVF_XYZ | D3DFVF_DIFFUSE


struct XYZWCOLOR_VERTEX {
	float	x, y, z, w;
	DWORD	color;
};
#define FVF_XYZWCOLOR_VERTEX			D3DFVF_XYZRHW  | D3DFVF_DIFFUSE


void
TestEffectC::do_frame( DeviceContextC* pContext )
{
	// Get the OpenGL device.
	DX8DeviceC*	pDevice = (DX8DeviceC*)pContext->query_interface( CLASS_DX8_DEVICEDRIVER );
	if( !pDevice ) {
		OutputDebugString( "TestEffectC::do_frame:  no device!!\n" );
		return;
	}

	// Get the OpenGL viewport.
	DX8ViewportC*	pViewport = (DX8ViewportC*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	if( !pViewport ) {
		OutputDebugString( "TestEffectC::do_frame:  no viewport!!\n" );
		return;
	}

	LPDIRECT3DDEVICE8	pD3DDevice = pDevice->get_d3ddevice();

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


	// Set orthographic projection.
	if( m_i32RenderMode == RENDERMODE_ORTHO ) {
		//
		// using orthogonal projection matrix
		//
		pViewport->set_ortho( m_rBBox, m_rBBox[0][0], m_rBBox[1][0], m_rBBox[1][1], m_rBBox[0][1] );

		XYZCOLOR_VERTEX	rQuad[4];
		DWORD	dwCol = D3DCOLOR_ARGB( (int32)(m_rColor[3] * 255), (int32)(m_rColor[0] * 255), (int32)(m_rColor[1] * 255), (int32)(m_rColor[2] * 255) );

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

		rQuad[1].x = m_rVertices[1][0];
		rQuad[1].y = m_rVertices[1][1];
		rQuad[1].z = 0;
		rQuad[1].color = dwCol ^ 0xffffffff;

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

		rQuad[3].x = m_rVertices[3][0];
		rQuad[3].y = m_rVertices[3][1];
		rQuad[3].z = 0;
		rQuad[3].color = dwCol ^ 0xffffffff;

		pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
		pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 2, rQuad, sizeof( XYZCOLOR_VERTEX ) );
	}
	else if( m_i32RenderMode == RENDERMODE_PERSPECTIVE ) {
		//
		// using perspective projection matrix
		//

		pViewport->set_perspective( m_rBBox, 60.0f, m_f32Aspect, 1, 500 );

		D3DXMATRIX	matView;
		D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3( sin( m_f32Frame * M_PI / 10.0 ) * -10, 3, cos( m_f32Frame * M_PI / 10.0 ) * -10 ),
                                  &D3DXVECTOR3( 0, 0, 0 ),
                                  &D3DXVECTOR3( 0, 1, 0 ) );
		pD3DDevice->SetTransform( D3DTS_VIEW, &matView );

		XYZCOLOR_VERTEX	rQuad[4];
		DWORD	dwCol = D3DCOLOR_ARGB( (int32)(m_rColor[3] * 255), (int32)(m_rColor[0] * 255), (int32)(m_rColor[1] * 255), (int32)(m_rColor[2] * 255) );

		rQuad[0].x = -5;
		rQuad[0].y = -5;
		rQuad[0].z = 0;
		rQuad[0].color = dwCol;

		rQuad[1].x = 5;
		rQuad[1].y = -5;
		rQuad[1].z = 0;
		rQuad[1].color = dwCol ^ 0xffffffff;

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

		rQuad[3].x = -5;
		rQuad[3].y = 5;
		rQuad[3].z = 0;
		rQuad[3].color = dwCol ^ 0xffffffff;

		pD3DDevice->SetVertexShader( FVF_XYZCOLOR_VERTEX );
		pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 2, rQuad, sizeof( XYZCOLOR_VERTEX ) );
	}
	else {
		//
		// using already transformed coordinates
		//
		pViewport->set_identity();

		XYZWCOLOR_VERTEX	rQuad[4];
		DWORD	dwCol = D3DCOLOR_ARGB( (int32)(m_rColor[3] * 255), (int32)(m_rColor[0] * 255), (int32)(m_rColor[1] * 255), (int32)(m_rColor[2] * 255) );

		Vector2C	rClient[4];

		for( uint32 i = 0; i < 4; i++ ) {
			rClient[i] = pViewport->layout_to_client( m_rVertices[i] );
			rClient[i][1] = pViewport->get_height() - 1 - rClient[i][1];	// Demopaja has origo in lower left, DX upper left.
		}

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

		rQuad[1].x = rClient[1][0];
		rQuad[1].y = rClient[1][1];
		rQuad[1].z = 0;
		rQuad[1].w = 1;
		rQuad[1].color = dwCol ^ 0xffffffff;

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

		rQuad[3].x = rClient[3][0];
		rQuad[3].y = rClient[3][1];
		rQuad[3].z = 0;
		rQuad[3].w = 1;
		rQuad[3].color = dwCol ^ 0xffffffff;

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

	pD3DDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE );
}

void
TestEffectC::eval_state( int32 i32Time, TimeContextC* pTimeContext )
{
	Matrix2C	rPosMat, rRotMat, rScaleMat, rPivotMat;

	Vector2C	rScale;
	Vector2C	rPos;
	Vector2C	rPivot;

	// Get parameters which affect the transformation.
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_POS ))->get_val( i32Time, rPos );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT ))->get_val( i32Time, rPivot );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE ))->get_val( i32Time, rScale );

	// Calculate transformation matrix.
	rPivotMat.set_trans( rPivot );
	rPosMat.set_trans( rPos );
	rScaleMat.set_scale( rScale ) ;
	m_rTM = rPivotMat * rScaleMat * rPosMat;


	// set aspectratio
	if( rScale[0] != 0 && rScale[1] != 0 ) {
		m_f32Aspect = fabs( rScale[0] / rScale[1] );
	}
	else {
		m_f32Aspect = 1.0f;
	}


	float32		f32Width = 25;
	float32		f32Height = 25;
	Vector2C	rMin, rMax;
	Vector2C	rVec;

	// Calcualte vertices of the rectangle.
	m_rVertices[0][0] = -f32Width;		// bottom-left
	m_rVertices[0][1] = -f32Height;

	m_rVertices[1][0] =  f32Width;		// bottom-right
	m_rVertices[1][1] = -f32Height;

	m_rVertices[2][0] =  f32Width;		// top-right
	m_rVertices[2][1] =  f32Height;

	m_rVertices[3][0] = -f32Width;		// top-left
	m_rVertices[3][1] =  f32Height;

	// Calculate bounding box
	for( uint32 i = 0; i < 4; i++ ) {
		rVec = m_rTM * m_rVertices[i];
		m_rVertices[i] = rVec;

		if( !i )
			rMin = rMax = rVec;
		else {
			if( rVec[0] < rMin[0] ) rMin[0] = rVec[0];
			if( rVec[1] < rMin[1] ) rMin[1] = rVec[1];
			if( rVec[0] > rMax[0] ) rMax[0] = rVec[0];
			if( rVec[1] > rMax[1] ) rMax[1] = rVec[1];
		}
	}

	// Store bounding box.
	m_rBBox[0] = rMin;
	m_rBBox[1] = rMax;

	// Get color
	((ParamColorC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_COLOR ))->get_val( i32Time, m_rColor );

	// Get rendermode
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_RENDERMODE ))->get_val( i32Time, m_i32RenderMode );


	m_f32Frame = (float32)i32Time / 256.0f;
}

BBox2C
TestEffectC::get_bbox()
{
	// Return the bounding box.
	return m_rBBox;
}

const Matrix2C&
TestEffectC::get_transform_matrix() const
{
	// Return the trnasformation matrix.
	return m_rTM;
}

bool
TestEffectC::hit_test( const Vector2C& rPoint )
{
	// Point in polygon test.
	// from c.g.a FAQ
	int		i, j;
	bool	bInside = false;

	for( i = 0, j = 4 - 1; i < 4; j = i++ ) {
		if( ( ((m_rVertices[i][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[j][1])) ||
			((m_rVertices[j][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[i][1])) ) &&
			(rPoint[0] < (m_rVertices[j][0] - m_rVertices[i][0]) * (rPoint[1] - m_rVertices[i][1]) / (m_rVertices[j][1] - m_rVertices[i][1]) + m_rVertices[i][0]) )

			bInside = !bInside;
	}

	return bInside;
}


enum TestEffectChunksE {
	CHUNK_TEST_BASE =			0x1000,
	CHUNK_TEST_TRANSGIZMO =		0x2000,
	CHUNK_TEST_ATTRIBGIZMO =	0x3000,
};

const uint32	TEST_VERSION = 1;

uint32
TestEffectC::save( SaveC* pSave )
{
	uint32	ui32Error = IO_OK;

	// EffectI base class
	pSave->begin_chunk( CHUNK_TEST_BASE, TEST_VERSION );
		ui32Error = EffectI::save( pSave );
	pSave->end_chunk();

	// Transform
	pSave->begin_chunk( CHUNK_TEST_TRANSGIZMO, TEST_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();

	// Attribute
	pSave->begin_chunk( CHUNK_TEST_ATTRIBGIZMO, TEST_VERSION );
		ui32Error = m_pAttGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

uint32
TestEffectC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_TEST_BASE:
			// EffectI base class
			if( pLoad->get_chunk_version() == TEST_VERSION )
				ui32Error = EffectI::load( pLoad );
			break;

		case CHUNK_TEST_TRANSGIZMO:
			// Transform
			if( pLoad->get_chunk_version() == TEST_VERSION )
				ui32Error = m_pTraGizmo->load( pLoad );
			break;

		case CHUNK_TEST_ATTRIBGIZMO:
			// Attribute
			if( pLoad->get_chunk_version() == TEST_VERSION )
				ui32Error = m_pAttGizmo->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END )
			return ui32Error;
	}

	return ui32Error;
}
