/**
 * $Id: KXH_ketsji_hooks.cpp,v 1.5 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 *****
 * Implementation of the application hooks between browser and app, 
 * where app = Ketjsi.
 */

/*
 * Application handle refers to ketsji_engine_data!!!
 */

#include <iostream.h>
#include <stdio.h>

/* Moz infra: */
#include "npapi.h"
#include "prthread.h"

/* Plugin related */
#include "NZC_application_hooks.h"
#include "NZC_application_callback.h"
#include "KXH_ketsji_hooks.h"

/* Util */
#include "STR_String.h"

/* Blender system */
#include "GEN_messaging.h"
#include "SYS_System.h"

/* Gameplayer interfacing */
#include "SCA_IInputDevice.h"
//#include "SCA_ISystem.h"
#include "GPC_System.h"
#include "GPC_Canvas.h"
#include "GPC_KeyboardDevice.h"
#include "GPC_MouseDevice.h"
#include "GPC_RenderTools.h"
#include "GPC_RawImage.h"
#include "GPC_RawLoadDotBlendArray.h"
#include "GPC_RawLogoArrays.h"
#include "SND_DeviceManager.h"
#include "RAS_OpenGLRasterizer.h"
#include "KX_KetsjiEngine.h"
#include "KX_BlenderSceneConverter.h"
#include "NG_LoopBackNetworkDeviceInterface.h"
#include "KX_PythonInit.h"
#include "KX_PyConstraintBinding.h"

#include "KXH_engine_data_wraps.h"

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

#if defined(_WIN32)

#include "KXH_MSWindows_services.h"

#elif defined(__APPLE__)

#else
#include <unistd.h>
#include <vector.h>
#include "KXH_unix_event_sinks.h"
#include "KXH_unix_services.h"
#endif

/* --------------------------------------------------------------------- */
/* Some handy defines...                                                 */
/* --------------------------------------------------------------------- */
/* if defined: log function entry */
#define KXH_GENERATE_LOG

/* --------------------------------------------------------------------- */

/** Cast the application handle to local data. */
ketsji_engine_data*
handle_to_data(
	APH_application_handle handle
	);

/** main() for the plugin thread */
void
plugin_mainloop(
	void* arg
	);

/* Let the gameengine do one step. */
void 
tick(
	ketsji_engine_data* k
	);

/** Make a gameengine and give it devices. */
void
initialize_gameengine(
	ketsji_engine_data* k,
	bool for_main_file
	);

/** Check for network messages posted by the current blend file */
void
check_for_messages(
	ketsji_engine_data* k
	);

/** Request transition to completely other url.  */
void
request_replacing_url(
	ketsji_engine_data* k,
	const STR_String& url
	);

/* Use the default .blend loader as loading animation. */
void 
open_default_loader(
	ketsji_engine_data* k
	);

/* Message the loading animation to up the progress. */
void 
update_loading_progress(
	ketsji_engine_data* k
	);

/** Stop, and drop resources from the current running engine. */
void 
release_resources(
	ketsji_engine_data* k
	);

/** Set the canvas to a plain fore and background colour. */
void
set_plain_colour(
	ketsji_engine_data* k
	);

/** Check whether there is a valid loading anim. */
bool 
loading_anim_file_valid(
	ketsji_engine_data* k
	);

/** Test the type/status of the main file. */
KXH_MAIN_FILE_STATE
main_file_state(
	ketsji_engine_data* k
	);

/** Little looper to enable all kinds of logging. */
static void 
to_state(
	ketsji_engine_data* k, 
	KXH_STATE i
	);

/** Redraw the canvas with a plain foreground colour. */
void 
set_plain_colour_redraw_func(
	ketsji_engine_data* k
	);

static void
KXH_log_entry(
	char* msg
	);


/* --------------------------------------------------------------------- */
/* Drawing functions                                                     */
/* --------------------------------------------------------------------- */

/** Acquire drawing context: blocking */
bool
KXH_begin_draw(KXH_plugin_handle h)
{
	ketsji_engine_data* k = (ketsji_engine_data*) h;
//	KXH_log_entry("KXH_begin_draw");
	if((!k) || (!k->kx_initialized)) return false;
	
	return (PLA_acquire_gl_context(k->plugin) ? true : false);
}

/** Swap buffers in the drawing context. This does not release the
 * context! */
void
KXH_swap_buffers(KXH_plugin_handle h)
{
	ketsji_engine_data* k = (ketsji_engine_data*) h;
//	KXH_log_entry("KXH_swap_buffers");
	if((!k) || (!k->kx_initialized)) return;
	PLA_swap_gl_context(k->plugin);	
}

/** Release drawing context. */
void
KXH_end_draw(KXH_plugin_handle h)
{
	ketsji_engine_data* k = (ketsji_engine_data*) h;
//	KXH_log_entry("KXH_end_draw");
	if((!k) || (!k->kx_initialized)) return;
	PLA_release_gl_context(k->plugin);
}

/* --------------------------------------------------------------------- */
/* Engine control functions                                              */
/* --------------------------------------------------------------------- */

void
APH_redraw(
	APH_application_handle handle
	)
{
	ketsji_engine_data* k = (ketsji_engine_data*) handle;
// 	KXH_log_entry("APH_redraw");
	if(!k) return;
	// make one step progress
	tick(k);
}

void
APH_reset_viewport(
	APH_application_handle handle, 
	int width, 
	int height
	)
{
	ketsji_engine_data* ked = (ketsji_engine_data*) handle;
// 	KXH_log_entry("APH_reset_viewport");
	if((!ked) || (!ked->kx_initialized)) return;

	/* Only sets some width/height (GPC_Canvas), so no lock
	 * needed, I think. */
	ked->canvas_device->Resize(width, height);
}

void
APH_main_file_failed(APH_application_handle h)
{
	ketsji_engine_data* ked = (ketsji_engine_data*) h;
	KXH_log_entry("APH_main_file_failed");
	ked->blendfile_failed = true;
}

void
APH_loading_anim_failed(APH_application_handle h)
{
	ketsji_engine_data* ked = (ketsji_engine_data*) h;
	KXH_log_entry("APH_loading_anim_failed");
	ked->loading_anim_failed = true;
}

void
APH_main_file_loaded(APH_application_handle h,
		     unsigned char * buffer,
		     int size)
{
	ketsji_engine_data* k = (ketsji_engine_data*) h;
	KXH_log_entry("APH_main_file_loaded");
	k->blendfile = BLO_read_from_memory(buffer, 
					    size, 
					    &(k->error));
	if ((!k->blendfile) 
	    || (k->error != BRE_NONE) ) {
		k->blendfile_failed = true;
	}
}

void
APH_loading_anim_loaded(APH_application_handle h,
			unsigned char * buffer,
			int size)
{
	ketsji_engine_data* k = (ketsji_engine_data*) h;
	KXH_log_entry("APH_loading_anim_loaded");
	k->loading_anim = BLO_read_from_memory(buffer, 
					       size, 
					       &(k->error));
	if ((!k->blendfile) 
	    || (k->error != BRE_NONE) ) {
		k->loading_anim_failed = true;
	}
}


/* --------------------------------------------------------------------- */
/* Implemetation of javascript hooks:                                    */
/* --------------------------------------------------------------------- */
/** send a message to the current gameworld*/
void
APH_send_message(
	APH_application_handle h, 
	char* to, 
	char* from, 
	char* subject, 
	char* body
	)
{
  
	ketsji_engine_data* k = handle_to_data(h);
	STR_String s_to, s_from, s_subject, s_body;

	KXH_log_entry("APH_send_message");

	s_to.Format("%s", to);
	s_from.Format("%s", from);
	s_subject.Format("%s", subject);
	s_body.Format("%f", body);

	if(k->net_dev) {
		// Store a progress message in the network device.



		NG_NetworkMessage* msg = new NG_NetworkMessage(s_to, s_from, s_subject, s_body);
		k->net_dev->SendNetworkMessage(msg);
		msg->Release();
	}

}


/** Prepare to restart with a new url. Streaming is started elsewhere. */
void
APH_restart_with_url(APH_application_handle h)
{
	ketsji_engine_data* k = handle_to_data(h);
	
	KXH_log_entry("APH_restart_with_url");

	std::cerr << " request restart for url " <<  endl;

	k->new_url_requested = true;
	to_state(k, KXH_AWAITING_NEW_STREAM);
	
}

/* --------------------------------------------------------------------- */
/* The progress function:                                                */
/* --------------------------------------------------------------------- */
void
KXH_proceed(ketsji_engine_data* k)
{
//   	KXH_log_entry("KXH_proceed");
  
	// Proceed with a game step. The stand-alone player switches on
	// the drawing context here, but doesn't deactivate it
	// afterwards... Isn't this what the canvas is for? Why are extra
	// context switches needed?
	if (k && k->kx_initialized && k->kx_engine) { 
// 		KXH_log_entry("KXH_proceed:: proceeding");
		k->kx_engine->NextFrame();
		k->kx_engine->Render();
	}

}



/* --------------------------------------------------------------------- */
/* Initialization and other crud                                         */
/* --------------------------------------------------------------------- */
// Note that the engine itself is initialised inside the thread! This
// is necessary to have a valid glx context when initialisation takes
// place. Without that, the rasterizer init runs into problems.
APH_application_handle
APH_create_application(PLA_plugin_handle p_handle)
{
	thread_data_wrap *t;

	KXH_log_entry("APH_create_application");

	t = (thread_data_wrap*) malloc(sizeof(thread_data_wrap));
	t->ked = NULL;
	t->ph = p_handle;

	t->t = PR_CreateThread(PR_USER_THREAD,
			       plugin_mainloop,
			       t,
			       PR_PRIORITY_NORMAL,
			       PR_LOCAL_THREAD,
			       PR_JOINABLE_THREAD, 
			       0);
	if (!t->t) { 
		std::cerr << "Blender3DPlugin: Failed to create "
			"thread for game, bailing." << endl; 
		exit(1); 
	} 

	/* make cond var from this: */
	while (!t->ked) {
#ifdef _WIN32
	  Sleep(20000);
#else
	  usleep(20000);
#endif
	}

	t->ked->thread_data = t;
	
	return (APH_application_handle) t->ked;
	
}


ketsji_engine_data*
init_thread(PLA_plugin_handle p_handle)
{
	ketsji_engine_data* ked = NULL;

	KXH_log_entry("init_thread");
	
	ked = (ketsji_engine_data*) malloc(sizeof(ketsji_engine_data));
	if(!ked) return NULL;	
	ked->plugin = p_handle;
	ked->kx_initialized = false;
	ked->terminate = false;
	ked->termination_request = false;
	ked->replacing_url_requested = false;
	ked->new_url_requested = false;

	ked->blendfile = NULL;
	ked->loading_anim = NULL;
	ked->blendfile_failed = false;
	ked->loading_anim_failed = false;

	ked->state = KXH_INIT;
	ked->termination_request = false;

	ked->mousedevice = NULL;
	ked->keyboarddevice = NULL;
	ked->net_dev = NULL;
	ked->canvas_device = NULL;
	ked->rasterizer = NULL;
	ked->rendertools = NULL;
	ked->audiodevice = NULL;
	ked->converter = NULL;
	
	return ked;

}

void
APH_terminate_application(APH_application_handle handle)
{
	ketsji_engine_data* k = (ketsji_engine_data*) handle;
	PRStatus st;
	
	KXH_log_entry("APH_terminate_application");

	// The request is looped through the cleanup... then the
	// terminate is set, and the thread can end. Maybe add a
	// timeout here?
	k->termination_request = true;

#ifndef _WIN32
	KXH_log_entry("APH_terminate_application:: wait for join");
	st = PR_JoinThread(k->thread_data->t);
	KXH_log_entry("APH_terminate_application:: join done");
	if (st != PR_SUCCESS) {
		std::cerr << "Blender3DPlugin: Critical failure "
			"joining gameengine thread, bailing." << endl;
		exit(1);
	}
	free(k);
#endif

}

void 
open_default_loader(ketsji_engine_data* k)
{
	unsigned char * data;
	int size;

	KXH_log_entry("open_default_loader");

	GetRawLoadingAnimation(&data, &size);
	k->loading_anim = BLO_read_from_memory(data, size, &(k->error));

}

void
initialize_gameengine(ketsji_engine_data* k, struct BlendFileData * active_file)
{
	KXH_log_entry("initialize_gameengine");

	// kx does some hidden gl things:
	PLA_acquire_gl_context(k->plugin);

	// underlying blender systems
	GEN_init_messaging_system();
	initglobals();	/* blender.c */

	// This is the platform dependence: 
	KXH_create_devices(k);

	// create the engine
	k->kx_engine = new KX_KetsjiEngine(k->logic_system);

	if (k->kx_engine)
	{	
		k->converter = new
			KX_BlenderSceneConverter(active_file->main,
						 k->kx_engine);
		
		PyObject* dictionaryobject 
			= initGamePlayerPythonScripting("Ketsji", 
						  psl_Highest);
		if (k->keyboarddevice 
		    && k->mousedevice 
		    && k->net_dev 
		    && k->canvas_device 
		    && k->rasterizer 
		    && k->rendertools 
		    && k->audiodevice 
		    && k->converter 
		    && dictionaryobject) {
			// only continue when everything is ok. 
			KXH_log_entry("initialize_gameengine:: devices ok, connecting...");
			
			k->kx_engine->SetKeyboardDevice(k->keyboarddevice);
			k->kx_engine->SetMouseDevice(k->mousedevice);		
			k->kx_engine->SetNetworkDevice(k->net_dev);
			k->kx_engine->SetCanvas(k->canvas_device);
			k->kx_engine->SetRasterizer(k->rasterizer);
			k->kx_engine->SetRenderTools(k->rendertools);
 			k->kx_engine->SetAudioDevice(k->audiodevice);
			k->kx_engine->SetSceneConverter(k->converter);
			k->kx_engine->SetPythonDictionary(dictionaryobject);
		} else {
			// do some error trap;
			std::cerr << "Blender3DPlugin:: could not initialize "
				"gameengine, bailing..." << endl;
			exit(1);
		}

		// do the startscenestuff
		Scene *scene = active_file->curscene;
		char *startsc = scene->id.name + 2;
		STR_String* startSceneName = new STR_String(startsc);
		
		KX_Scene* startscene 
			= new KX_Scene((SCA_IInputDevice*)k->keyboarddevice,
				       (SCA_IInputDevice*)k->mousedevice, k->net_dev,
				       k->audiodevice,
				       startSceneName->Ptr());
		
		initRasterizer(k->rasterizer, k->canvas_device);
		initGameLogic(startscene);
		initGameKeys();
		initPythonConstraintBinding();
		
		KXH_log_entry("APH_initialize_gameengine:: will enter kx engine");
		
		k->converter->ConvertScene(
			startSceneName->Ptr(), 
			startscene,
			dictionaryobject,
			k->keyboarddevice,
			k->rendertools,
			k->canvas_device);

		k->kx_engine->AddScene(startscene);
		
		// Inside, the rasterizer is initialised! This
		// requires gl calls...
		k->rasterizer->Init();
		k->kx_engine->StartEngine();

		// that's it:
		k->kx_initialized = true;
		KXH_log_entry("APH_initialize_gameengine:: mainloop locked");
	}
	
	PLA_release_gl_context(k->plugin);
}

void 
tick(ketsji_engine_data* k)
{
 	KXH_log_entry("tick");

	if (!k) {
		KXH_log_entry("Engine data not defined!");
		return;
	}

	switch (k->state) {
	case KXH_INIT:
		KXH_log_entry("tick: init");
		set_plain_colour(k);
			
		if (PLA_get_loading_animation_url(k->plugin)) {
			// A loading animation url was given:
			// use it. Loading was already
			// initiated elsewhere.
			KXH_log_entry("tick: show fg");
			to_state(k, KXH_SHOW_FG_COLOUR);
		} else {
			KXH_log_entry("tick: default loader ");
			open_default_loader(k);
			initialize_gameengine(k, k->loading_anim);
			to_state(k, KXH_PLAY_LOADING_ANIM);
		}
		break;
	case KXH_SHOW_FG_COLOUR:
		/* load the animation. */
		set_plain_colour(k);			

		// next state: 
		if (k->loading_anim) {
			to_state(k, KXH_TEST_LOADING_FILE_TYPE);
		} else if (k->loading_anim_failed) {
			to_state(k, KXH_GL_ERROR_STATE);
		}
		break;
	case KXH_TEST_LOADING_FILE_TYPE:
		// valid means: must be a publisher file
		if (loading_anim_file_valid(k)){
			initialize_gameengine(k, k->loading_anim);
			to_state(k, KXH_PLAY_LOADING_ANIM);
		} else {
			to_state(k, KXH_GL_ERROR_STATE);
		}
		break;
	case KXH_PLAY_LOADING_ANIM:
		// send a msg to the loading anim :
		update_loading_progress(k);
		KXH_proceed(k);
		// next state:
		if (k->blendfile) {
			// clean up the running engine
			release_resources(k);
			to_state(k, KXH_TEST_MAIN_FILE);
		} else if (k->blendfile_failed) {
			release_resources(k);
			to_state(k, KXH_GL_ERROR_STATE);
		}
		break;
	case KXH_TEST_MAIN_FILE:
		switch (main_file_state(k)) {
		case KXH_MAIN_FILE_IS_PUBLISHER_FILE:
			initialize_gameengine(k, k->blendfile);
			to_state(k, KXH_PLAY_MAIN_FILE);
			break;
		case KXH_MAIN_FILE_IS_NOT_PUBLISHER_FILE:
			initialize_gameengine(k, k->blendfile);
			to_state(k, KXH_ADD_LOGOS);
			break;
		case KXH_MAIN_FILE_CORRUPT:
			to_state(k, KXH_GL_ERROR_STATE);
			break;
		default:
			to_state(k, KXH_GL_ERROR_STATE);
		}
		break;
	case KXH_ADD_LOGOS:
		KXH_add_banners(k);
		to_state(k, KXH_PLAY_MAIN_FILE);
		break;
	case KXH_PLAY_MAIN_FILE:
		// The gameengine's event table bookkeeping
		// interferes with the event chain. Mutex it.
			
		PLA_lock_events(k->plugin);
		KXH_proceed(k);
		PLA_unlock_events(k->plugin);
		
		if (k->replacing_url_requested) {
			to_state(k, KXH_AWAITING_NEW_STREAM);
		}

		break;
	case KXH_AWAITING_NEW_STREAM:
		/* exit, and request transition from javascript */
		/* the exit itself should be effectuated by the
		 * browser? or do we exit ourselves? */
		std::cerr << "Early cleanup awaiting new url" << endl;
		
		to_state(k, KXH_CLEAN_UP);
		

		break;
	case KXH_GL_ERROR_STATE:
		// state transition here is forced from the
		// outside, and caught by the general
		// termination criterium
			
		// for now, just colour cycle a bit
		set_plain_colour(k);
		break;
	case KXH_CLEAN_UP:
		// this is the actual TERM signal:
		release_resources(k);
			
		if (k->blendfile) {
			BLO_blendfiledata_free(k->blendfile);
			k->blendfile = NULL;
		}

		if (k->loading_anim) {
			BLO_blendfiledata_free(k->loading_anim);
			k->loading_anim = NULL;
		}
		// we may want to add termination of file streaming as well
		k->terminate = true;
		break;
	default: 
		std::cerr << "Blender Plugin entered bad state!!! Exiting before we dump core... " << endl;
		exit(1);
	}

	KXH_log_entry("tick done");

}


void
plugin_mainloop(void* arg)
{
	thread_data_wrap* tdw = (thread_data_wrap*) arg;

	KXH_log_entry("plugin_mainloop");

	// This should let the init_ continue, so we can go on and
	// await the glx context.
	tdw->ked = init_thread(tdw->ph);

	// Wait for window:: 
	PLA_await_window(tdw->ph);

	if (PLA_create_gl_context(tdw->ph)) {
		
		/* This needs to become more sophisticated. */
		tdw->ked->sleep_time = 
			(int) (1000000.0 
			       / (float)PLA_requested_framerate(tdw->ph));
		
		while (!tdw->ked->terminate) {
			
			// we keep it routed through the browser api
			// connection layer, so it is still possible
			// to do async update requests. Threading
			// should be changed a bit for that maybe :/
			// This is looped through to the state engine
			// stepping
			PLA_request_application_progress(tdw->ked->plugin);
			
			// we poll the networkdevice for messages posted by
			// the blend file:
			check_for_messages(tdw->ked);
			
			// adapt for framerate... should be
			// improved...
#ifdef _WIN32 
			Sleep(30 * tdw->ked->sleep_time);
#else
			usleep(tdw->ked->sleep_time);
#endif
		
			// always loop through the cleanup. This also
			// overrides any pending state changes. The cleanup
			// state sets the final termination criterium.
			if (tdw->ked->termination_request) {
				KXH_log_entry(" --- term request seen --- ");
				to_state(tdw->ked, KXH_CLEAN_UP);
			}
		}

		PLA_delete_gl_context(tdw->ph);
		
	}
	KXH_log_entry("Leaving plugin thread");

#ifdef _WIN32
	free(tdw->ked);
#endif

	// After this return, the thread will terminate: 
	return;

}

// ---------------------------------------------------------------------
// Helper functions:
// ---------------------------------------------------------------------

ketsji_engine_data*
handle_to_data(APH_application_handle handle)
{
	return (ketsji_engine_data*) handle;
}


void
check_for_messages(ketsji_engine_data* k)
{
  
	if (k->net_dev) {
		vector<NG_NetworkMessage*> msgs;
		msgs = k->net_dev->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;
	
// 				std::cerr << "Message received:" << msg->GetDestinationName()
// 					  << ", " << msg->GetSubject() << endl;

				if (msg->GetDestinationName() == STR_String("host_application")) {
					if (msg->GetSubject() == STR_String("load_url")) {
						request_replacing_url(k, msg->GetMessageText());
					}
				}
			}
		}
	}
}


void
request_replacing_url(ketsji_engine_data* k,
		      const STR_String& url)
{
	int retval;
	char c_url[4096];

	if (url.Length() > 4000) {
		return;
	}

	// this should become an strncpy. And there should be some
	// error handling
	strcpy(c_url, url);

	retval = PLA_request_replacing_url(k->plugin,
					   c_url);

	if (retval) {
		fprintf(stderr, "geturl failed with %d\n", retval);
	}

}

void
set_plain_colour(ketsji_engine_data* k) 
{
//	KXH_log_entry("set_plain_colour");
	set_plain_colour_redraw_func(k);
}

void 
set_plain_colour_redraw_func(ketsji_engine_data* k)
{
	
  //	KXH_log_entry("set_plain_colour_redraw_func");
	PLA_acquire_gl_context(k->plugin);

	float* c = PLA_get_foreground_colour(k->plugin);

#ifndef _WIN32
 	glClearColor(c[0], c[1], c[2], 1.0);
 	glClear(GL_COLOR_BUFFER_BIT);
#endif

	PLA_swap_gl_context(k->plugin);
	PLA_release_gl_context(k->plugin);

}

bool 
loading_anim_file_valid(ketsji_engine_data* k)
{
	KXH_log_entry("loading_anim_file_valid");

	// no file: always bad
	if (!k->loading_anim) return false;

	// not a publisher file: also bad
	if (k->loading_anim->type != BLENFILETYPE_PUB) return false;

	// valid publisher file: good
	return true;
}

KXH_MAIN_FILE_STATE
main_file_state(ketsji_engine_data* k)
{
	KXH_log_entry("main_file_state");
	if (!k->blendfile) return KXH_MAIN_FILE_CORRUPT;

	if (k->blendfile->type == BLENFILETYPE_PUB) { 
		return KXH_MAIN_FILE_IS_PUBLISHER_FILE;
	} else { 
		return KXH_MAIN_FILE_IS_NOT_PUBLISHER_FILE;
	}
}

void 
update_loading_progress(ketsji_engine_data* k)
{
	float progress = 
		(float) PLA_stream_retrieved(k->plugin) 
		/ (float) PLA_stream_total(k->plugin);

//	KXH_log_entry("update_loading_progress");

	// Communicate with a net-message: 
	STR_String to = "";
	STR_String from = "";
	STR_String subject = "progress";
	STR_String body;
	body.Format("%f", progress);  // a number between 0.0 and 1.0

	if(k->net_dev) {
		// Store a progress message in the network device.
		NG_NetworkMessage* msg = new NG_NetworkMessage(to, from, subject, body);
		k->net_dev->SendNetworkMessage(msg);
		msg->Release();
	}
}

void 
release_resources(ketsji_engine_data* k)
{
	KXH_log_entry("release_resources");

	if (k) {
		if (k->net_dev) {
			k->net_dev->Disconnect();
			delete k->net_dev;
			k->net_dev = NULL;
		}
		if (k->kx_engine) {
			// needs to be guarded if there are other KX-s
			// running!
			exitGamePythonScripting();
			k->kx_engine->StopEngine();
			delete k->kx_engine;
			k->kx_engine = NULL;
		}
		if (k->logic_system) {
			delete k->logic_system;
			k->logic_system = NULL;
		}
		if (k->converter) {
			delete k->converter;
			k->converter = NULL;
		}
		if (k->rasterizer) {
			delete k->rasterizer;
			k->rasterizer = NULL;
		}
		if (k->rendertools) {
			delete k->rendertools;
			k->rendertools = NULL;
		}
		if (k->canvas_device) {
			delete k->canvas_device;
			k->canvas_device = NULL;
		}
		if (k->mousedevice) {
			delete k->mousedevice;
			k->mousedevice = NULL;
		}
		if (k->keyboarddevice) {
			delete k->keyboarddevice;
			k->keyboarddevice = NULL;
		}
 		SND_DeviceManager::Unsubscribe();

		k->kx_initialized = false;
	}
}



// ---------------------------------------------------------------------

void 
static to_state(ketsji_engine_data* k, KXH_STATE i)
{
#ifdef KXH_GENERATE_LOG
	FILE* fp = fopen("/tmp/plugin_log","a");
	if (!fp) return;
	fprintf(fp, "--> KXH_ketsji_hooks:: moving from state %d to state %d \n", k->state, i); 
	fflush(fp);
	fclose (fp);
#endif
	k->state = i;
}

static void
KXH_log_entry(char* msg)
{
#ifdef KXH_GENERATE_LOG
	FILE* fp = fopen("/tmp/plugin_log","a");
	if (!fp) return;
  	fprintf(fp, "--> KXH_ketsji_hooks::(%p) %s\n", 
		PR_GetCurrentThread(),
		msg); 
	fflush(fp);
	fclose (fp);
#endif
}


