//-------------------------------------------------------------------------
//
// File:		DemopajaPlayerC.cpp
// Purpose:		Moppi Demopaja Player class implementation
// Author:		memon <memon@inside.org>
// Version:		0.51
//
//-------------------------------------------------------------------------
//	Copyright (c) 2000 - 2002 Moppi Productions. All Rights Reserved.
//  This file is part of Moppi Demopaja SDK.
//  For conditions of distribution and use, see the accompanying license.txt file.
//  http://moppi.inside.org/demopaja/
//-------------------------------------------------------------------------


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

#include "PajaTypes.h"
#include "LayerC.h"
#include "SceneC.h"
#include "EffectI.h"
#include "EffectPostProcessI.h"
#include "ImportableI.h"
#include "UndoC.h"
#include "FileListC.h"
#include "FileIO.h"
#include "DemopajaVersion.h"
#include "DemoInterfaceC.h"
#include "DeviceContextC.h"
#include "TimeContextC.h"
#include "DemopajaPlayerC.h"
#include <string>
#include <vector>
#include "OpenGLDriver\OpenGLDeviceC.h"
#include "GraphicsDeviceI.h"
#include "GraphicsViewportI.h"
#include "SubSceneC.h"
#include "AudioSpectrumC.h"

#include "fmod.h"
#include "fmod_errors.h"

#include "LoadMemC.h"

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



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




const uint32	DEMOPAJA_DOC_VERSION_1 = (1 << 16);
const uint32	DEMOPAJA_DOC_VERSION = (1 << 16) | 1;

enum DocChunksE {
	CHUNK_FILE				= 0x1000,
	CHUNK_SCENE				= 0x2000,
	CHUNK_PROPERTIES		= 0x3000,
	CHUNK_MUSIC				= 0x4000,
	CHUNK_DEVICEDRIVER		= 0x5000,
};



// Static variables
bool			DemopajaPlayerC::m_bMusicSystemPresent = false;
FSOUND_STREAM*	DemopajaPlayerC::m_pMusicStream = 0;
int32			DemopajaPlayerC::m_i32MusicChannel = 0;
HINSTANCE		DemopajaPlayerC::m_hInst = 0;


static
int32
get_time()
{
	LARGE_INTEGER	d;
	double			i, j;
	QueryPerformanceCounter( &d );
	i = (double)d.QuadPart;
	QueryPerformanceFrequency( &d );
	j = (double)d.QuadPart;
	return (int32)((i / j) * 1000.0);
}


static
void
show_error()
{
	LPVOID lpMsgBuf;
	FormatMessage( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER | 
		FORMAT_MESSAGE_FROM_SYSTEM | 
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
		(LPTSTR) &lpMsgBuf,
		0,
		NULL 
	);
	// Process any inserts in lpMsgBuf.
	// ...
	// Display the string.
	MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
	// Free the buffer.
	LocalFree( lpMsgBuf );
}


DemopajaPlayerC::DemopajaPlayerC() :
	m_pDeviceContext( 0 ),
	m_pTimeContext( 0 ),
	m_pFileList( 0 ),
	m_pScene( 0 ),
	m_bFullscreen( false ),
	m_pMusicData( 0 ),
	m_ui32MusicDataSize( 0 ),
	m_pLoadCallback( 0 ),
	m_ui32FullscreenWidth( 0 ),
	m_ui32FullscreenHeight( 0 )
{
	// empty
}

DemopajaPlayerC::~DemopajaPlayerC()
{
	if( m_pScene )
		m_pScene->release();
	if( m_pFileList )
		m_pFileList->release();
	delete m_pDeviceContext;
	delete m_pTimeContext;
}


void
DemopajaPlayerC::initialise_data( uint32 ui32Reason )
{
	uint32				i;

	// init importables
	for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
		ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
		if( !pImp )
			continue;
		pImp->initialize( ui32Reason, m_pDemoInterface );
	}

	// init effects
	for( i = 0; i < m_pScene->get_layer_count(); i++ ) {
		LayerC*	pLayer = m_pScene->get_layer( i );
		if( !pLayer )
			continue;
		for( uint32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( !pEffect )
				continue;
			pEffect->initialize( ui32Reason, m_pDemoInterface );
		}
	}
}

// file io
uint32
DemopajaPlayerC::load_scene( LoadC* pLoad )
{
	//
	// load chunks
	//

	uint32		ui32Error = IO_OK;
	uint32		i;
	char		szStr[2049];
	FactoryC*	pFactory = pLoad->get_factory();

	//
	// Set old file format compatible graphics driver
	//
	m_sGraphicsDeviceName = "OpenGL Device Driver";
	m_rGraphicsDeviceId = CLASS_OPENGL_DEVICEDRIVER;


	if( m_pLoadCallback )
		m_pLoadCallback( 0.0f );

	std::vector<FileHandleC*>	rHandles;

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

		switch( pLoad->get_chunk_id() ) {

		case CHUNK_MUSIC:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					ui32Error = pLoad->read_str( szStr );
					m_sMusicFile = szStr;
				}
			}
			break;

		case CHUNK_DEVICEDRIVER:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					// Read class id
					uint32	ui32ClassId[2];
					pLoad->read( &ui32ClassId, sizeof( ui32ClassId ) );
					// Read class name
					ui32Error = pLoad->read_str( szStr );

					ClassIdC	rClassId( ui32ClassId[0], ui32ClassId[1] );

					DeviceInterfaceI*	pDev = (DeviceInterfaceI*)pFactory->create( rClassId );

					if( pDev ) {
						ui32Error = pDev->load( pLoad );
						m_pDeviceContext->register_interface( pDev );
					}
					else {
						char		szClassId[32];
						std::string	sMsg;

						_snprintf( szClassId, 31, " (0x%08x, 0x%08x)", ui32ClassId[0], ui32ClassId[1] );	// this isnt the way to go, but I hate c++ streams

						sMsg = "- Device driver: Device driver ";
						sMsg += szStr;
						sMsg += szClassId;
						sMsg += " cannot be created. Propably missing DLL.";

						pLoad->add_error_message( sMsg.c_str() );

						ui32Error = IO_ERROR_READ;
					}
				}
			}
			break;

		case CHUNK_SCENE:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION )
					ui32Error = m_pScene->load( pLoad, false );
			}
			break;

		case CHUNK_FILE:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {

					uint32	ui32ClassId[2];
					int32	i32Flags = 0;
					int32	i32ParentId = FILEHANDLE_INVALID_ID;
					bool	bFolder = false;
					std::string	sFolderName;

					if( pLoad->get_chunk_version() == DEMOPAJA_DOC_VERSION_1 ) {
						// read class id
						ui32Error = pLoad->read( &ui32ClassId, sizeof( ui32ClassId ) );
						// read class name
						ui32Error = pLoad->read_str( szStr );
					}
					else {
						// read flags
						ui32Error = pLoad->read( &i32Flags, sizeof( i32Flags ) );
						// read parent ID
						ui32Error = pLoad->read( &i32ParentId, sizeof( i32ParentId ) );

						if( i32Flags & FILEHANDLE_FOLDER ) {
							// read folder data
							// fodler name
							ui32Error = pLoad->read_str( szStr );
							sFolderName = szStr;
							bFolder = true;
						}
						else {
							// read file data
							// read class id
							ui32Error = pLoad->read( &ui32ClassId, sizeof( ui32ClassId ) );
							// read class name
							ui32Error = pLoad->read_str( szStr );
						}
					}

					FileHandleC*	pHandle = 0;

					if( bFolder ) {
						// add to the root folder, patch parents later.
						pHandle = m_pFileList->add_folder( sFolderName.c_str(), 0 );
						pHandle->set_parent_id( i32ParentId );
					}
					else {
						ClassIdC	rClassId( ui32ClassId[0], ui32ClassId[1] );

						ImportableI*	pImportable = (ImportableI*)pFactory->create( rClassId );

						if( pImportable )  {
							ui32Error = pImportable->load( pLoad );
							// add importable to file list
							// but test for duplicates
							// add to the root folder, patch parents later.
							pHandle = m_pFileList->add_file( pImportable, 0 );
							pHandle->set_parent_id( i32ParentId );
						}
						else {
							char		szClassId[32];
							std::string	sMsg;

							_snprintf( szClassId, 31, " (0x%08x, 0x%08x)", ui32ClassId[0], ui32ClassId[1] );	// this isnt the way to go, but I hate c++ streams

							sMsg = "- Load File: Importable ";
							sMsg += szStr;
							sMsg += szClassId;
							sMsg += " cannot be created. Propably missing DLL.";

							pLoad->add_error_message( sMsg.c_str() );

							ui32Error = IO_ERROR_READ;
						}
					}
					rHandles.push_back( pHandle);

				}
			}
			break;

		default:
			break;
		}

		pLoad->close_chunk();

		if( m_pLoadCallback ) {
			float32	f32Percent = 0;
			if( pLoad->get_file_size() )
				f32Percent = (float32)pLoad->get_read_pos() / (float32)pLoad->get_file_size();
			m_pLoadCallback( f32Percent );
		}

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

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

	//
	// patch file handles
	//
	for( i = 0; i < pLoad->get_file_handle_patch_count(); i++ ) {
		FileHandleC*	pHandle = rHandles[pLoad->get_file_handle_patch_id( i )];
		if( pHandle ) {
			void**	pPointer = pLoad->get_file_handle_patch_pointer( i );
			(*pPointer) = (void*)pHandle;
		}
		else {
			pLoad->add_error_message( "Load Doc: FileHandle patching failed." );
		}
	}

	// Init devices

	if( !m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE ) ) {
		DeviceInterfaceI*	pDev = (DeviceInterfaceI*)pFactory->create( CLASS_OPENGL_DEVICEDRIVER );

		::MessageBox( 0, "No default device driver.", "Load Error", MB_ICONERROR | MB_OK );
	
		if( pDev )
			m_pDeviceContext->register_interface( pDev );
		else
			pLoad->add_error_message( "Load Doc: Could not initialise graphics device." );

		return IO_ERROR_OPEN;
	}

	return IO_OK;
}

void
DemopajaPlayerC::register_plugin( ClassDescC* pDesc )
{
	m_rFactory.register_class( pDesc );
}

void
DemopajaPlayerC::load_plugins( const char* szPath )
{
	m_rFactory.load_plugins( szPath );
}


PajaTypes::uint32
DemopajaPlayerC::load_demo( const char* szFileName )
{
	//
	// load scene
	//

	LoadC	rLoad( &m_rFactory );
//	LoadZipC	rLoad( &m_rFactory );

	if( rLoad.open( szFileName, "MOPPI|DEMOPAJA", 14 ) != IO_OK ) {
//	if( rLoad.open( szFileName, "demo.dmo", "MOPPI|DEMOPAJA", 14 ) != IO_OK ) {
		if( rLoad.get_error() == IO_ERROR_FORMAT )
			::MessageBox( 0, "File is in wrong format.", "Load Error", MB_ICONERROR | MB_OK );
		else
			::MessageBox( 0, "Cannot open file for input.", "Load Error", MB_ICONERROR | MB_OK );
		return rLoad.get_error();
	}

	uint32	ui32Error;

	ui32Error = load_scene( &rLoad );

	if( ui32Error != IO_OK && ui32Error != IO_END ) {
		std::string	sErrorLog;

		sErrorLog = "Loading file failed.\n\n";

		if( rLoad.get_error_message_count() ) {
			sErrorLog += "Following error occured:\n";
			for( uint32 i = 0; i < rLoad.get_error_message_count(); i++ ) {
				sErrorLog += rLoad.get_error_message( i );
				sErrorLog += "\n";
			}
		}

		::MessageBox( 0, sErrorLog.c_str(), "Load Error", MB_ICONERROR | MB_OK );

		return ui32Error;
	}

	return IO_OK;
}


PajaTypes::uint32
DemopajaPlayerC::load_demo( PajaTypes::uint8* pData, PajaTypes::uint32 ui32Size )
{
	if( !pData || !ui32Size )
		return IO_ERROR_OPEN;

	//
	// load scene
	//

	LoadMemC	rLoad( &m_rFactory );

	if( rLoad.open( pData, ui32Size, "MOPPI|DEMOPAJA", 14 ) != IO_OK ) {
		if( rLoad.get_error() == IO_ERROR_FORMAT )
			::MessageBox( 0, "File is in wrong format.", "Load Error", MB_ICONERROR | MB_OK );
		else
			::MessageBox( 0, "Cannot open file for input.", "Load Error", MB_ICONERROR | MB_OK );
		return rLoad.get_error();
	}

	uint32	ui32Error;

	ui32Error = load_scene( &rLoad );

	if( ui32Error != IO_OK && ui32Error != IO_END ) {
		std::string	sErrorLog;

		sErrorLog = "Loading file failed.\n\n";

		if( rLoad.get_error_message_count() ) {
			sErrorLog += "Following error occured:\n";
			for( uint32 i = 0; i < rLoad.get_error_message_count(); i++ ) {
				sErrorLog += rLoad.get_error_message( i );
				sErrorLog += "\n";
			}
		}

		::MessageBox( 0, sErrorLog.c_str(), "Load Error", MB_ICONERROR | MB_OK );

		return ui32Error;
	}

	return IO_OK;
}


bool
DemopajaPlayerC::init_music_system()
{
	//
	// Init FSOUND
	//

	FSOUND_SetOutput( FSOUND_OUTPUT_DSOUND );

	if( !FSOUND_Init( 44100, 32, 0 ) ) {
		::MessageBox( NULL, FMOD_ErrorString( FSOUND_GetError() ), "FMOD Error", MB_OK | MB_ICONERROR );
		m_bMusicSystemPresent = false;
		return false;
	}
	else {
		m_bMusicSystemPresent = true;
	}

	//
	// load the music
	//

	// If the music data pointer is set, try to load music from mem.
	if( m_pMusicData ) {

		TRACE( "load music data from memory\n" );

//		m_pMusicStream = FSOUND_Stream_OpenFile( (const char*)m_pMusicData, FSOUND_LOADMEMORY | FSOUND_NORMAL | FSOUND_LOOP_NORMAL, m_ui32MusicDataSize );
		m_pMusicStream = FSOUND_Stream_Open( (const char*)m_pMusicData, FSOUND_NORMAL | FSOUND_LOOP_NORMAL, 0, m_ui32MusicDataSize );
		if( !m_pMusicStream )
			return false;
	}
	else {
		// Load from a file.
		if( !m_sMusicFile.empty() && m_bMusicSystemPresent ) {

			// is here something loaded already?
			if( m_pMusicStream )
				FSOUND_Stream_Close( m_pMusicStream );

			char szFname[_MAX_FNAME];

			m_pMusicStream = FSOUND_Stream_Open( m_sMusicFile.c_str(), FSOUND_NORMAL | FSOUND_LOOP_NORMAL, 0, 0 );
//			m_pMusicStream = FSOUND_Stream_OpenFile( m_sMusicFile.c_str(), FSOUND_NORMAL | FSOUND_LOOP_NORMAL, 0 );

			if( !m_pMusicStream ) {
				// try to load it from current dir
				char szExt[_MAX_EXT];
				_splitpath( m_sMusicFile.c_str(), 0, 0, szFname, szExt );
				strcat( szFname, szExt );

				//			TRACE( "plaaplaamusic file: %s\n", szFname );
				
				m_pMusicStream = FSOUND_Stream_Open( szFname, FSOUND_NORMAL | FSOUND_LOOP_NORMAL, 0, 0 );
				if( !m_pMusicStream )
					return false;
			}
		}
	}

	return true;
}


void
DemopajaPlayerC::play_music( int32 i32Time )
{
	if( m_bMusicSystemPresent && m_pMusicStream ) {
		FSOUND_Stream_SetTime( m_pMusicStream, 0 );
		m_i32MusicChannel = FSOUND_Stream_Play( FSOUND_FREE, m_pMusicStream );
		FSOUND_SetPan( m_i32MusicChannel, FSOUND_STEREOPAN );
	}
	else {
		m_i32MusicStartTime = get_time();
	}

	m_f64MusicTimeScale = (float64)m_pScene->get_beats_per_min() * (float64)m_pScene->get_qnotes_per_beat() * 256.0 / 60.0;
}

void
DemopajaPlayerC::stop_music()
{
	if( m_pMusicStream )
		FSOUND_Stream_Stop( m_pMusicStream );
}

int32
DemopajaPlayerC::get_music_pos()
{
	if( m_bMusicSystemPresent && m_pMusicStream ) {
		int32	i32StreamTime = FSOUND_Stream_GetTime( m_pMusicStream );
		int32	i32Time = (int32)((float64)i32StreamTime / 1000.0 * m_f64MusicTimeScale);
		return i32Time;
	}
	else {
		int32	i32DiffTime = get_time() - m_i32MusicStartTime;
		return (int32)((float64)i32DiffTime / 1000.0 * m_f64MusicTimeScale);
	}
}


bool
DemopajaPlayerC::init( HINSTANCE hInst, bool bFullScreen )
{
	m_bFullscreen = bFullScreen;
	m_hInst = hInst;

	// Create vital containers and interfaces.
	m_pScene = (SceneC*)SceneC::create_new();
	m_pFileList = (FileListC*)FileListC::create_new();
	m_pDeviceContext = new DeviceContextC;
	m_pTimeContext = new TimeContextC( m_pScene->get_beats_per_min(), m_pScene->get_qnotes_per_beat(), m_pScene->get_edit_accuracy() );
	m_pDemoInterface = new DemoInterfaceC( m_pDeviceContext, m_pTimeContext, m_pFileList, &m_rFactory );
	m_rDeviceFeedback.set_scene( m_pScene );
	m_rDeviceFeedback.set_demo_interface( m_pDemoInterface );

	// Register internal classes.
	m_rFactory.register_class( &g_rSceneEffectDesc );
	m_rFactory.register_class( &g_rSceneImportDesc );
	m_rFactory.register_class( &g_rSceneToImageImportDesc );
	m_rFactory.register_class( &g_rSharedBufferImportDesc );
	m_rFactory.register_class( &g_rAudioSpectrumDesc );

	return true;
}


int32
DemopajaPlayerC::run()
{
	int32	i, j, k;

	if( !m_pScene || !m_pFileList )
		return -1;

	//
	// Init music
	//
	init_music_system();

	// Set time settings
	m_pTimeContext->set_time_settings( m_pScene->get_beats_per_min(), m_pScene->get_qnotes_per_beat(), m_pScene->get_edit_accuracy() );

	//
	// Init devices
	//

	for( i = 0; i < m_pDeviceContext->get_interface_count(); i++ ) {
		DeviceInterfaceI*	pDev = m_pDeviceContext->get_interface( i );

		if( pDev->get_super_class_id() == SUPERCLASS_GRAPHICSDEVICE ) {
			GraphicsDeviceI*	pGDev = (GraphicsDeviceI*)pDev;

			int32	i32LayoutWidth = m_pScene->get_layout_width();
			int32	i32LayoutHeight = m_pScene->get_layout_height();

			HDC dc = GetDC( 0 ); // desktop dc
			int32	i32DesktopBPP = GetDeviceCaps( dc, BITSPIXEL );
			ReleaseDC( 0, dc );

			// Calculate correct mapping
			GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGDev->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
			BBox2C	rLayout( Vector2C( 0, 0 ), Vector2C( (float32)i32LayoutWidth, (float32)i32LayoutHeight ) );
			pViewport->set_viewport( rLayout );
			pViewport->set_layout( rLayout );

			// Init graphics device
			if( m_bFullscreen ) {

				if( m_ui32FullscreenWidth == 0 || m_ui32FullscreenHeight == 0 ) {
					m_ui32FullscreenWidth = i32LayoutWidth;
					m_ui32FullscreenHeight = i32LayoutHeight;
				}

				if( !pGDev->init( m_hInst, NULL, 123, GRAPHICSDEVICE_CREATE_FULLSCREEN,
//					i32LayoutWidth, i32LayoutHeight, i32DesktopBPP, &m_rDeviceFeedback ) ) {
					m_ui32FullscreenWidth, m_ui32FullscreenHeight, i32DesktopBPP, &m_rDeviceFeedback ) ) {
					::MessageBox( NULL, "Could not initialise graphics device (init fullscreen).", "Error", MB_OK );
					return -1;
				}
			}
			else
			{

				TRACE( "layout: %d x %d\n", i32LayoutWidth, i32LayoutHeight );

				if( !pGDev->init( m_hInst, NULL, 123, GRAPHICSDEVICE_CREATE_WINDOWED,
					i32LayoutWidth, i32LayoutHeight, i32DesktopBPP, &m_rDeviceFeedback ) ) {
					::MessageBox( NULL, "Could not initialise graphics device (init windowed).", "Error", MB_OK );
					return -1;
				}
			}

			// Set temp buffer size.
			pGDev->set_temp_graphicsbuffer_size( i32LayoutWidth, i32LayoutHeight );
		}
	}

	//
	// Init effects
	//
	initialise_data( INIT_INITIAL_UPDATE );

	GraphicsDeviceI*	pDevice = (GraphicsDeviceI*)m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pDevice )
		return -1;

	//
	// Start music
	//

	play_music( 0 );


	//
	// Demo loop
	//

	int32			i32FrameSizeInTicks = 256 / m_pScene->get_edit_accuracy();
	int32			i32RenderTime = 0;
	int32			i32MaxRenderTime = m_pScene->get_duration();
	MSG				rMsg;
	int32			i32FrameID = 1;

	std::vector<Composition::EffectI*>	rEffectStack;
	std::vector<PajaTypes::int32>				rEffectTimeStack;

	do {
		if( PeekMessage( &rMsg , pDevice->get_hwnd() , 0 , 0 , PM_REMOVE ) ) {
			TranslateMessage( &rMsg ) ;
			DispatchMessage( &rMsg );
		}
		else {
			i32RenderTime = get_music_pos();
			if( i32RenderTime >= i32MaxRenderTime ) {
				break;
			}

			m_pTimeContext->set_frame_id( i32FrameID );

			// set the device active
			pDevice->activate();

			// clear device
			pDevice->clear_device( GRAPHICSDEVICE_ALLBUFFERS, m_pScene->get_layout_color() );

			if( !pDevice->begin_draw() )
				continue;

/*

			//
			// draw effects
			//

			pDevice->begin_effects();

			for( int32 i = m_pScene->get_layer_count() - 1; i >= 0 ; i-- ) {

				LayerC*	pLayer = m_pScene->get_layer( i );

				if( !pLayer )
					continue;

				if( !pLayer->get_timesegment()->is_visible( i32RenderTime ) )
					continue;

				int32	i32TimeOffset;
				i32TimeOffset = pLayer->get_timesegment()->get_segment_start();

				for( int32 j = pLayer->get_effect_count() - 1; j >= 0; j-- ) {
					EffectI*	pEffect = pLayer->get_effect( j );

					if( !pEffect )
						continue;

					if( pEffect->get_timesegment()->is_visible( i32RenderTime - i32TimeOffset ) ) {
						pEffect->eval_state( i32RenderTime - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start()) );
					}
				}
			}
*/

			//
			// draw effects
			//

			pDevice->begin_effects();

			for( i = m_pScene->get_layer_count() - 1; i >= 0 ; i-- ) {

				LayerC*	pLayer = m_pScene->get_layer( i );

				if( !pLayer )
					continue;

				if( !pLayer->get_timesegment()->is_visible( i32RenderTime ) )
					continue;

				// Make sure there's enough space for all the layers in the stack.
				if( rEffectStack.empty() || rEffectStack.size() < pLayer->get_effect_count() )
				{
					rEffectStack.resize( pLayer->get_effect_count() + 1 );
					rEffectTimeStack.resize( pLayer->get_effect_count() + 1 );
				}

				// Init stack
				for( j = 0; j < (int32)rEffectStack.size(); j++ )
				{
					rEffectStack[j] = 0;
					rEffectTimeStack[j] = 0;
				}

				int32	i32StackHead = 0;

				int32	i32TimeOffset;
				i32TimeOffset = pLayer->get_timesegment()->get_segment_start();

				for( int32 j = pLayer->get_effect_count() - 1; j >= 0; j-- ) {
					EffectI*	pEffect = pLayer->get_effect( j );

					if( !pEffect )
						continue;

					if( pEffect->get_timesegment()->is_visible( i32RenderTime - i32TimeOffset ) )
					{
						if( pEffect->get_super_class_id() == SUPERCLASS_EFFECT_POST_PROCESS )
						{
							EffectPostProcessI*	pPostProcess = (EffectPostProcessI*)pEffect;

							// use render target instead
							GraphicsBufferI*	pOldGBuffer = 0;
							GraphicsBufferI*	pGBuffer = pPostProcess->get_render_target();

							if( !pGBuffer )
								continue;

							pOldGBuffer = pDevice->set_render_target( pGBuffer );

							if( pGBuffer->get_state() == DEVICE_STATE_LOST )
							{
								pDevice->set_render_target( pOldGBuffer );
								continue;
							}

							GraphicsViewportI*	pBufViewport = (GraphicsViewportI*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
							if( !pBufViewport )
							{
								pDevice->set_render_target( pOldGBuffer );
								continue;
							}

							// Save current layout and viewport
							BBox2C	rOldLayout = pBufViewport->get_layout();
							BBox2C	rOldViewport = pBufViewport->get_viewport();
							float32	f32OldAspect = pBufViewport->get_pixel_aspect_ratio();

							// Set new based on the scene effects bounding box
							BBox2C	rNewLayout( Vector2C( 0, 0 ), Vector2C( (float32)m_pScene->get_layout_width(), (float32)m_pScene->get_layout_height() ) );
							BBox2C	rNewViewport( Vector2C( 0, 0 ), Vector2C( (float32)m_pScene->get_layout_width(), (float32)m_pScene->get_layout_height() ) );
							BBox2C	rOldClipLayout;


							// set newly calculated layout viewport
							// the viewport is set after begin_draw() because begin draw saves the viewport and projection matrices.
							pBufViewport->set_layout( rNewLayout );
							pBufViewport->set_viewport( rNewViewport );

							// Set correct pixels size.
							// It's often that the graphics buffer has different aspect ratio than the rendering
							// output is. Problems arise on perspective projection etc. The following code
							// handles the situation.
							float32	f32AspectVP = (float32)pBufViewport->get_height() / (float32)pBufViewport->get_width();
							float32	f32AspectHW = (float32)m_pScene->get_layout_height() / (float32)m_pScene->get_layout_width();
							pBufViewport->set_pixel_aspect_ratio( f32AspectHW / f32AspectVP  );


							// clear device
							ColorC	BGCol = m_pScene->get_layout_color();
							BGCol[3] = 0.0f;
							pDevice->clear_device( GRAPHICSDEVICE_ALLBUFFERS, BGCol );

							pDevice->begin_draw();

							//
							// draw effects
							//

							pDevice->begin_effects();

							// Render all effects in the stack into the effect's render target.
							for( k = 0; k < i32StackHead; k++ )
							{
								if( rEffectStack[k] )
								{
									EffectI*	pFX = rEffectStack[k];
									pFX->eval_state( rEffectTimeStack[k] );
								}
							}

							// restore old layout and viewport
							pBufViewport->set_layout( rOldLayout );
							pBufViewport->set_viewport( rOldViewport );
							pBufViewport->set_pixel_aspect_ratio( f32OldAspect );

							pDevice->end_effects();
							pDevice->end_draw();

							// flush device (swap buffers)
							pDevice->flush();

							pDevice->set_render_target( pOldGBuffer );

							// Reset stack.
							i32StackHead = 0;
						}

						rEffectStack[i32StackHead] = pEffect;
						rEffectTimeStack[i32StackHead] = i32RenderTime - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());
						i32StackHead++;
					}
				}

				// Flush the stack.
				for( k = 0; k < i32StackHead; k++ )
				{
					if( rEffectStack[k] )
					{
						EffectI*	pFX = rEffectStack[k];
						pFX->eval_state( rEffectTimeStack[k] );
					}
				}
			}

			pDevice->end_effects();

			pDevice->end_draw();

			// flush device (swap buffers)
			pDevice->flush();
		}
	} while( rMsg.message != DP_END_PREVIEW );


	//
	// Shut down
	//

	stop_music();

	m_pScene->release();
	m_pFileList->release();
	delete m_pDeviceContext;
	delete m_pTimeContext;
	delete m_pDemoInterface;

	m_pScene = 0;
	m_pFileList = 0;
	m_pDeviceContext = 0;
	m_pTimeContext = 0;
	m_pDemoInterface = 0;

	if( m_bMusicSystemPresent ) {
		if( m_pMusicStream )
			FSOUND_Stream_Close( m_pMusicStream );
		FSOUND_Close();
	}

	return 0;
}

void
DemopajaPlayerC::set_music_data( uint8* pData, uint32 ui32Size )
{
	m_pMusicData = pData;
	m_ui32MusicDataSize = ui32Size;
}

void
DemopajaPlayerC::set_load_callback( LoadCallbackF pFunc )
{
	m_pLoadCallback = pFunc;
}

void
DemopajaPlayerC::set_fullscreen_resolution( uint32 ui32Width, uint32 ui32Height )
{
	m_ui32FullscreenWidth = ui32Width;
	m_ui32FullscreenHeight = ui32Height;
}