/**
 * $Id: BlenderPlayerCtl.cpp,v 1.7 2004/05/21 09:18:42 kester Exp $
 *
 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL/BL DUAL LICENSE BLOCK *****
 * Blender Player Active X control class implementation.
 */

#include "stdafx.h"
#include <mshtml.h> 

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

//#define AX_NO_SOUND

#include "BlenderPlayer.h"
#include "BlenderPlayerCtl.h"
#include "BlenderPlayerPpg.h"
#include "SafeControl.h"

#pragma warning (disable : 4786)

#include "GEN_messaging.h"
#include "SYS_System.h"
#include "NG_NetworkScene.h"
#include "NG_LoopBackNetworkDeviceInterface.h"
#include "KX_KetsjiEngine.h"
#include "KX_BlenderSceneConverter.h"
#include "RAS_OpenGLRasterizer.h"
#include "KX_PythonInit.h"
#include "KX_PyConstraintBinding.h"

#include "SND_DeviceManager.h"

#include "GPW_Canvas.h"
#include "GPW_KeyboardDevice.h"
#include "GPC_MouseDevice.h"
#include "GPW_System.h"
#include "GPC_RenderTools.h"


/**********************************
 * Begin Blender include block
 **********************************/
#ifdef __cplusplus
extern "C"
{
#endif  // __cplusplus

#include "BLI_blenlib.h"

#include "DNA_scene_types.h"

#include "BLO_readfile.h"

#ifdef __cplusplus
}
#endif // __cplusplus
/**********************************
 * End Blender include block
 **********************************/


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNCREATE(CBlenderPlayerCtrl, COleControl)

CRITICAL_SECTION CBlenderPlayerCtrl::s_cs;
bool CBlenderPlayerCtrl::s_criticalSectionInitialized = false;
STR_String CBlenderPlayerCtrl::s_messageAddress;
STR_String CBlenderPlayerCtrl::s_messageLoadURLSubject;
int CBlenderPlayerCtrl::s_numControls = 0;
int CBlenderPlayerCtrl::s_numEnginesRunning = 0;


/////////////////////////////////////////////////////////////////////////////
// Message map

BEGIN_MESSAGE_MAP(CBlenderPlayerCtrl, COleControl)
	//{{AFX_MSG_MAP(CBlenderPlayerCtrl)
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MBUTTONDOWN()
	ON_WM_MBUTTONUP()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_MOUSEMOVE()
	//}}AFX_MSG_MAP
	ON_OLEVERB(AFX_IDS_VERB_EDIT, OnEdit)
	ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CBlenderPlayerCtrl, COleControl)
	//{{AFX_DISPATCH_MAP(CBlenderPlayerCtrl)
	DISP_PROPERTY_NOTIFY(CBlenderPlayerCtrl, "showFrameCount", m_showFrameCount, OnShowFrameCountChanged, VT_BOOL)
	DISP_PROPERTY_NOTIFY(CBlenderPlayerCtrl, "showProfileInfo", m_showProfileInfo, OnShowProfileInfoChanged, VT_BOOL)
	DISP_PROPERTY_NOTIFY(CBlenderPlayerCtrl, "blenderURL", m_blenderURL, OnBlenderURLChanged, VT_BSTR)
	DISP_PROPERTY_NOTIFY(CBlenderPlayerCtrl, "showProperties", m_showProperties, OnShowPropertiesChanged, VT_BOOL)
	DISP_PROPERTY_NOTIFY(CBlenderPlayerCtrl, "loadingURL", m_loadingURL, OnLoadingURLChanged, VT_BSTR)
	DISP_PROPERTY_NOTIFY(CBlenderPlayerCtrl, "frameRate", m_frameRate, OnFrameRateChanged, VT_I2)
	DISP_PROPERTY_NOTIFY(CBlenderPlayerCtrl, "useFileBackColor", m_useFileBackColor, OnUseFileBackColorChanged, VT_BOOL)
	DISP_FUNCTION(CBlenderPlayerCtrl, "Rewind", Rewind, VT_EMPTY, VTS_NONE)
	DISP_FUNCTION(CBlenderPlayerCtrl, "SendMessage", SendMessage, VT_BOOL, VTS_BSTR VTS_BSTR VTS_BSTR VTS_BSTR)
	DISP_STOCKPROP_BACKCOLOR()
	DISP_STOCKPROP_FORECOLOR()
	//}}AFX_DISPATCH_MAP
	DISP_FUNCTION_ID(CBlenderPlayerCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()


/////////////////////////////////////////////////////////////////////////////
// Event map

BEGIN_EVENT_MAP(CBlenderPlayerCtrl, COleControl)
	//{{AFX_EVENT_MAP(CBlenderPlayerCtrl)
	// NOTE - ClassWizard will add and remove event map entries
	//    DO NOT EDIT what you see in these blocks of generated code !
	//}}AFX_EVENT_MAP
END_EVENT_MAP()


/////////////////////////////////////////////////////////////////////////////
// Property pages

// Add more property pages as needed.  Remember to increase the count!
BEGIN_PROPPAGEIDS(CBlenderPlayerCtrl, 2)
	PROPPAGEID(CBlenderPlayerPropPage::guid)
	PROPPAGEID(CLSID_CColorPropPage)
END_PROPPAGEIDS(CBlenderPlayerCtrl)


/////////////////////////////////////////////////////////////////////////////
// Initialize class factory and guid

IMPLEMENT_OLECREATE_EX(CBlenderPlayerCtrl, "BLENDERPLAYER.BlenderPlayerCtrl.1",
	0x5db05cb8, 0x7751, 0x469d, 0xa1, 0xdd, 0x45, 0xc8, 0xc2, 0x1, 0xc0, 0x13)


/////////////////////////////////////////////////////////////////////////////
// Type library ID and version

IMPLEMENT_OLETYPELIB(CBlenderPlayerCtrl, _tlid, _wVerMajor, _wVerMinor)


/////////////////////////////////////////////////////////////////////////////
// Interface IDs

const IID BASED_CODE IID_DBlenderPlayer =
		{ 0x75be7171, 0x64c0, 0x4125, { 0x99, 0xa8, 0x1a, 0xe5, 0xe, 0xa4, 0x9d, 0x6c } };
const IID BASED_CODE IID_DBlenderPlayerEvents =
		{ 0x7990fce5, 0x9a6, 0x4e39, { 0x94, 0xb6, 0x3e, 0x47, 0xe, 0xd5, 0xc2, 0x2a } };


/////////////////////////////////////////////////////////////////////////////
// Control type information

static const DWORD BASED_CODE _dwBlenderPlayerOleMisc =
	OLEMISC_ACTIVATEWHENVISIBLE |
	OLEMISC_SETCLIENTSITEFIRST |
	OLEMISC_INSIDEOUT |
	OLEMISC_CANTLINKINSIDE |
	OLEMISC_RECOMPOSEONRESIZE;

IMPLEMENT_OLECTLTYPE(CBlenderPlayerCtrl, IDS_BLENDERPLAYER, _dwBlenderPlayerOleMisc)


/////////////////////////////////////////////////////////////////////////////
// CBlenderPlayerCtrl::CBlenderPlayerCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CBlenderPlayerCtrl

BOOL CBlenderPlayerCtrl::CBlenderPlayerCtrlFactory::UpdateRegistry(BOOL bRegister)
{
	if (bRegister) {
        HRESULT hr = S_OK ;
        // Register as safe for scripting
        hr = CreateComponentCategory(CATID_SafeForScripting, L"Controls that are safely scriptable");
        if (FAILED(hr))
           return FALSE;
        hr = RegisterCLSIDInCategory(m_clsid, CATID_SafeForScripting);
        if (FAILED(hr))
            return FALSE;
        // register as safe for initializing
        hr = CreateComponentCategory(CATID_SafeForInitializing,
                L"Controls safely initializable from persistent data");
        if (FAILED(hr))
            return FALSE;
        hr = RegisterCLSIDInCategory(m_clsid, CATID_SafeForInitializing);
        if (FAILED(hr))
            return FALSE;

	// TODO: Verify that your control follows apartment-model threading rules.
	// Refer to MFC TechNote 64 for more information.
	// If your control does not conform to the apartment-model rules, then
	// you must modify the code below, changing the 6th parameter from
	// afxRegInsertable | afxRegApartmentThreading to afxRegInsertable.

		return AfxOleRegisterControlClass(
			AfxGetInstanceHandle(),
			m_clsid,
			m_lpszProgID,
			IDS_BLENDERPLAYER,
			IDB_BLENDERPLAYER,
			afxRegInsertable | afxRegApartmentThreading,
			_dwBlenderPlayerOleMisc,
			_tlid,
			_wVerMajor,
			_wVerMinor);
	}
	else {
        HRESULT hr = S_OK ;
        hr = UnRegisterCLSIDInCategory(m_clsid, CATID_SafeForScripting);
        if (FAILED(hr))
            return FALSE;
        hr = UnRegisterCLSIDInCategory(m_clsid, CATID_SafeForInitializing);
        if (FAILED(hr))
            return FALSE;
		return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
	}
}


/////////////////////////////////////////////////////////////////////////////
// CBlenderPlayerCtrl::CBlenderPlayerCtrl - Constructor

CBlenderPlayerCtrl::CBlenderPlayerCtrl()
// C4355 warning on m_blenderData(this) and m_loadingData(this), but this is okay.
#pragma warning(disable: 4355)
: m_blenderData(this), m_loadingData(this), m_refresher(this, 1000)
{

	/* Initialize the critical section object used to lock game engine access */
	if (!s_criticalSectionInitialized) {
		InitializeCriticalSection(&s_cs);
		s_criticalSectionInitialized = true;
		s_messageAddress = "host_application";
		s_messageLoadURLSubject = "load_url";
	}
	s_numControls++;

	InitializeIIDs(&IID_DBlenderPlayer, &IID_DBlenderPlayerEvents);

	// Initialize member variables
	m_runState = stateIdle;
	m_showFrameCount = FALSE;
	m_showProfileInfo = FALSE;
	m_showProperties = FALSE;
	m_useFileBackColor = FALSE;
	m_drawingMetafile = false;
	m_blenderDataValid = false;
	m_loadingDataValid = false;
	m_lastProgress = 0;
	m_engineInitialized = false;
	m_engineRunning = false;
	m_frameRate = 60;
	m_ketsjiengine = 0;
	m_system = 0;
	m_keyboard = 0;
	m_mouse = 0;
	m_canvas = 0;
	m_rendertools = 0;
	m_rasterizer = 0;
	m_sceneconverter = 0;
	m_networkdevice = 0;
	m_audiodevice = 0;
	m_gamedata = 0;

	// Get loading animation Blender data from the resources
	HINSTANCE hInstApp = AfxGetResourceHandle();
	m_loadingAnimation.load(hInstApp, MAKEINTRESOURCE(IDR_LOAD), "BLEND");
	// Get logo pixmap data from the resources and convert to OpenGL compliant dimensions (128x128)
	m_blenderLogo.load(
		hInstApp, MAKEINTRESOURCE(IDR_LOGO_BLENDER), "RAW",
		115, 32,
		128, 128, RawImageRsrc::alignTopLeft, 8, 8);
	m_nanLogo.load(
		hInstApp, MAKEINTRESOURCE(IDR_LOGO_BLENDER3D), "RAW",
		136, 11,
		256, 256, RawImageRsrc::alignBottomRight, 8, 8);
	RecreateControlWindow();

	m_refresher.Start();
}


/////////////////////////////////////////////////////////////////////////////
// CBlenderPlayerCtrl::~CBlenderPlayerCtrl - Destructor

CBlenderPlayerCtrl::~CBlenderPlayerCtrl()
{
	exitEngine();
	m_refresher.Stop();
	s_numControls--;
}



void CBlenderPlayerCtrl::drawIdle(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	//CBrush bkBrush(TranslateColor(GetBackColor()));
	CBrush bkBrush;
	bkBrush.CreateHatchBrush(HS_BDIAGONAL, TranslateColor(GetForeColor()));
	pdc->FillRect(rcBounds, &bkBrush);
//	pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH)));
}



void CBlenderPlayerCtrl::drawLoadLoading(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	OLE_COLOR oleColor = GetForeColor();
	pdc->FillSolidRect(rcBounds, TranslateColor(oleColor));
}



void CBlenderPlayerCtrl::drawLoadBlender(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	drawEngine(pdc, rcBounds, rcInvalid);
}



void CBlenderPlayerCtrl::drawEngine(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	if (m_engineRunning) {
		if (m_canvas) {
			// Check if the device context is still the same as the one in the canvas.
			HDC hCurrentDC = pdc->GetSafeHdc();
			HDC hCanvasDC = m_canvas->GetHDC();
			if (hCurrentDC != hCanvasDC) {
				// Set the current device context so that we blit to the right device
				m_canvas->SetHDC(hCurrentDC);
			}

			/* Check if the width and height of the control are still the same as set
			 * in the canvas. This will make the control scale in Powerpoint.
			 * We don't want to scale when the control is scaled down.
			 */
			int ww = rcBounds.Width();
			int wh = rcBounds.Height();
			//this->GetControlSize(&ww, &wh);
			int cw = m_canvas->GetWidth();
			int ch = m_canvas->GetHeight();
			// Only size when the area is bigger
			if (((ww * wh) > (cw * ch))) {
				m_canvas->Resize(ww, wh);
			}
		}

		// Update the state of the game engine
		if (m_system) {			
			// Prevent access to the game engine data from other threads
			EnterCriticalSection(&s_cs);
			
			// kick the engine
			m_ketsjiengine->NextFrame();
			
			// render the frame
			m_ketsjiengine->Render();

			// first check if we want to exit
			m_exitRequested = m_ketsjiengine->GetExitCode();
			
			if (m_exitRequested == KX_EXIT_REQUEST_START_OTHER_GAME)
			{
				m_blenderURL = m_ketsjiengine->GetExitString();
			}
			
			// Release private access to the game engine data
			LeaveCriticalSection(&s_cs);
		}
	}
}



void CBlenderPlayerCtrl::drawError(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	drawEngine(pdc, rcBounds, rcInvalid);
}



bool CBlenderPlayerCtrl::initEngine(void)
{
	if (!m_engineInitialized) {

		SYS_SystemHandle syshandle = SYS_GetSystem();
		GEN_init_messaging_system();

		if (syshandle) {
			// Set game engine preferences
			SYS_WriteCommandLineInt(syshandle, "fixedtime", 0);
			SYS_WriteCommandLineInt(syshandle, "vertexarrays", 0);
			// Timing information display
			updateEngineInfoDisplay();
			
			m_rendertools = new GPC_RenderTools();
			if (m_rendertools)
			{
				// create the inputdevices
				m_keyboard = new GPW_KeyboardDevice();
				if (m_keyboard)
				{
					m_mouse = new GPC_MouseDevice();
					if (m_mouse)
					{
						// create a networkdevice
						m_networkdevice = new NG_LoopBackNetworkDeviceInterface();
						if (m_networkdevice)
						{
							// get an audiodevice
							//SND_DeviceManager::SetDeviceType(snd_e_fmoddevice);
							SND_DeviceManager::Subscribe();
							m_audiodevice = SND_DeviceManager::Instance();
							if (m_audiodevice)
							{
								// create a ketsjisystem (only needed for timing and stuff)
								m_system = new GPW_System();
								if (m_system)
								{
									// create the ketsjiengine
									m_ketsjiengine = new KX_KetsjiEngine(m_system);
									
									// set the devices
									m_ketsjiengine->SetKeyboardDevice(m_keyboard);
									m_ketsjiengine->SetMouseDevice(m_mouse);
									m_ketsjiengine->SetNetworkDevice(m_networkdevice);
									m_ketsjiengine->SetRenderTools(m_rendertools);
									m_ketsjiengine->SetNetworkDevice(m_networkdevice);
									m_ketsjiengine->SetAudioDevice(m_audiodevice);
									
									m_ketsjiengine->SetUseFixedTime(false);
									//m_ketsjiengine->SetTimingDisplay(frameRate, profile, properties);
									
									m_engineInitialized = true;
								}
							}
						}
					} 
				}
			}
		}
	}

	return m_engineInitialized;
}



void CBlenderPlayerCtrl::SetGameData(struct BlendFileData *gamedata)
{
	if (m_gamedata) {
		BLO_blendfiledata_free(m_gamedata);
	}

	m_gamedata = gamedata;
}



bool CBlenderPlayerCtrl::LoadGameData(char *fromfile)
{
	BlendReadError error;
	BlendFileData *bfd= BLO_read_from_file(fromfile, &error);

	if (bfd) {
		SetGameData(bfd);
		return true;
	} else {
		return false;
	}
}



bool CBlenderPlayerCtrl::LoadGameData(void *mem, int memsize)
{
	BlendReadError error;
	BlendFileData *bfd= BLO_read_from_memory(mem, memsize, &error);

	if (bfd) {
		SetGameData(bfd);

		return true;
	} else {
		return false;
	}
}

bool CBlenderPlayerCtrl::startEngine(void)
{
	if (m_engineRunning) {
		return false;
	}
	if (!m_engineInitialized) {
		if (!initEngine()) {
			return false;
		}
	}
	if (m_hWnd == NULL) {
		// The device context is not valid unless we're activated and thus have a valid window handle.
		return false;
	}

	// Activate the global Blender database 
	bool isPublisher;
	bool logos = true;
	char* dir;
	CString path = m_blenderData.GetPath();
	switch (m_runState) {
	case stateLoadBlender:
		if (m_loadingDataValid) {
			if (activateBlenderData(m_loadingData, isPublisher)) {
				if (!isPublisher) {
					showError("Loading animation is not a Blender Publisher file.");
					return false;
				}
				logos = !isPublisher;
			}
			else {
				showError("Error loading file.");
				return false;
			}
		}
		else {
			if (!LoadGameData(m_loadingAnimation.getData(), m_loadingAnimation.getDataSize())) {
				return false;
			}
		}
		break;

	case stateRun:
		if (!m_blenderDataValid) {
			return false;
		}
		if (activateBlenderData(m_blenderData, isPublisher)) {
			if (m_loadingDataValid && !isPublisher) {
				showError("Loading animations only allowed with Blender Publisher files.");
				return false;
			}
			logos = !isPublisher;
		}
		else {
			showError("Error loading file.");
			return false;
		}
		break;

	case stateLoadAsyncFailed:
		// Director does not load files asynchronously, try loading it from disk.
		dir = (char*)((LPCTSTR)path);
		if (!LoadGameData(dir)) {
			showError("Error loading file.");
			return false;
		}
		isPublisher = m_gamedata->type == BLENFILETYPE_PUB;
		logos = !isPublisher;
		break;

	case stateError:
		// Until we have an error animation
		if (!LoadGameData(m_loadingAnimation.getData(), m_loadingAnimation.getDataSize())) {
			showError("Error loading file.");
			return false;
		}
		break;

	default:
		return false;
	}

	// Create the canvas.
	int width, height;
	this->GetControlSize(&width, &height);
	CDC* dc = this->GetDC();
	if (!dc) {
		return false;
	}

	HDC hDC = dc->GetSafeHdc();
	m_canvas = new GPW_Canvas(m_hWnd, hDC, width, height);
	if (!m_canvas) {
		return false;
	}

	// Initialize the canvas
	m_canvas->Init();

	if (logos) {
		m_canvas->SetBannerDisplayEnabled(true);
		width = m_blenderLogo.getWidth();
		height = m_blenderLogo.getHeight();
		m_canvas->AddBanner(width, height, width, height,
			(unsigned char*)m_blenderLogo.getData(), GPC_Canvas::alignTopLeft);
		width = m_nanLogo.getWidth();
		height = m_nanLogo.getHeight();
		m_canvas->AddBanner(width, height, width, height,
			(unsigned char*)m_nanLogo.getData(), GPC_Canvas::alignBottomRight);
	}

	// Check if there is a camera, do not start without it.
	if (!m_gamedata->curscene->camera) {
		showError(CString("No camera in the Blender file."));
		return false;
	}

	STR_String startscenename = m_gamedata->curscene->id.name + 2;

	if (m_ketsjiengine)
	{
		// create the rasterizer
		m_rasterizer = new RAS_OpenGLRasterizer(m_canvas);
		m_ketsjiengine->SetCanvas(m_canvas);
		m_ketsjiengine->SetRasterizer(m_rasterizer);
		updateEngineInfoDisplay();
		
		// create a scene converter, create and convert the starting scene
		m_sceneconverter = new KX_BlenderSceneConverter(m_gamedata->main, m_ketsjiengine);
		if (m_sceneconverter)
		{
			m_ketsjiengine->SetSceneConverter(m_sceneconverter);
			
			KX_Scene* startscene = new KX_Scene(m_keyboard,
				m_mouse,
				m_networkdevice,
				m_audiodevice,
				startscenename);
			
			PyObject* m_dictionaryobject = initGamePlayerPythonScripting("Ketsji", psl_Highest);
			m_ketsjiengine->SetPythonDictionary(m_dictionaryobject);

			initRasterizer(m_rasterizer, m_canvas);			
			initGameLogic(startscene);
			initGameKeys();
			initPythonConstraintBinding();
			
			m_sceneconverter->ConvertScene(
				startscenename,
				startscene,
				m_dictionaryobject,
				m_keyboard,
				m_rendertools,
				m_canvas);
			m_ketsjiengine->AddScene(startscene);
			
			if (m_frameRate > 0) {
				UINT nElapse = 1000 / m_frameRate;
				m_refresher.SetInterval(nElapse);
			}
			
			m_audiodevice->StopAllObjects();
			m_rasterizer->Init();
			m_ketsjiengine->StartEngine();
			
			m_engineRunning = true;
			updateFrameColor();
			// Timing information display
			updateEngineInfoDisplay();
		}
	}
	if (!m_engineRunning) {
		stopEngine();
	}
	else {
		s_numEnginesRunning++;
	}
	return m_engineRunning;
}


void CBlenderPlayerCtrl::stopEngine()
{
	/* 
	 * Disable Python exit until we are the last engine being stopped.
	 * This is because there is only one Python interpreter running 
	 * for all engines at the moment.
	 * Until this is changed, we can stop the interpreter only when
	 * the last engine is destroyed. If we would shut it down every time
	 * we shut an engine down, remaining engines will crash on Python use.
	 */
	if (m_engineRunning) {
		s_numEnginesRunning--;
		if (s_numEnginesRunning < 1) {
			exitGamePythonScripting();
		}
	}

	if (m_ketsjiengine)
		m_ketsjiengine->StopEngine();

	if (m_networkdevice)
		m_networkdevice->Disconnect();

	if (m_sceneconverter) {
		delete m_sceneconverter;
		m_sceneconverter = 0;
	}
	if (m_engineRunning) {
		// Free game data
		SetGameData(NULL);
	}
	m_refresher.SetInterval(1000);
	m_engineRunning = false;
}


void CBlenderPlayerCtrl::exitEngine()
{
	stopEngine();

	if (m_ketsjiengine)
	{
		delete m_ketsjiengine;
		m_ketsjiengine = 0;
	}
	if (m_system)
	{
		delete m_system;
		m_system = 0;
	}
	if (m_audiodevice)
	{
		SND_DeviceManager::Unsubscribe();
		m_audiodevice = 0;
	}
	if (m_networkdevice)
	{
		delete m_networkdevice;
		m_networkdevice = 0;
	}
	if (m_mouse)
	{
		delete m_mouse;
		m_mouse = 0;
	}
	if (m_keyboard)
	{
		delete m_keyboard;
		m_keyboard = 0;
	}
	if (m_rasterizer)
	{
		delete m_rasterizer;
		m_rasterizer = 0;
	}
	if (m_rendertools)
	{
		delete m_rendertools;
		m_rendertools = 0;
	}
	if (m_canvas)
	{
		delete m_canvas;
		m_canvas = 0;
	}

	m_engineInitialized = false;
}


bool CBlenderPlayerCtrl::startDownload(void)
{
	bool success;
	if (!m_loadingDataValid && !m_loadingURL.IsEmpty()) {
		success = startLoadingDownload();
	}
	else {
		success = startBlenderDownload();
	}
	return success;
}


bool CBlenderPlayerCtrl::startBlenderDownload(void)
{
	bool success = true;
	InternalSetReadyState(READYSTATE_INTERACTIVE);
	m_blenderData.SetPath(m_blenderURL);
	CFileException error;
	if (m_blenderData.Open(&error)) {
		m_blenderDataValid = false;
		m_runState = stateLoadBlender;
		InvalidateControl();
	}
	else {
		// Director does not load files asynchronously, try loading it from disk.
		m_runState = stateLoadAsyncFailed;
		InvalidateControl();
		/*
		CString str;
		str.Format("Could not open file: %s.", m_blenderURL);
		showError(str);
		success = false;
		*/
	}
	return success;
}


bool CBlenderPlayerCtrl::startLoadingDownload(void)
{
	bool success = true;
	InternalSetReadyState(READYSTATE_INTERACTIVE);
	m_loadingData.SetPath(m_loadingURL);
	CFileException error;
	if (m_loadingData.Open(&error)) {
		m_loadingDataValid = false;
		m_runState = stateLoadLoading;
		// Force a redraw
		InvalidateControl();
	}
	else {
		m_runState = stateError;
		CString str;
		str.Format("Could not open file: %s.", m_loadingURL);
		success = false;
	}
	return success;
}


bool CBlenderPlayerCtrl::activateBlenderData(CBlenderDataPathProperty& blenderData, bool& isPublisher)
{
	bool success = false;

	// Download of Blender data file complete
	int filelen = blenderData.GetLength();
	if (filelen > 0) {
		// Did receive data, try to read it (should test here)
		CWaitCursor waitCursor;
		blenderData.SeekToBegin();
		unsigned char* buf = (unsigned char*)::malloc(filelen);
		blenderData.Read(buf, filelen);

		// Load the Blender file from memory
		success = LoadGameData(buf, filelen);
		isPublisher = m_gamedata->type == BLENFILETYPE_PUB;
		::free(buf);

		waitCursor.Restore();
	}
	else {
		// Something wrong with the download
		CString msg = "Invalid Blender data.";
		showError(msg);
	}

	return success;
}


void CBlenderPlayerCtrl::checkDownload(void)
{
	switch (m_runState) {
	case stateLoadLoading:
		if (GetReadyState() == READYSTATE_COMPLETE) {
			stopEngine();
			m_loadingDataValid = true;
			startBlenderDownload();
			//m_runState = stateLoadBlender;
			//InternalSetReadyState(READYSTATE_INTERACTIVE);
		}
		break;
	case stateLoadBlender:
		if (GetReadyState() == READYSTATE_COMPLETE) {
			stopEngine();
			m_blenderDataValid = true;
			m_runState = stateRun;
		}
		break;
	}
}


void CBlenderPlayerCtrl::showError(const CString& msg)
{
	// First: show a dialog
	CString caption = "Blender Player Error";
	// Temporary disable for Director 8.5 which chokes on the Dialog
	// MessageBox(msg, caption, MB_ICONERROR);

	if (m_engineRunning) {
		stopEngine();
	}
	m_runState = stateError;
}


void CBlenderPlayerCtrl::sendMessage(
		const STR_String& to,
		const STR_String& from,
		const STR_String& subject,
		const STR_String& body)
{
	if (m_networkdevice) {
		// Store a progress message in the network device.
		NG_NetworkMessage* msg = new NG_NetworkMessage(to, from, subject, body);
		m_networkdevice->SendNetworkMessage(msg);
		msg->Release();
	}
}


void CBlenderPlayerCtrl::updateEngineInfoDisplay(void)
{

	SYS_SystemHandle syshandle = SYS_GetSystem();
	if (syshandle) {
		SYS_WriteCommandLineInt(syshandle, "show_framerate", m_showFrameCount);
		SYS_WriteCommandLineInt(syshandle, "show_profile", m_showProfileInfo);
		SYS_WriteCommandLineInt(syshandle, "show_properties", m_showProperties);
	}
	if (m_ketsjiengine)
	{
		// Timing information display
		m_ketsjiengine->SetTimingDisplay(
			m_showFrameCount==TRUE, m_showProfileInfo==TRUE, m_showProperties==TRUE);
	}
}


float CBlenderPlayerCtrl::determineProgress(void) const
{
	float progress;
	if ((m_blenderData.m_ulProgress > 0) && 
		(m_blenderData.m_ulProgressMax != m_blenderData.m_ulProgress)) {
		progress = (float)m_blenderData.m_ulProgress;
		progress /= (float)m_blenderData.m_ulProgressMax;
	}
	else {
		progress = 0.f;
	}
	return progress ;
}


void CBlenderPlayerCtrl::updateLoadingAnimation(float progress)
{
	// Send a message with the current progress
	STR_String to = "";
	STR_String from = "";
	STR_String subject = "progress";
	STR_String body;
	body.Format("%f", progress);
	sendMessage(to, from, subject, body);
}


void CBlenderPlayerCtrl::updateFrameColor(void)
{
	if (m_engineRunning) {
		m_ketsjiengine->SetUseOverrideFrameColor(!m_useFileBackColor);
		float r, g, b;
		convertOleColorToRGB(GetBackColor(), r, g, b);
		m_ketsjiengine->SetOverrideFrameColor(r, g, b);
	}
}

	
void CBlenderPlayerCtrl::convertColorRefToRGB(COLORREF color, float& r, float& g, float& b)
{
	unsigned char* ptr = (unsigned char*) &color;
	r = ((float)ptr[0]) / ((float)0xFF);
	g = ((float)ptr[1]) / ((float)0xFF);
	b = ((float)ptr[2]) / ((float)0xFF);
}


void CBlenderPlayerCtrl::convertOleColorToRGB(OLE_COLOR color, float& r, float& g, float& b)
{
	COLORREF ref = TranslateColor(color);
	convertColorRefToRGB(ref, r, g, b);
}


void CBlenderPlayerCtrl::processMessages(void)
{
	// Only process messages if the engine is running
	if (m_engineRunning && m_networkdevice) {
		vector<NG_NetworkMessage*> msgs;
		msgs = m_networkdevice->RetrieveNetworkMessages();
		if (msgs.size() > 0) {
			vector<NG_NetworkMessage*>::iterator it;
			/* At the moment, we look for messages for the "host_application"
			 * with subject "load_url" only.
			 */
			for (it = msgs.begin(); it != msgs.end(); it++) {
				NG_NetworkMessage* msg = *it;
				if (msg->GetDestinationName() == s_messageAddress) {
					if (msg->GetSubject() == s_messageLoadURLSubject) {
						loadURL(msg->GetMessageText());
					}
				}
			}
		}
	}
}


void CBlenderPlayerCtrl::loadURL(const STR_String& url)
{
	bool success = false;
	IOleClientSite* pClientSite = GetClientSite();
	if (pClientSite) {
		IOleContainer* pContainer;
		pClientSite->GetContainer(&pContainer);
		if (pContainer) {
			IHTMLDocument2* pHTMLDocument2;
			HRESULT hr;
			hr = pContainer->QueryInterface(IID_IHTMLDocument2, (void**)&pHTMLDocument2);
			if (hr == S_OK) {
				STR_String genURL = url;
				CString strURL = genURL.Ptr();
				pHTMLDocument2->put_URL(strURL.AllocSysString());
				pHTMLDocument2->Release();
			}
			pContainer->Release();
		}
		pClientSite->Release();
	}
}


void CBlenderPlayerCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	// Check whether there was file downloading progress
	checkDownload();

	if (!m_hWnd) {
		/* Can't draw with OpenGL without valid window.
		 * This is really en error situation since OnDrawMetaFile should be called
		 * when the window handle is invalid.
		 */
		return;
	}

	// Save the current time to establish frame drawing time below
	DWORD ticksStart = ::GetTickCount();

	if (m_drawingMetafile) {
		// The control is being activated
		if (m_runState != stateIdle) {
			if (m_hWnd) {
				HDC hDC = this->GetDC()->GetSafeHdc();
				exitEngine();
				startEngine();
			}
		}
		m_drawingMetafile = false;
	}

	if (m_engineRunning) {
		// See if the engine had messages for us
		processMessages();
	}

	float progress;
	switch (m_runState) {
	case stateIdle:
		drawIdle(pdc, rcBounds, rcInvalid);
		break;

	case stateLoadLoading:
		drawLoadLoading(pdc, rcBounds, rcInvalid);
		break;

	case stateLoadBlender:
		if (!m_engineRunning) {
			m_lastProgress = 0;
			if (!startEngine()) {
				InvalidateControl();
			}
		}
		// Downloading data, show download progress
		progress = determineProgress();
		if (progress != m_lastProgress) {
			updateLoadingAnimation(progress);
			m_lastProgress = progress;
		}
		drawLoadBlender(pdc, rcBounds, rcInvalid);
		break;

	case stateRun:
		if (!m_engineRunning) {
			if (!startEngine()) {
				InvalidateControl();
			}
		}
		if (m_exitRequested == KX_EXIT_REQUEST_START_OTHER_GAME)
		{
			// kill the current engine, clean up etc
			exitEngine();
			// reset the exitrequest
			m_exitRequested = KX_EXIT_REQUEST_NO_REQUEST;
			// load/rewind the blendfile
			Rewind();
		}
		else
		{
			drawEngine(pdc, rcBounds, rcInvalid);
		}
		break;

	case stateError:
		if (!m_engineRunning) {
			m_lastProgress = 0;
			if (!startEngine()) {
				InvalidateControl();
			}
		}
		else {
			// Until there is an error animation we show the loading animation
			if (!m_lastProgress) {
				updateLoadingAnimation(100.f);
				m_lastProgress = 100.f;
			}
			drawError(pdc, rcBounds, rcInvalid);
		}
		break;

	case stateLoadAsyncFailed:
		if (!m_engineRunning) {
			if (!startEngine()) {
				InvalidateControl();
			}
			else {
				m_runState = stateRun;
			}
		}
		break;
	}
	/*
	if (m_engineRunning && (m_frameRate > 0)) {
		DWORD ticksEnd = ::GetTickCount();
		DWORD ticks = ticksEnd - ticksStart;
		if (ticks < m_frameRate) {
			UINT nElapse = 1000 / m_frameRate;
			m_refresher.SetInterval(nElapse);
		}
		else {
			m_refresher.SetInterval(ticks);
		}
	}
	*/
}


/*
 * This routine is called when a control is needed to be drawn 
 * to a metafile device context often used when the control is in inactive
 * state.
 * The default implementation (in COleControl) calls OnDraw.
 * We can't use OpenGL when inactive but only routines offered by the device
 * context pDC.
 */
void CBlenderPlayerCtrl::OnDrawMetafile(CDC* pDC, const CRect& rcBounds) 
{
	if (!m_drawingMetafile) {
		// Switched to inactive state
		if (m_engineRunning) {
			stopEngine();
		}
		m_drawingMetafile = true;
	}
	checkDownload();
	drawIdle(pDC, rcBounds, rcBounds);
}


void CBlenderPlayerCtrl::DoPropExchange(CPropExchange* pPX)
{
	ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
	COleControl::DoPropExchange(pPX);

	stopEngine();
	m_runState = stateIdle;
	m_frameRate = 60;
	m_showFrameCount = FALSE;
	m_showProfileInfo = FALSE;
	m_showProperties = FALSE;
	m_useFileBackColor = FALSE;
	m_drawingMetafile = false;
	m_blenderDataValid = false;
	m_loadingDataValid = false;
	m_lastProgress = 0;

	InternalSetReadyState(READYSTATE_INTERACTIVE);
	// 1.0.0.3 parameter name:
	// PX_String(pPX, _T("data"), m_blenderURL);
	PX_String(pPX, _T("blenderURL"), m_blenderURL);
	m_initialBlenderURL = m_blenderURL;
	PX_String(pPX, _T("loadingURL"), m_loadingURL);
	m_initialLoadingURL = m_loadingURL;
	PX_Short(pPX, _T("frameRate"), m_frameRate);
	/**
	 * There is a bug in the PX_Bool implementation.
	 * BOOLs with True value are returned as -1!
	 */
	if (PX_Bool(pPX, _T("showFrameCount"), m_showFrameCount)) {
		m_showFrameCount = m_showFrameCount != 0;
	}
	if (PX_Bool(pPX, _T("showProfileInfo"), m_showProfileInfo)) {
		m_showProfileInfo = m_showProfileInfo != 0;
	}
	if (PX_Bool(pPX, _T("showProperties"), m_showProperties)) {
		m_showProperties = m_showProperties != 0;
	}
	if (PX_Bool(pPX, _T("useFileBackColor"), m_useFileBackColor)) {
		m_useFileBackColor = m_useFileBackColor != 0;
	}

	// TEMP 
	//m_blenderURL = "c:\\loadtest.blend";
	//m_initialBlenderURL = m_blenderURL;

	/*
	if (!m_blenderURL.IsEmpty()) {
		startDownload();
	}
	else {
		m_runState = stateIdle;
	}
	*/
	startDownload();
	InvalidateControl();
}


void CBlenderPlayerCtrl::OnResetState()
{
	COleControl::OnResetState();  // Resets defaults found in DoPropExchange
}


void CBlenderPlayerCtrl::AboutBox()
{
	CDialog dlgAbout(IDD_ABOUTBOX_BLENDERPLAYER);
	dlgAbout.DoModal();
}



void CBlenderPlayerCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (m_keyboard /* && !(nFlags & KF_REPEAT) */) {
		m_keyboard->ConvertWinEvent(nChar, true);
	}
	COleControl::OnKeyDown(nChar, nRepCnt, nFlags);
}


void CBlenderPlayerCtrl::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (m_keyboard /* && !(nFlags & KF_REPEAT) */) {
		m_keyboard->ConvertWinEvent(nChar, false);
	}
	COleControl::OnKeyUp(nChar, nRepCnt, nFlags);
}


void CBlenderPlayerCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	if (m_mouse) {
		m_mouse->ConvertButtonEvent(GPC_MouseDevice::buttonLeft, true, point.x, point.y);
	}
	COleControl::OnLButtonDown(nFlags, point);
}


void CBlenderPlayerCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if (m_mouse) {
		m_mouse->ConvertButtonEvent(GPC_MouseDevice::buttonLeft, false, point.x, point.y);
	}
	COleControl::OnLButtonUp(nFlags, point);
}


void CBlenderPlayerCtrl::OnMButtonDown(UINT nFlags, CPoint point) 
{
	if (m_mouse) {
		m_mouse->ConvertButtonEvent(GPC_MouseDevice::buttonMiddle, true, point.x, point.y);
	}
	COleControl::OnMButtonDown(nFlags, point);
}


void CBlenderPlayerCtrl::OnMButtonUp(UINT nFlags, CPoint point) 
{
	if (m_mouse) {
		m_mouse->ConvertButtonEvent(GPC_MouseDevice::buttonMiddle, false, point.x, point.y);
	}
	COleControl::OnMButtonUp(nFlags, point);
}


void CBlenderPlayerCtrl::OnRButtonDown(UINT nFlags, CPoint point) 
{
	if (m_mouse) {
		m_mouse->ConvertButtonEvent(GPC_MouseDevice::buttonRight, true, point.x, point.y);
	}
	COleControl::OnRButtonDown(nFlags, point);
}


void CBlenderPlayerCtrl::OnRButtonUp(UINT nFlags, CPoint point) 
{
	if (m_mouse) {
		m_mouse->ConvertButtonEvent(GPC_MouseDevice::buttonRight, false, point.x, point.y);
	}
	COleControl::OnRButtonUp(nFlags, point);
}


void CBlenderPlayerCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (m_mouse) {
		m_mouse->ConvertMoveEvent(point.x, point.y);
	}
	COleControl::OnMouseMove(nFlags, point);
}


LRESULT CBlenderPlayerCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
#ifdef DO_YOU_WANT_CRASHES_UNDER_WIN98_AND_WIN_ME
	// If the cursor is inside our window, grab the focus so that we get keyboard events
	POINT point;
	if (::GetCursorPos(&point)) {
		if (WindowFromPoint(point) == this) {
			if (this->GetFocus() != this) {
				this->SetFocus();
			}
		}
	}
#endif //DO_YOU_WANT_CRASHES_UNDER_WIN98_AND_WIN_ME
	return COleControl::WindowProc(message, wParam, lParam);
}


BOOL CBlenderPlayerCtrl::PreTranslateMessage(MSG* pMsg) 
{
    bool handleNow = false;
	bool down = false;

    switch (pMsg->message)
	{
//   case WM_ACTIVATE:
//		Beep(1000, 1000);
//		break;
    case WM_KEYDOWN:
		down = true;
		// Fall through
    case WM_KEYUP:
		/*
		 * Grab these keyboard events before IE handles them.
		 * We could also grab VK_FXX keys but currently we don't.
		 * In IE, these keys have special meanings.
		 */
		switch (pMsg->wParam)
		{
		case VK_UP:
		case VK_DOWN:
		case VK_LEFT:
		case VK_RIGHT:
		case VK_END:
		case VK_HOME:
		case VK_PRIOR:
		case VK_NEXT:
			handleNow = true;
			break;
		}
		if (handleNow) {
			if (down) {
				OnKeyDown(pMsg->wParam, LOWORD(pMsg->lParam), HIWORD(pMsg->lParam));
			}
			else {
				OnKeyUp(pMsg->wParam, LOWORD(pMsg->lParam), HIWORD(pMsg->lParam));
			}
		}
		break;
    }
    return handleNow ? 1 : COleControl::PreTranslateMessage(pMsg);
}


void CBlenderPlayerCtrl::OnLoadingURLChanged() 
{
	m_loadingDataValid = false;
	startDownload();
	SetModifiedFlag();
}

void CBlenderPlayerCtrl::OnBlenderURLChanged() 
{
	m_blenderDataValid = false;
	startDownload();
	SetModifiedFlag();
}


void CBlenderPlayerCtrl::Rewind() 
{
	if (m_runState == stateRun) {
		stopEngine();
		m_runState = stateIdle;
		m_showFrameCount = FALSE;
		m_showProfileInfo = FALSE;
		m_showProperties = FALSE;
		m_drawingMetafile = false;
		m_blenderDataValid = false;
		m_loadingDataValid = false;
		m_lastProgress = 0;
		InternalSetReadyState(READYSTATE_INTERACTIVE);
		startDownload();
	}
}


void CBlenderPlayerCtrl::OnShowFrameCountChanged() 
{
	updateEngineInfoDisplay();
	SetModifiedFlag();
}


void CBlenderPlayerCtrl::OnShowProfileInfoChanged() 
{
	updateEngineInfoDisplay();
	SetModifiedFlag();
}


void CBlenderPlayerCtrl::OnShowPropertiesChanged() 
{
	updateEngineInfoDisplay();
	SetModifiedFlag();
}


BOOL CBlenderPlayerCtrl::SendMessage(LPCTSTR to, LPCTSTR from, LPCTSTR subject, LPCTSTR body) 
{
	STR_String lto = to;
	STR_String lfrom = from;
	STR_String lsubject = subject;
	STR_String lbody = body;
	sendMessage(lto, lfrom, lsubject, lbody);
	return TRUE;
}


DWORD CBlenderPlayerCtrl::GetControlFlags() 
{
	//return clipPaintDC;
	return COleControl::GetControlFlags();
}

void CBlenderPlayerCtrl::OnForeColorChanged() 
{
	COleControl::OnForeColorChanged();
}

void CBlenderPlayerCtrl::OnBackColorChanged() 
{
	updateFrameColor();	
	COleControl::OnBackColorChanged();
}

void CBlenderPlayerCtrl::OnFrameRateChanged() 
{
	if (m_engineRunning && (m_frameRate > 0)) {
		UINT nElapse = 1000 / m_frameRate;
		m_refresher.SetInterval(nElapse);
	}
	SetModifiedFlag();
}

void CBlenderPlayerCtrl::OnUseFileBackColorChanged() 
{
	updateFrameColor();
	SetModifiedFlag();
}
