//
// DX9ASEImport.cpp
//
// DX9 ASE Import Plugin
//
// Copyright (c) 2000-2004 memon/moppi productions
//

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d9.h>
#include <d3dx9math.h>
#include <dxerr9.h>

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableI.h"
#include "DX9DeviceC.h"
#include "Dx9ViewportC.h"
#include "FileIO.h"
#include "ASELoaderC.h"
#include "HelperC.h"
#include "LightC.h"
#include "CameraC.h"

#include "DX9MeshPlugin.h"

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

using namespace DX9MeshPlugin;

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

//////////////////////////////////////////////////////////////////////////
//
// ASE Importer class implementation.
//

DX9ASEImportC::DX9ASEImportC() :
	m_bFileRefsValid( false ),
	m_i32CurrentFrame( 0 )
{
}

DX9ASEImportC::DX9ASEImportC( EditableI* pOriginal ) :
	ImportableI( pOriginal ),
	m_bFileRefsValid( false ),
	m_i32CurrentFrame( 0 )
{
}

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

	uint32	i;
	for( i = 0; i < m_rHelpers.size(); i++ )
		delete m_rHelpers[i];
	for( i = 0; i < m_rCameras.size(); i++ )
		delete m_rCameras[i];
	for( i = 0; i < m_rLights.size(); i++ )
		delete m_rLights[i];
}

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

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

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

void
DX9ASEImportC::copy( EditableI* pEditable )
{
	// Importables which loads the data from a file does
	// not have to implement this method, since they will
	// not be duplicated.
}

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

	m_sFileName = pFile->m_sFileName;
	m_bFileRefsValid = pFile->m_bFileRefsValid;
	m_rFileRefs = pFile->m_rFileRefs;

	m_rHelpers = pFile->m_rHelpers;
	m_rCameras = pFile->m_rCameras;
	m_rLights = pFile->m_rLights;
	m_i32FPS = pFile->m_i32FPS;
	m_i32TicksPerFrame = pFile->m_i32TicksPerFrame;
	m_i32FirstFrame = pFile->m_i32FirstFrame;
	m_i32LastFrame = pFile->m_i32LastFrame;
}

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

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

// loads the file
bool
DX9ASEImportC::load_file( const char* szName, DemoInterfaceC* pInterface )
{
	m_pDemoInterface = pInterface;

	DeviceContextC* pContext = m_pDemoInterface->get_device_context();

	// Get the OpenGL device.
	DX9DeviceC*	pDevice = (DX9DeviceC*)pContext->query_interface( CLASS_DX9_DEVICEDRIVER );
	if( !pDevice )
		return false;
	LPDIRECT3DDEVICE9	pD3DDevice = pDevice->get_d3ddevice();


	ASELoaderC	rLdr;

	if( !rLdr.load( szName, pInterface ) )
		return false;

	uint32	i;
	for( i = 0; i < m_rHelpers.size(); i++ )
		delete m_rHelpers[i];
	for( i = 0; i < m_rCameras.size(); i++ )
		delete m_rCameras[i];
	for( i = 0; i < m_rLights.size(); i++ )
		delete m_rLights[i];

	rLdr.get_cameras( m_rCameras );
	rLdr.get_lights( m_rLights );
	rLdr.get_scenegraph( m_rHelpers );
	rLdr.get_time_parameters( m_i32FPS, m_i32TicksPerFrame, m_i32FirstFrame, m_i32LastFrame );

	m_sFileName = szName;

	m_bFileRefsValid = false;

	return true;
}

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

	DeviceContextC* pContext = pInterface->get_device_context();
	TimeContextC* pTimeContext = pInterface->get_time_context();

	DX9DeviceC*	pDevice = (DX9DeviceC*)pContext->query_interface( CLASS_DX9_DEVICEDRIVER );
	if( !pDevice )
		return;

	if( ui32Reason == INIT_DEVICE_CHANGED )
	{

		if( pDevice->get_state() == DEVICE_STATE_SHUTTINGDOWN )
		{
		}

	}
	else if( ui32Reason == INIT_DEVICE_INVALIDATE )
	{
		// Invalidate device resources.
		if( pDevice->get_state() == DEVICE_STATE_LOST )
		{
		}
	}
	else if( ui32Reason == INIT_DEVICE_VALIDATE )
	{
		// Restore device resources.
		if( pDevice->get_state() == DEVICE_STATE_LOST )
		{
		}
	}
	else if( ui32Reason == INIT_INITIAL_UPDATE )
	{
	}
}

SuperClassIdC
DX9ASEImportC::get_super_class_id()
{
	return SUPERCLASS_IMPORT;
}

ClassIdC
DX9ASEImportC::get_class_id()
{
	return CLASS_DX9ASE_IMPORT;
}

const char*
DX9ASEImportC::get_class_name()
{
	return CLASS_DX9ASE_IMPORT_NAME;
}

const char*
DX9ASEImportC::get_info()
{
	static char	szInfo[256];
	_snprintf( szInfo, 255, "%d helpers, %d cameras, %d lights", m_rHelpers.size(), m_rCameras.size(), m_rLights.size() );
	return szInfo;
}

ClassIdC
DX9ASEImportC::get_default_effect()
{
	return NULL_CLASSID; // CLASS_DX9MESH_EFFECT;
}


int32
DX9ASEImportC::get_duration()
{
	if( !m_pDemoInterface )
		return -1;
	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();
	int32	i32Length = m_i32LastFrame - m_i32FirstFrame;
	return (int32)pTimeContext->convert_fps_to_time( i32Length, m_i32FPS );
}

float32
DX9ASEImportC::get_start_label()
{
	return (float32)m_i32FirstFrame;
}

float32
DX9ASEImportC::get_end_label()
{
	return (float32)m_i32LastFrame;
}

void
DX9ASEImportC::eval_state( int32 i32Time )
{
	if( !m_pDemoInterface )
		return;

	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();

	int32	i32Length = (m_i32LastFrame - m_i32FirstFrame) * m_i32TicksPerFrame;

	if( i32Length > 0 )
	{
		m_i32CurrentFrame = (int32)(pTimeContext->convert_time_to_fps( i32Time, m_i32FPS ) * (float64)m_i32TicksPerFrame);
		m_i32CurrentFrame %= i32Length;
		m_i32CurrentFrame += m_i32FirstFrame * m_i32TicksPerFrame;

		while( m_i32CurrentFrame < (m_i32FirstFrame * m_i32TicksPerFrame) )
			m_i32CurrentFrame += i32Length;
		while( m_i32CurrentFrame >= (m_i32LastFrame * m_i32TicksPerFrame) )
			m_i32CurrentFrame -= i32Length;
	}
	else
		m_i32CurrentFrame = 0;
}

uint32
DX9ASEImportC::get_reference_file_count()
{
	if( !m_bFileRefsValid )
	{
		m_rFileRefs.clear();
		for( uint32 i = 0; i < m_rHelpers.size(); i++ )
			count_file_references( m_rHelpers[i] );
		m_bFileRefsValid = true;
	}

	return (uint32)m_rFileRefs.size();
}

FileHandleC*
DX9ASEImportC::get_reference_file( uint32 ui32Index )
{
	if( !m_bFileRefsValid )
	{
		m_rFileRefs.clear();
		for( uint32 i = 0; i < m_rHelpers.size(); i++ )
			count_file_references( m_rHelpers[i] );
		m_bFileRefsValid = true;
	}

	if( ui32Index >= 0 && ui32Index < m_rFileRefs.size() )
		return m_rFileRefs[ui32Index];

	return 0;
}

uint32
DX9ASEImportC::get_helper_count()
{
	return (uint32)m_rHelpers.size();
}

HelperC*
DX9ASEImportC::get_helper( uint32 ui32Index )
{
	if( ui32Index >= 0 && ui32Index < m_rHelpers.size() )
		return m_rHelpers[ui32Index];
	return 0;
}

uint32
DX9ASEImportC::get_camera_count()
{
	return (uint32)m_rCameras.size();
}

CameraC*
DX9ASEImportC::get_camera( uint32 ui32Index )
{
	if( ui32Index >= 0 && ui32Index < m_rCameras.size() )
		return m_rCameras[ui32Index];
	return 0;
}

uint32
DX9ASEImportC::get_light_count()
{
	return (uint32)m_rLights.size();
}

LightC*
DX9ASEImportC::get_light( uint32 ui32Index )
{
	if( ui32Index >= 0 && ui32Index < m_rLights.size() )
		return m_rLights[ui32Index];
	return 0;
}

void
DX9ASEImportC::get_time_parameters( int32& i32FPS, int32& i32TicksPerFrame, int32& i32FirstFrame, int32& i32LastFrame )
{
	i32FPS = m_i32FPS;
	i32TicksPerFrame = m_i32TicksPerFrame;
	i32FirstFrame = m_i32FirstFrame;
	i32LastFrame = m_i32LastFrame;
}

int32
DX9ASEImportC::get_current_frame() const
{
	return m_i32CurrentFrame;
}

void
DX9ASEImportC::count_file_references( HelperC* pHelper )
{
/*
	if( pMesh->get_texture() )
		m_rFileRefs.push_back( pMesh->get_texture() );

	for( uint32 i = 0; i < pMesh->get_child_count(); i++ )
		count_file_references( (HelperC*)pMesh->get_child( i ) );
*/
}

enum ASEImportChunksE {
	CHUNK_ASEIMPORT_BASE			= 0x1000,
	CHUNK_ASEIMPORT_HELPER		= 0x2000,
	CHUNK_ASEIMPORT_CAMERA		= 0x3000,
	CHUNK_ASEIMPORT_LIGHT			= 0x4000,
};

const uint32	ASEIMPORT_VERSION = 1;

uint32
DX9ASEImportC::save_helper( SaveC* pSave, HelperC* pItem, uint32 ui32ParentId, uint32& ui32Id )
{
	uint32	ui32Error = IO_OK;
	uint32	ui32CurId = ui32Id;

	pSave->begin_chunk( CHUNK_ASEIMPORT_HELPER, ASEIMPORT_VERSION );
		ui32Error = pSave->write( &ui32ParentId, sizeof( ui32ParentId ) );
		ui32Error = pSave->write( &ui32Id, sizeof( ui32Id ) );
		ui32Error = pItem->save( pSave );
		ui32Id++;
	pSave->end_chunk();

	for( uint32 i = 0; i < pItem->get_child_count(); i++ ) {
		ui32Error = save_helper( pSave, (HelperC*)pItem->get_child( i ), ui32CurId, ui32Id );
	}

	return ui32Error;
}

HelperC*
DX9ASEImportC::find_parent( HelperC* pItem, uint32 ui32ParentId )
{
	HelperC*	pParent = 0;

	if( pItem->get_id() == ui32ParentId )
		return pItem;

	for( uint32 i = 0; i < pItem->get_child_count(); i++ ) {
		pParent = find_parent( (HelperC*)pItem->get_child( i ), ui32ParentId );
		if( pParent )
			return pParent;
	}

	return 0;
}

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

	// file base
	pSave->begin_chunk( CHUNK_ASEIMPORT_BASE, ASEIMPORT_VERSION );
		sStr = m_sFileName;
		if( sStr.size() > 255 )
			sStr.resize( 255 );
		ui32Error = pSave->write_str( sStr.c_str() );
		ui32Error = pSave->write( &m_i32FPS, sizeof( m_i32FPS ) );
		ui32Error = pSave->write( &m_i32TicksPerFrame, sizeof( m_i32TicksPerFrame ) );
		ui32Error = pSave->write( &m_i32FirstFrame, sizeof( m_i32FirstFrame ) );
		ui32Error = pSave->write( &m_i32LastFrame, sizeof( m_i32LastFrame ) );
	pSave->end_chunk();

	// file data
	uint32	i;

	// cameras
	for( i = 0; i < m_rCameras.size(); i++ ) {
		pSave->begin_chunk( CHUNK_ASEIMPORT_CAMERA, ASEIMPORT_VERSION );
			ui32Error = m_rCameras[i]->save( pSave );
		pSave->end_chunk();
	}

	// lights
	for( i = 0; i < m_rLights.size(); i++ ) {
		pSave->begin_chunk( CHUNK_ASEIMPORT_LIGHT, ASEIMPORT_VERSION );
			ui32Error = m_rLights[i]->save( pSave );
		pSave->end_chunk();
	}

	uint32	ui32Id = 0;

	// helper items
	for( i = 0; i < m_rHelpers.size(); i++ ) {
		save_helper( pSave, m_rHelpers[i], 0xffffffff, ui32Id );
	}

	return ui32Error;
}

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

	uint32	ui32Id = 0;
	uint32	ui32ParentId = 0;

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_ASEIMPORT_BASE:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				ui32Error = pLoad->read_str( szStr );
				m_sFileName = szStr;
				ui32Error = pLoad->read( &m_i32FPS, sizeof( m_i32FPS ) );
				ui32Error = pLoad->read( &m_i32TicksPerFrame, sizeof( m_i32TicksPerFrame ) );
				ui32Error = pLoad->read( &m_i32FirstFrame, sizeof( m_i32FirstFrame ) );
				ui32Error = pLoad->read( &m_i32LastFrame, sizeof( m_i32LastFrame ) );
			}
			break;

		case CHUNK_ASEIMPORT_CAMERA:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				CameraC*	pCam = new CameraC;
				ui32Error = pCam->load( pLoad );
				m_rCameras.push_back( pCam );
			}
			break;

		case CHUNK_ASEIMPORT_LIGHT:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				LightC*	pCam = new LightC;
				ui32Error = pCam->load( pLoad );
				m_rLights.push_back( pCam );
			}
			break;

		case CHUNK_ASEIMPORT_HELPER:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				HelperC*	pHelper = new HelperC;

				ui32Error = pLoad->read( &ui32ParentId, sizeof( ui32ParentId ) );
				ui32Error = pLoad->read( &ui32Id, sizeof( ui32Id ) );
				ui32Error = pHelper->load( pLoad );
				pHelper->set_id( ui32Id );

				HelperC*	pParent = 0;
				if( ui32ParentId != 0xffffffff ) {
					for( uint32 i = 0; i < m_rHelpers.size(); i++ ) {
						pParent = find_parent( m_rHelpers[i], ui32ParentId );
						if( pParent )
							break;
					}
				}

				if( pParent )
					pParent->add_child( pHelper );
				else
					m_rHelpers.push_back( pHelper );
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}
