//
// ImagePlugin
//
// This example plugin demostrates loading an image from a PCX file and
// how to use the ImportableImageI interface.
//
// Also simple image plugin effect is demonstrated.
//

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableImageI.h"
#include "OpenGLDeviceC.h"
#include "OpenGLViewportC.h"
#include "ImagePlugin.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ImageResampleC.h"

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

using namespace ImagePlugin;


//////////////////////////////////////////////////////////////////////////
//
//  PCX importer class descriptor.
//

PCXImportDescC::PCXImportDescC()
{
	// empty
}

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

void*
PCXImportDescC::create()
{
	return PCXImportC::create_new();
}

int32
PCXImportDescC::get_classtype() const
{
	return CLASS_TYPE_FILEIMPORT;
}

SuperClassIdC
PCXImportDescC::get_super_class_id() const
{
	return SUPERCLASS_IMAGE;
}

ClassIdC
PCXImportDescC::get_class_id() const
{
	return CLASS_PCX_IMPORT;
}

const char*
PCXImportDescC::get_name() const
{
	return "PCX Image";
}

const char*
PCXImportDescC::get_desc() const
{
	return "Importer for PCX images";
}

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

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

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

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

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

const ClassIdC&
PCXImportDescC::get_required_device_driver( uint32 ui32Idx )
{
	return PajaSystem::CLASS_OPENGL_DEVICEDRIVER;
}

uint32
PCXImportDescC::get_ext_count() const
{
	return 1;
}

const char*
PCXImportDescC::get_ext( uint32 ui32Index ) const
{
	if( ui32Index == 0 )
		return "pcx";
	return 0;
}



//////////////////////////////////////////////////////////////////////////
//
//  Simple image effect class descriptor.
//


ImageDescC::ImageDescC()
{
	// empty
}

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

void*
ImageDescC::create()
{
	return (void*)ImageEffectC::create_new();
}

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

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

ClassIdC
ImageDescC::get_class_id() const
{
	return CLASS_SIMPLEIMAGE_EFFECT;
};

const char*
ImageDescC::get_name() const
{
	return "Simple Image";
}

const char*
ImageDescC::get_desc() const
{
	return "Simple Image effect";
}

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

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

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

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

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

const ClassIdC&
ImageDescC::get_required_device_driver( uint32 ui32Idx )
{
	return PajaSystem::CLASS_OPENGL_DEVICEDRIVER;
}

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

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

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

ImageDescC		g_rImageDesc;
PCXImportDescC	g_rPCXImportDesc;


#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 2;
}


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

__declspec( dllexport )
ClassDescC*
get_classdesc( int32 i )
{
	if( i == 0 )
		return &g_rImageDesc;
	else if( i == 1 )
		return &g_rPCXImportDesc;
	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 "ImagePlugin.dll - Simple Image (c)2000 memon/moppi productions";
}

#endif

//////////////////////////////////////////////////////////////////////////
//
// PCX Importer class implementation.
//


PCXImportC::PCXImportC() :
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_ui32TextureId( 0 )
{
	// empty
}

PCXImportC::PCXImportC( EditableI* pOriginal ) :
	ImportableImageI( pOriginal ),
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_ui32TextureId( 0 )
{
	// empty
}

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

	// Delete creted texture.
	if( m_ui32TextureId )
		glDeleteTextures( 1, &m_ui32TextureId );

	delete m_pData;
}

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

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

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

void
PCXImportC::copy( EditableI* pEditable )
{
	// empty
}

void
PCXImportC::restore( EditableI* pEditable )
{
	PCXImportC*	pFile = (PCXImportC*)pEditable;

	m_ui32TextureId = pFile->m_ui32TextureId;
	m_pData = pFile->m_pData;
	m_i32Width = pFile->m_i32Width;
	m_i32Height = pFile->m_i32Height;
	m_i32Bpp = pFile->m_i32Bpp;
	m_sFileName = pFile->m_sFileName;
}

const char*
PCXImportC::get_filename()
{
	return m_sFileName.c_str();
}

void
PCXImportC::set_filename( const char* szName )
{
	m_sFileName = szName;
}

bool
PCXImportC::read_encoded_block( uint32 ui32Size, uint8* pBuffer, FILE* pStream )
{
	uint32	ui32Count;
	uint32	ui32Val;
	uint8*	pDest = pBuffer;

	for( uint32 i = 0; i < ui32Size; ) {

		ui32Val = fgetc( pStream );
		ui32Count = 1;

		if( feof( pStream ) )
			return true;

		if( (ui32Val & 0xC0) == 0xC0 ) {
			ui32Count = ui32Val & 0x3F;
			ui32Val = fgetc( pStream );
			if( feof( pStream ) )
				return false;
		}

		if( (ui32Count + i) > ui32Size ) {
			return false;
		}

		for( uint32 j = 0; j < ui32Count; j++ )
			*pDest++ = (uint8)ui32Val;


		i += ui32Count;
	}

	return true;
}


bool
PCXImportC::load_file( const char* szName, DemoInterfaceC* pInterface )
{
	FILE*		pStream;
	PCXHeaderS	rHeader;

	if( (pStream = fopen( szName, "rb" )) == 0 ) {
		return false;
	}

	// Store file name.
	m_sFileName = szName;


	// Read header
	fread( &rHeader, 1, sizeof( rHeader ), pStream );

	// We only support BPP images
	if( rHeader.m_ui8BPP != 8 )
		return false;

	// Store width and height
	m_i32Width = rHeader.m_ui16BytesPerLine;
	m_i32Height = (rHeader.m_ui16YMax - rHeader.m_ui16YMin) + 1;

	if( rHeader.m_ui8NPlanes == 1 ) {
		// Read 8-bit image
		uint32	ui32Size = m_i32Width * m_i32Height;
		uint8*	pBuffer = new uint8[ui32Size];

		// Encode
		if( !read_encoded_block( ui32Size, pBuffer, pStream ) )
			return false;

		if( rHeader.m_ui16PalInfo == 2 ) {
			// Greyscale
			m_pData = pBuffer;
			m_i32Bpp = 8;
		}
		else {
			// Read palette.
			uint8	ui8Palette[256 * 3];
			fseek( pStream, SEEK_END, -768 );
			fread( ui8Palette, 256 * 3, 1, pStream );

			m_pData = new uint8[m_i32Width * m_i32Height * 3];
			uint8*	pSrc = pBuffer;
			uint8*	pDest = m_pData;

			// Convert paletted to RGB
			for( uint32 i = 0; i < m_i32Height; i++ ) {
				for( uint32 j = 0; j < m_i32Width; j++ ) {
					*pDest++ = ui8Palette[(*pSrc) * 3 + 2];
					*pDest++ = ui8Palette[(*pSrc) * 3 + 1];
					*pDest++ = ui8Palette[(*pSrc) * 3 + 0];
					pSrc++;
				}
			}

			m_i32Bpp = 24;
			delete [] pBuffer;
		}

	}
	else if( rHeader.m_ui8NPlanes == 3 ) {
		// Read 24-bit image.
		uint32	ui32Size = m_i32Width * m_i32Height * 3;
		uint8*	pBuffer = new uint8[ui32Size];

		// Encode
		if( !read_encoded_block( ui32Size, pBuffer, pStream ) )
			return false;

		uint8*	pDest = m_pData;
		m_pData = new uint8[ui32Size];

		// Convert to interleaved RGB
		for( uint32 i = 0; i < m_i32Height; i++ ) {
			for( uint32 j = 0; j < m_i32Width; j++ ) {
				*pDest++ = pBuffer[i * m_i32Width * 3 + j];
				*pDest++ = pBuffer[i * m_i32Width * 3 + j + m_i32Width];
				*pDest++ = pBuffer[i * m_i32Width * 3 + j + m_i32Width * 2];
			}
		}

		delete [] pBuffer;

		m_i32Bpp = 24;
	}


	// flip vertically.
	uint32	ui32RowSize = m_i32Width * (m_i32Bpp / 8);
	uint8*	pTmpLine = new uint8[ui32RowSize];
	for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
		uint8*	pTopLine = m_pData + (ui32YTop * ui32RowSize);
		uint8*	pBottomLine = m_pData + (ui32YBot * ui32RowSize);
		memcpy( pTmpLine, pTopLine, ui32RowSize );
		memcpy( pTopLine, pBottomLine, ui32RowSize );
		memcpy( pBottomLine, pTmpLine, ui32RowSize );
	}
	delete [] pTmpLine;

	fclose( pStream );

	return true;
}

void
PCXImportC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	ImportableI::initialize( ui32Reason, pInterface );

	DeviceContextC* pContext = pInterface->get_device_context();

	OpenGLDeviceC*	pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
	if( !pDevice )
		return;

	if( ui32Reason == INIT_DEVICE_CHANGED ) {

		if( pDevice->get_state() == DEVICE_STATE_SHUTTINGDOWN ) {
			// Delete textures
			glDeleteTextures( 1, &m_ui32TextureId );
			m_ui32TextureId = 0;
		}

	}
}

ClassIdC
PCXImportC::get_class_id()
{
	return CLASS_PCX_IMPORT;
}

const char*
PCXImportC::get_class_name()
{
	return "PCX Image";
}


float32
PCXImportC::get_width()
{
	return m_i32Width;
}

float32
PCXImportC::get_height()
{
	return m_i32Height;
}

int32
PCXImportC::get_data_width()
{
	return m_i32Width;
}

int32
PCXImportC::get_data_height()
{
	return m_i32Height;
}

int32
PCXImportC::get_data_pitch()
{
	return m_i32Width;
}

int32
PCXImportC::get_data_bpp()
{
	return m_i32Bpp;
}

uint8*
PCXImportC::get_data()
{
	return m_pData;
}



static
uint32
ceil_power2( uint32 ui32Num )
{
	uint32	i = (ui32Num & -ui32Num);
	while( i < ui32Num )
		i <<= 1;
	return i;
}

static
int32
nearest_power2( int32 i32Num )
{
	int32	i = ceil_power2( i32Num );	// bigger
	int32	j = i / 2;					// smaller

	int32	i32DiffI = i - i32Num;
	int32	i32DiffJ = i32Num - j;

	// diff J has to be twice as little as diffI to be chosen.
	i32DiffI /= 2;

	if( i32DiffJ < i32DiffI )
		return j;

	return i;
}

void
PCXImportC::upload_texture()
{
	glGenTextures( 1, &m_ui32TextureId );
	glBindTexture( GL_TEXTURE_2D, m_ui32TextureId );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

	uint8*	pResampleData = 0;

	int32	i32Width = m_i32Width;
	int32	i32Height = m_i32Height;
	uint8*	pData = m_pData;

	// Find nearest power of two size
	int32	i32WidthPow2 = nearest_power2( m_i32Width );
	int32	i32HeightPow2 = nearest_power2( m_i32Height );

	// If the power of two size differs from the original data -> resample.
	if( i32WidthPow2 != m_i32Width || i32HeightPow2 != m_i32Height ) {
		pResampleData = new uint8[i32WidthPow2 * i32HeightPow2 * (m_i32Bpp / 8)];
		
		ImageResampleC::resample_bilinear( m_i32Bpp, m_pData, m_i32Width, m_i32Height, pResampleData, i32WidthPow2, i32HeightPow2 );

		pData = pResampleData;
		i32Width = i32WidthPow2;
		i32Height = i32HeightPow2;
	}

	// Upload the image data
	if( m_i32Bpp == 8 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA , i32Width, i32Height, 0, GL_ALPHA , GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp == 24 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, i32Width, i32Height, 0, GL_RGB, GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp == 32 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, i32Width, i32Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData );
	}

	// Delete resample data
	if( pResampleData )
		delete [] pResampleData;
}

void
PCXImportC::bind_texture( DeviceInterfaceI* pInterface, uint32 ui32Properties )
{
	if( !pInterface || pInterface->get_class_id() != CLASS_OPENGL_DEVICEDRIVER )
		return;

	if( !m_ui32TextureId ) {
		upload_texture();
	}
	else
		glBindTexture( GL_TEXTURE_2D, m_ui32TextureId );

	if( ui32Properties & IMAGE_LINEAR ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	}

	if( ui32Properties & IMAGE_CLAMP ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	}

}

const char*
PCXImportC::get_info()
{
	static char	szInfo[256];
	_snprintf( szInfo, 255, "%s %d x %d x %dbit", m_i32Bpp == 8 ? "GREY" : "RGB", m_i32Width, m_i32Height, m_i32Bpp );
	return szInfo;
}

ClassIdC
PCXImportC::get_default_effect()
{
	return CLASS_SIMPLEIMAGE_EFFECT;
}


int32
PCXImportC::get_duration()
{
	return -1;
}

float32
PCXImportC::get_start_label()
{
	return 0;
}

float32
PCXImportC::get_end_label()
{
	return 0;
}

void
PCXImportC::eval_state( int32 i32Time )
{
	// empty
}


enum PCXImportChunksE {
	CHUNK_PCXIMPORT_BASE =	0x1000,
	CHUNK_PCXIMPORT_DATA =	0x2000,
};

const uint32	PCXIMPORT_VERSION_1 = 1;
const uint32	PCXIMPORT_VERSION = 2;


uint32
PCXImportC::save( SaveC* pSave )
{
	uint32		ui32Error = IO_OK;
	uint8		ui8Tmp;
	std::string	sStr;

	// file base
	pSave->begin_chunk( CHUNK_PCXIMPORT_BASE, PCXIMPORT_VERSION );
		sStr = m_sFileName;
		if( sStr.size() > 255 )
			sStr.resize( 255 );
		ui32Error = pSave->write_str( sStr.c_str() );
	pSave->end_chunk();

	// file data
	pSave->begin_chunk( CHUNK_PCXIMPORT_DATA, PCXIMPORT_VERSION );
		ui32Error = pSave->write( &m_i32Width, sizeof( m_i32Width ) );
		ui32Error = pSave->write( &m_i32Height, sizeof( m_i32Height ) );
		ui8Tmp = (uint8)m_i32Bpp;
		ui32Error = pSave->write( &ui8Tmp, sizeof( ui8Tmp ) );
		ui32Error = pSave->write( m_pData, m_i32Width * m_i32Height * (m_i32Bpp / 8) );
	pSave->end_chunk();

	return ui32Error;
}

uint32
PCXImportC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;
	char	szStr[256];
	uint8	ui8Tmp;

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_PCXIMPORT_BASE:
			{
				if( pLoad->get_chunk_version() == PCXIMPORT_VERSION ) {
					ui32Error = pLoad->read_str( szStr );
					m_sFileName = szStr;
				}
			}
			break;

		case CHUNK_PCXIMPORT_DATA:
			{
				// delete old data if any
				delete m_pData;

				// version handling
				if( pLoad->get_chunk_version() == PCXIMPORT_VERSION_1 ) {
					// load new
					ui32Error = pLoad->read( &m_i32Width, sizeof( m_i32Width ) );
					ui32Error = pLoad->read( &m_i32Height, sizeof( m_i32Height ) );
					ui32Error = pLoad->read( &ui8Tmp, sizeof( ui8Tmp ) );
					m_i32Bpp = ui8Tmp;
					m_pData = new uint8[m_i32Width * m_i32Height * (m_i32Bpp / 8)];
					ui32Error = pLoad->read( m_pData, m_i32Width * m_i32Height * (m_i32Bpp / 8) );

					// The old version had the data upside down, flip vertically.
					uint32	ui32RowSize = m_i32Width * (m_i32Bpp / 8);
					uint8*	pTmpLine = new uint8[ui32RowSize];
					for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
						uint8*	pTopLine = m_pData + (ui32YTop * ui32RowSize);
						uint8*	pBottomLine = m_pData + (ui32YBot * ui32RowSize);
						memcpy( pTmpLine, pTopLine, ui32RowSize );
						memcpy( pTopLine, pBottomLine, ui32RowSize );
						memcpy( pBottomLine, pTmpLine, ui32RowSize );
					}
					delete [] pTmpLine;
				}
				else if( pLoad->get_chunk_version() == PCXIMPORT_VERSION ) {
					// load new
					ui32Error = pLoad->read( &m_i32Width, sizeof( m_i32Width ) );
					ui32Error = pLoad->read( &m_i32Height, sizeof( m_i32Height ) );
					ui32Error = pLoad->read( &ui8Tmp, sizeof( ui8Tmp ) );
					m_i32Bpp = ui8Tmp;
					m_pData = new uint8[m_i32Width * m_i32Height * (m_i32Bpp / 8)];
					ui32Error = pLoad->read( m_pData, m_i32Width * m_i32Height * (m_i32Bpp / 8) );
				}
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}


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

ImageEffectC::ImageEffectC()
{
	//
	// 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(	ParamFloatC::create_new( m_pTraGizmo, "Rotation", 0, ID_TRANSFORM_ROT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0, 0, 1.0f ) );

	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 );

	m_pAttGizmo->add_parameter(	ParamColorC::create_new( m_pAttGizmo, "Fill Color", ColorC( 1, 1, 1, 1 ), ID_ATTRIBUTE_COLOR,
							PARAM_STYLE_COLORPICKER, PARAM_ANIMATABLE ) );
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Fill Image", SUPERCLASS_IMAGE, NULL_CLASSID, ID_ATTRIBUTE_FILE, PARAM_STYLE_FILE, PARAM_ANIMATABLE ) );
}

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

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

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

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

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

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

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

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

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

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

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

GizmoI*
ImageEffectC::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
ImageEffectC::get_class_id()
{
	// Return the class ID. Should be same as in the class descriptor.
	return CLASS_SIMPLEIMAGE_EFFECT;
}

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

void
ImageEffectC::set_default_file( int32 i32Time, FileHandleC* pHandle )
{
	// Sets the default file.

	// Get the file parameter.
	ParamFileC*	pParam = (ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_FILE );

	// Begin Undo block.
	UndoC*	pOldUndo = pParam->begin_editing( get_undo() );
		// Set the file.
		pParam->set_file( i32Time, pHandle );
	// End undo block.
	pParam->end_editing( pOldUndo );
}

ParamI*
ImageEffectC::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_ROTATION )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_ROT );
	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
ImageEffectC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	EffectI::initialize( ui32Reason, pInterface );
}

void
ImageEffectC::eval_state( PajaTypes::int32 i32Time )
{
	DeviceContextC* pContext = m_pDemoInterface->get_device_context();
	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();

	Matrix2C	rPosMat, rRotMat, rScaleMat, rPivotMat;

	Vector2C	rScale;
	Vector2C	rPos;
	Vector2C	rPivot;
	float32		f32Rot;

	// 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 );
	((ParamFloatC*)m_pTraGizmo->get_parameter( ID_TRANSFORM_ROT ))->get_val( i32Time, f32Rot );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE ))->get_val( i32Time, rScale );

	// Calculate transformation matrix.
	rPivotMat.set_trans( rPivot );
	rPosMat.set_trans( rPos );
	rRotMat.set_rot( f32Rot / 180.0f * M_PI );
	rScaleMat.set_scale( rScale ) ;
	m_rTM = rPivotMat * rRotMat * rScaleMat * rPosMat;

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

	// Get the size from the fiel or use the defautls if no file.
	ImportableImageI*	pImp = 0;
	FileHandleC*		pHandle = 0;
	int32				i32FileTime = 0;

	// Get file handle, and file evaluation time.
	// The evaluation time is calculated from the file time offset and time scale.
	((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_FILE ))->get_file( i32Time, pHandle, i32FileTime );

	if( pHandle ) {
		// Get importable.
		pImp = (ImportableImageI*)pHandle->get_importable();

		// Evaluate the importable, NOTE: use the file evaluation time here,
		// instead of the time passed to the Composition::EffectI::eval_state().
		pImp->eval_state( i32FileTime );

		f32Width = (float32)pImp->get_width() * 0.5f;
		f32Height = (float32)pImp->get_height() * 0.5f;
	}


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

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

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

	m_rVertices[3][0] = -f32Width;		// bottom-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 fill color.
	((ParamColorC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_COLOR ))->get_val( i32Time, m_rFillColor );


	//
	// Render the effect
	//

	// Get the OpenGL device.
	OpenGLDeviceC*	pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
	if( !pDevice )
		return;

	// Get the OpenGL viewport.
	OpenGLViewportC*	pViewport = (OpenGLViewportC*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	if( !pViewport )
		return;

	// Set orthographic projection.
	pViewport->set_ortho( m_rBBox, m_rBBox[0][0], m_rBBox[1][0], m_rBBox[1][1], m_rBBox[0][1] );

	// Get file handle and image.
	if( pImp ) {
		// If there is image set it as current texture.
		pImp->bind_texture( pDevice, IMAGE_CLAMP | IMAGE_LINEAR );
		glEnable( GL_TEXTURE_2D );
	}
	else
		glDisable( GL_TEXTURE_2D );

	glDisable( GL_DEPTH_TEST );
	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glDepthMask( GL_FALSE );

	// Set color
	glColor4fv( m_rFillColor );


	// Draw rectangle.
	glBegin( GL_QUADS );

	glTexCoord2f( 0, 0 );
	glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );
	
	glTexCoord2f( 1, 0 );
	glVertex2f( m_rVertices[1][0], m_rVertices[1][1] );
	
	glTexCoord2f( 1, 1 );
	glVertex2f( m_rVertices[2][0], m_rVertices[2][1] );

	glTexCoord2f( 0, 1 );
	glVertex2f( m_rVertices[3][0], m_rVertices[3][1] );

	glEnd();

	glDepthMask( GL_TRUE );
}

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

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

bool
ImageEffectC::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 ImageEffectChunksE {
	CHUNK_IMAGEEFFECT_BASE =		0x1000,
	CHUNK_IMAGEEFFECT_TRANSGIZMO =	0x2000,
	CHUNK_IMAGEEFFECT_ATTRIBGIZMO =	0x3000,
};

const uint32	IMAGEEFFECT_VERSION = 1;

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

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

	// Transform
	pSave->begin_chunk( CHUNK_IMAGEEFFECT_TRANSGIZMO, IMAGEEFFECT_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();

	// Attribute
	pSave->begin_chunk( CHUNK_IMAGEEFFECT_ATTRIBGIZMO, IMAGEEFFECT_VERSION );
		ui32Error = m_pAttGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

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

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

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

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}
