/*******************************************************************************
 * renderfrontend.cpp
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/source/frontend/renderfrontend.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

// configbase.h must always be the first POV file included within base *.cpp files
#include "base/configbase.h"
#include "base/types.h"
#include "base/povms.h"
#include "base/povmscpp.h"
#include "base/povmsgid.h"
#include "base/processoptions.h"
#include "base/platformbase.h"
#include "base/fileinputoutput.h"

#include "frontend/configfrontend.h"
#include "frontend/renderfrontend.h"

#include <boost/scoped_ptr.hpp>

// this must be the last file included
#include "base/povdebug.h"

namespace pov_frontend
{

const int gStreamTypeUtilDataCount = 6;

const POVMSType gStreamTypeUtilData[gStreamTypeUtilDataCount] =
{
	kPOVAttrib_DebugFile,
	kPOVAttrib_FatalFile,
	kPOVAttrib_RenderFile,
	kPOVAttrib_StatisticsFile,
	kPOVAttrib_WarningFile,
	kPOVAttrib_AllFile
};

const char *gStreamDefaultFile[gStreamTypeUtilDataCount] =
{
	"debug.out",
	"fatal.out",
	"render.out",
	"stats.out",
	"warning.out",
	"alltext.out"
};

const int gStreamNumber[gStreamTypeUtilDataCount] =
{
	DEBUG_STREAM,
	FATAL_STREAM,
	RENDER_STREAM,
	STATISTIC_STREAM,
	WARNING_STREAM,
	ALL_STREAM
};

namespace Message2Console
{

void InitInfo(POVMS_Object& cppmsg, TextStreamBuffer *tsb);

void FileMessage(TextStreamBuffer *tsb, int stream, POVMSObjectPtr msg);
const char *GetOptionSwitchString(POVMSObjectPtr msg, POVMSType key, bool defaultstate);

}

class FileTextStreamBuffer : public TextStreamBuffer
{
	public:
		FileTextStreamBuffer(const UCS2 *filename, bool append) : stream(filename, POV_File_Text_Stream, append) { }
		virtual ~FileTextStreamBuffer() { flush(); stream.flush(); }
	protected:
		virtual void lineoutput(const char *str, unsigned int chars) { stream.printf("%s\n", string(str, chars).c_str()); stream.flush(); }
	private:
		OTextStream stream;
};

RenderFrontendBase::RenderFrontendBase(POVMSContext ctx) :
	POVMS_MessageReceiver(ctx),
	context(ctx)
{
	InstallFront(kPOVMsgClass_BackendControl, kPOVMsgIdent_Failed, this, &RenderFrontendBase::HandleMessage);

	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_ParserStatistics, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_Warning, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_Error, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_FatalError, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_Debug, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_Progress, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_Done, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_SceneOutput, kPOVMsgIdent_Failed, this, &RenderFrontendBase::HandleMessage);

	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_RenderStatistics, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_Warning, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_Error, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_FatalError, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_Debug, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_Progress, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_Done, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewOutput, kPOVMsgIdent_Failed, this, &RenderFrontendBase::HandleMessage);

	InstallFront(kPOVMsgClass_ViewImage, kPOVMsgIdent_PixelSet, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewImage, kPOVMsgIdent_PixelBlockSet, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewImage, kPOVMsgIdent_PixelRowSet, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewImage, kPOVMsgIdent_RectangleFrameSet, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_ViewImage, kPOVMsgIdent_FilledRectangleSet, this, &RenderFrontendBase::HandleMessage);

	InstallFront(kPOVMsgClass_FileAccess, kPOVMsgIdent_FindFile, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_FileAccess, kPOVMsgIdent_ReadFile, this, &RenderFrontendBase::HandleMessage);
	InstallFront(kPOVMsgClass_FileAccess, kPOVMsgIdent_CreatedFile, this, &RenderFrontendBase::HandleMessage);
}

RenderFrontendBase::~RenderFrontendBase()
{
	// nothing to do
}

void RenderFrontendBase::ConnectToBackend(POVMSAddress backendaddress, POVMS_Object& obj, POVMS_Object *resultobj, shared_ptr<Console>& console)
{
	POVMS_Message msg(obj, kPOVMsgClass_BackendControl, kPOVMsgIdent_InitInfo);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(backendaddress);

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
		throw POV_EXCEPTION_CODE(result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr));

	backendaddresses.insert(backendaddress);

	if(resultobj != NULL)
		*resultobj = result;

	if(console)
		Message2Console::InitInfo(result, console.get());
}

void RenderFrontendBase::DisconnectFromBackend(POVMSAddress backendaddress)
{
	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_BackendControl, kPOVMsgIdent_Done);
	POVMS_Message result(kPOVObjectClass_ResultData);

	// Do not check here, the backend will check if the request to disconnect is valid! [trf]
	backendaddresses.erase(backendaddress);

	msg.SetDestinationAddress(backendaddress);

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
		throw POV_EXCEPTION_CODE(result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr));
}

RenderFrontendBase::SceneId RenderFrontendBase::CreateScene(SceneData& shd, POVMSAddress backendaddress, POVMS_Object& obj)
{
	if(backendaddresses.find(backendaddress) == backendaddresses.end())
		throw POV_EXCEPTION_STRING("TODO"); // TODO FIXME

	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_BackendControl, kPOVMsgIdent_CreateScene);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(backendaddress);

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() == kPOVMsgIdent_Done)
	{
		shd.scenepath = Path(obj.TryGetUCS2String(kPOVAttrib_InputFile, ""));
		shd.scenepath.SetFile("");

		shd.outputpath = Path(obj.TryGetUCS2String(kPOVAttrib_OutputPath, ""));
		// TODO FIXME BEGIN - this should not be needed, determine reason and fix [trf]
		if (shd.outputpath.Empty() == true)
		{
			shd.outputpath = Path(obj.TryGetUCS2String(kPOVAttrib_OutputFile, ""));
			shd.outputpath.SetFile("");
			if (shd.outputpath.Empty() == false)
				obj.SetUCS2String(kPOVAttrib_OutputPath, shd.outputpath().c_str());
		}
		// TODO FIXME END

		shd.verbose = obj.TryGetBool(kPOVAttrib_Verbose, true);

		for(size_t i = 0; i < MAX_STREAMS; i++)
		{
			shd.consoleoutput[i] = true;
			shd.streams[i].reset();
		}

		shd.consoleoutput[ALL_STREAM] = false;

		if(obj.Exist(kPOVAttrib_AllConsole))
		{
			bool b = obj.GetBool(kPOVAttrib_AllConsole);
			// NEVERE disable copyright banner BANNER_STREAM stream! [trf]
			shd.consoleoutput[DEBUG_STREAM] = b;
			shd.consoleoutput[FATAL_STREAM] = b;
			shd.consoleoutput[RENDER_STREAM] = b;
			shd.consoleoutput[STATISTIC_STREAM] = b;
			shd.consoleoutput[WARNING_STREAM] = b;
		}
		else
		{
			if(obj.Exist(kPOVAttrib_DebugConsole))
				shd.consoleoutput[DEBUG_STREAM] = obj.GetBool(kPOVAttrib_DebugConsole);
			if(obj.Exist(kPOVAttrib_FatalConsole))
				shd.consoleoutput[FATAL_STREAM] = obj.GetBool(kPOVAttrib_FatalConsole);
			if(obj.Exist(kPOVAttrib_RenderConsole))
				shd.consoleoutput[RENDER_STREAM] = obj.GetBool(kPOVAttrib_RenderConsole);
			if(obj.Exist(kPOVAttrib_StatisticsConsole))
				shd.consoleoutput[STATISTIC_STREAM] = obj.GetBool(kPOVAttrib_StatisticsConsole);
			if(obj.Exist(kPOVAttrib_WarningConsole))
				shd.consoleoutput[WARNING_STREAM] = obj.GetBool(kPOVAttrib_WarningConsole);
		}

		for(size_t i = 0; i < gStreamTypeUtilDataCount; i++)
		{
			if(obj.Exist(gStreamTypeUtilData[i]) == true)
			{
				shd.streamnames[gStreamNumber[i]] = obj.GetUCS2String(gStreamTypeUtilData[i]);
				if(ProcessOptions::IsTrue(UCS2toASCIIString(shd.streamnames[gStreamNumber[i]]).c_str()) == true)
					shd.streamnames[gStreamNumber[i]] = ASCIItoUCS2String(gStreamDefaultFile[i]);
			}
		}

		// append to the stream if this is a continued trace or animation.
		bool append = obj.TryGetBool(kPOVAttrib_ContinueTrace, false) || obj.TryGetBool(kPOVAttrib_AppendConsoleFiles, false);

		for(size_t i = 0; i < MAX_STREAMS; i++)
		{
			if(shd.streamnames[i].empty() == false)
			{
				Path tmp(shd.streamnames[i]);
#if 1
				// allow only pure file names
				if (tmp.HasVolume() || !tmp.GetFolder().empty())
					throw POV_EXCEPTION(kParamErr, "Stream output files must reside in same directory as image output file.");
				Path path(shd.outputpath, tmp);
#elif 0
				// allow arbitrary relative file names, but no absolute file names
				if (tmp.HasVolume())
					throw POV_EXCEPTION(kParamErr, "Stream output file names must be specified relative to the output file directory.");
				Path path(shd.outputpath, tmp);
#else
				// allow arbitrary file names
				Path path;
				if (tmp.HasVolume())
					// file name is absolute
					path = tmp;
				else
					// file name is relative
					path = Path(shd.outputpath, tmp);
#endif
				shd.streams[i].reset(new FileTextStreamBuffer(path().c_str(), append));
				if (append && i != DEBUG_STREAM)
				{
					shd.streams[i]->puts("\n"
					                     "==============================================================================\n"
					                     "=                       Appending to stream output file                      =\n"
					                     "==============================================================================\n");
				}
			}
		}

		if(obj.Exist(kPOVAttrib_LibraryPath) == true)
		{
			POVMS_List lps;

			obj.Get(kPOVAttrib_LibraryPath, lps);
			for(int i = 1; i <= lps.GetListSize(); i++)
			{
				POVMS_Attribute lp;

				lps.GetNth(i, lp);
				UCS2String str = lp.GetUCS2String();
				if (str.empty() == true)
					continue;
#ifndef POV_FILE_SEPARATOR_2
				if (*str.rbegin() != POV_FILE_SEPARATOR)
					str += POV_FILE_SEPARATOR;
#else
				if (*str.rbegin() != POV_FILE_SEPARATOR && *str.rbegin() != POV_FILE_SEPARATOR_2)
					str += POV_FILE_SEPARATOR;
#endif
				shd.searchpaths.push_back(Path(str));
			}
		}

		return SceneId(backendaddress, result.GetInt(kPOVAttrib_SceneId));
	}
	else
		throw POV_EXCEPTION_CODE(result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr));
}

void RenderFrontendBase::CloseScene(SceneData& shd, SceneId sid)
{
	if(sid == Id())
		return;

	try
	{
		POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_BackendControl, kPOVMsgIdent_CloseScene);
		POVMS_Message result(kPOVObjectClass_ResultData);

		msg.SetDestinationAddress(sid.GetAddress());
		msg.SetInt(kPOVAttrib_SceneId, sid.GetIdentifier());

		POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

		if(result.GetIdentifier() != kPOVMsgIdent_Done)
			throw POV_EXCEPTION_CODE(result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr));
	}
	catch(pov_base::Exception&)
	{
		shd.state = SceneData::Scene_Invalid; // Cannot recover from error! - Current state of backend scene invalid! [trf]
		throw;
	}
}

void RenderFrontendBase::StartParser(SceneData& shd, SceneId sid, POVMS_Object& obj)
{
	if(shd.state != SceneData::Scene_Created)
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(obj, kPOVMsgClass_SceneControl, kPOVMsgIdent_StartParser);

	msg.SetDestinationAddress(sid.GetAddress());
	msg.SetInt(kPOVAttrib_SceneId, sid.GetIdentifier());

	POVMS_SendMessage(context, msg, NULL, kPOVMSSendMode_NoReply);

	shd.state = SceneData::Scene_Parsing;
}

void RenderFrontendBase::PauseParser(SceneData& shd, SceneId sid)
{
	if(shd.state != SceneData::Scene_Parsing)
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_SceneControl, kPOVMsgIdent_PauseParser);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(sid.GetAddress());
	msg.SetInt(kPOVAttrib_SceneId, sid.GetIdentifier());

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
	{
		int err = result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr);

		if((err != kNoErr) && (err != kNotNowErr))
			throw POV_EXCEPTION_CODE(err);
	}

	shd.state = SceneData::Scene_Paused;
}

void RenderFrontendBase::ResumeParser(SceneData& shd, SceneId sid)
{
	if(shd.state != SceneData::Scene_Paused)
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_SceneControl, kPOVMsgIdent_ResumeParser);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(sid.GetAddress());
	msg.SetInt(kPOVAttrib_SceneId, sid.GetIdentifier());

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
	{
		int err = result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr);

		if((err != kNoErr) && (err != kNotNowErr))
			throw POV_EXCEPTION_CODE(err);
	}

	shd.state = SceneData::Scene_Parsing;
}

void RenderFrontendBase::StopParser(SceneData& shd, SceneId sid)
{
	if((shd.state != SceneData::Scene_Parsing) && (shd.state != SceneData::Scene_Paused))
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_SceneControl, kPOVMsgIdent_StopParser);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(sid.GetAddress());
	msg.SetInt(kPOVAttrib_SceneId, sid.GetIdentifier());

	shd.state = SceneData::Scene_Stopping;

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
	{
		int err = result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr);

		if((err != kNoErr) && (err != kNotNowErr))
			throw POV_EXCEPTION_CODE(err);
	}
}

RenderFrontendBase::ViewId RenderFrontendBase::CreateView(SceneData& shd, ViewData& vhd, SceneId sid, POVMS_Object& obj)
{
	POVMS_Message msg(obj, kPOVMsgClass_SceneControl, kPOVMsgIdent_CreateView);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(sid.GetAddress());
	msg.SetInt(kPOVAttrib_SceneId, sid.GetIdentifier());

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() == kPOVMsgIdent_Done)
		return ViewId(sid.GetAddress(), result.GetInt(kPOVAttrib_ViewId));
	else
		throw POV_EXCEPTION_CODE(result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr));
}

void RenderFrontendBase::CloseView(ViewData& vhd, ViewId vid)
{
	if(vid == Id())
		return;

	try
	{
		POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_SceneControl, kPOVMsgIdent_CloseView);
		POVMS_Message result(kPOVObjectClass_ResultData);

		msg.SetDestinationAddress(vid.GetAddress());
		msg.SetInt(kPOVAttrib_ViewId, vid.GetIdentifier());

		POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

		if(result.GetIdentifier() != kPOVMsgIdent_Done)
			throw POV_EXCEPTION_CODE(result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr));
	}
	catch(pov_base::Exception&)
	{
		vhd.state = ViewData::View_Invalid; // Cannot recover from error! - Current state of backend view invalid! [trf]
		throw;
	}
}

void RenderFrontendBase::StartRender(ViewData& vhd, ViewId vid, POVMS_Object& obj)
{
	if((vhd.state != ViewData::View_Created) && (vhd.state != ViewData::View_Rendered))
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(obj, kPOVMsgClass_ViewControl, kPOVMsgIdent_StartRender);

	msg.SetDestinationAddress(vid.GetAddress());
	msg.SetInt(kPOVAttrib_ViewId, vid.GetIdentifier());

	POVMS_SendMessage(context, msg, NULL, kPOVMSSendMode_NoReply);

	vhd.state = ViewData::View_Rendering;
}

void RenderFrontendBase::PauseRender(ViewData& vhd, ViewId vid)
{
	if(vhd.state != ViewData::View_Rendering)
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_ViewControl, kPOVMsgIdent_PauseRender);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(vid.GetAddress());
	msg.SetInt(kPOVAttrib_ViewId, vid.GetIdentifier());

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
	{
		int err = result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr);

		if((err != kNoErr) && (err != kNotNowErr))
			throw POV_EXCEPTION_CODE(err);
	}

	vhd.state = ViewData::View_Paused;
}

void RenderFrontendBase::ResumeRender(ViewData& vhd, ViewId vid)
{
	if(vhd.state != ViewData::View_Paused)
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_ViewControl, kPOVMsgIdent_ResumeRender);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(vid.GetAddress());
	msg.SetInt(kPOVAttrib_ViewId, vid.GetIdentifier());

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
	{
		int err = result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr);

		if((err != kNoErr) && (err != kNotNowErr))
			throw POV_EXCEPTION_CODE(err);
	}

	vhd.state = ViewData::View_Rendering;
}

void RenderFrontendBase::StopRender(ViewData& vhd, ViewId vid)
{
	if((vhd.state != ViewData::View_Rendering) && (vhd.state != ViewData::View_Paused))
		throw POV_EXCEPTION_CODE(kNotNowErr);

	POVMS_Message msg(kPOVObjectClass_ControlData, kPOVMsgClass_ViewControl, kPOVMsgIdent_StopRender);
	POVMS_Message result(kPOVObjectClass_ResultData);

	msg.SetDestinationAddress(vid.GetAddress());
	msg.SetInt(kPOVAttrib_ViewId, vid.GetIdentifier());

	vhd.state = ViewData::View_Stopping;

	POVMS_SendMessage(context, msg, &result, kPOVMSSendMode_WaitReply);

	if(result.GetIdentifier() != kPOVMsgIdent_Done)
	{
		int err = result.TryGetInt(kPOVAttrib_ErrorNumber, kNoErr);

		if((err != kNoErr) && (err != kNotNowErr))
			throw POV_EXCEPTION_CODE(err);
	}
}

void RenderFrontendBase::HandleMessage(POVMS_Message& msg, POVMS_Message& result, int)
{
	POVMSType ident = msg.GetIdentifier();

	switch(msg.GetClass())
	{
		case kPOVMsgClass_BackendControl:
			if(msg.Exist(kPOVAttrib_ErrorNumber) == true)
				OutputFatalError(msg.TryGetString(kPOVAttrib_EnglishText, "Unknown failure in backend!"), msg.GetInt(kPOVAttrib_ErrorNumber));
			else
				OutputFatalError(msg.TryGetString(kPOVAttrib_EnglishText, "Unknown failure in backend!"), 0);
			break;
		case kPOVMsgClass_SceneOutput:
			HandleParserMessage(SceneId(msg.GetSourceAddress(), msg.GetInt(kPOVAttrib_SceneId)), ident, msg);
			break;
		case kPOVMsgClass_ViewOutput:
			HandleRenderMessage(ViewId(msg.GetSourceAddress(), msg.GetInt(kPOVAttrib_ViewId)), ident, msg);
			break;
		case kPOVMsgClass_ViewImage:
			HandleImageMessage(ViewId(msg.GetSourceAddress(), msg.GetInt(kPOVAttrib_ViewId)), ident, msg);
			break;
		case kPOVMsgClass_FileAccess:
			HandleFileMessage(ViewId(msg.GetSourceAddress(), msg.GetInt(kPOVAttrib_SceneId)), ident, msg, result);
			break;
	}
}

void RenderFrontendBase::MakeBackupPath(POVMS_Object& ropts, ViewData& vd, const Path& outputpath)
{
	vd.imageBackupFile = outputpath;
	vd.imageBackupFile.SetFile((Path(ropts.TryGetUCS2String(kPOVAttrib_OutputFile, ""))).GetFile());
	if(vd.imageBackupFile.GetFile().empty() == true)
		vd.imageBackupFile.SetFile(ropts.TryGetUCS2String(kPOVAttrib_InputFile, "object.pov"));

	vd.imageBackupFile.SetFile(GetFileName(Path(vd.imageBackupFile.GetFile())) + ASCIItoUCS2String(".pov-state"));
}

void RenderFrontendBase::NewBackup(POVMS_Object& ropts, ViewData& vd, const Path& outputpath)
{
	vd.imageBackup.reset();

	MakeBackupPath(ropts, vd, outputpath);
	if(POV_ALLOW_FILE_WRITE(vd.imageBackupFile().c_str(), POV_File_Data_Backup) == false)
		throw POV_EXCEPTION(kCannotOpenFileErr, "Permission denied to create render state output file.");
	vd.imageBackup = shared_ptr<OStream>(POV_PLATFORM_BASE.CreateOStream(POV_File_Data_Backup));
	if(vd.imageBackup)
	{
		Backup_File_Header hdr;

		if(vd.imageBackup->open(vd.imageBackupFile().c_str()) == false)
			throw POV_EXCEPTION(kCannotOpenFileErr, "Cannot create render state output file.");
		memcpy(hdr.sig, RENDER_STATE_SIG, sizeof(hdr.sig));
		memcpy(hdr.ver, RENDER_STATE_VER, sizeof(hdr.ver));
		if(!vd.imageBackup->write(&hdr, sizeof(hdr)))
			throw POV_EXCEPTION(kFileDataErr, "Cannot write header to render state output file.");
		vd.imageBackup->flush();

		// we remove the file now since if the render doesn't complete and the file
		// already exists, an attempt to do a continue later on will skip the render.
		UCS2String filename = ropts.TryGetUCS2String(kPOVAttrib_OutputFile, "");
		if(filename.length() > 0)
		{
			// we do this test even if the file doesn't exist as we need to write there
			// eventually anyhow. might as well test if before the render starts ...
			if(CheckIfFileExists(filename.c_str()))
				POV_UCS2_REMOVE(filename.c_str());
		}
	}
	else
		throw POV_EXCEPTION(kCannotOpenFileErr, "Cannot create render state output file.");
}

void RenderFrontendBase::ContinueBackup(POVMS_Object& ropts, ViewData& vd, ViewId vid, POVMSInt& serial, vector<POVMSInt>& skip, const Path& outputpath)
{
	bool outputToFile = ropts.TryGetBool(kPOVAttrib_OutputToFile, true);

	// Note: due to the fact that tellg() only returns a 32-bit int on some platforms,
	// currently this code will only work properly with a state file that is < 4gb in
	// size, which works out to a render of roughly 16k*16k pixels.

	serial = 0;
	vd.imageBackup.reset();
	MakeBackupPath(ropts, vd, outputpath);

	boost::scoped_ptr<IStream> inbuffer(POV_PLATFORM_BASE.CreateIStream(POV_File_Data_Backup));

	size_t pos = sizeof(Backup_File_Header);

	if(inbuffer != NULL)
	{
		Backup_File_Header hdr;

		if(inbuffer->open(vd.imageBackupFile().c_str()) == true)
		{
			// IOBase::eof() only is based on feof() and will only return
			// true if we have attempted to read past the end of the file.
			// therefore msg.Read() will throw an exception when we try to
			// read from the end of the file, which isn't harmful per se but
			// makes debugging more difficult since it is caught by the VC++
			// IDE, and we don't want to disable catching pov_base::Exception
			// since they are generally useful. therefore we explicitly check
			// for the end of the file.
			inbuffer->seekg (0, IOBase::seek_end);
			POV_LONG end = inbuffer->tellg();
			inbuffer->seekg (0, IOBase::seek_set);

			if (!inbuffer->read (&hdr, sizeof (hdr)))
				throw POV_EXCEPTION(kFileDataErr, "Cannot read header from render state file.");
			if (memcmp (hdr.sig, RENDER_STATE_SIG, sizeof (hdr.sig)) != 0)
				throw POV_EXCEPTION(kFileDataErr, "Render state file header appears to be invalid.");
			if (memcmp (hdr.ver, RENDER_STATE_VER, sizeof (hdr.ver)) != 0)
				throw POV_EXCEPTION(kFileDataErr, "Render state file was written by another version of POV-Ray.");

			while(pos < end && inbuffer->eof() == false)
			{
				POVMS_Message msg;

				try
				{
					msg.Read(*(inbuffer.get()));

					// do not render complete blocks again
					if(msg.Exist(kPOVAttrib_PixelId) == true)
					{
						POVMSInt pid = msg.GetInt(kPOVAttrib_PixelId);

						if(pid > serial)
							skip.push_back(pid);
						else
							serial++;
					}

					HandleImageMessage(vid, msg.GetIdentifier(), msg);
				}
				catch(pov_base::Exception&)
				{
					// ignore all problems, just assume file is broken from last message on
					break;
				}
				pos = inbuffer->tellg();
			}
		}
		else
		{
			// file doesn't exist, we create it via NewBackup
			if (outputToFile == true)
				NewBackup(ropts, vd, outputpath);
			return;
		}
	}
	else
		throw POV_EXCEPTION(kCannotOpenFileErr, "Cannot open state file from previous render.");

	// make sure the input file is closed since we're about to write to it
	inbuffer.reset();

	// if there isn't going to be an output file, we don't write to the state file
	if(outputToFile == true)
	{
		vd.imageBackup = shared_ptr<OStream>(POV_PLATFORM_BASE.CreateOStream(POV_File_Data_Backup));
		if(vd.imageBackup)
		{
			if(vd.imageBackup->open(vd.imageBackupFile().c_str(), IOBase::append) == false)
				throw POV_EXCEPTION(kCannotOpenFileErr, "Cannot append to state output file.");

			vd.imageBackup->seekg(0, IOBase::seek_end);
			vd.imageBackup->seekg(min((POV_LONG)pos, vd.imageBackup->tellg()), IOBase::seek_set);
		}
		else
			throw POV_EXCEPTION(kCannotOpenFileErr, "Cannot create state output file stream.");
	}
}

namespace Message2Console
{

void InitInfo(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;
	const int NUMBER_OF_AUTHORS_ACROSS = 4;
	POVMSAttributeList attrlist;
	POVMSAttribute item;
	char charbuf[1024];
	int h, i, j;
	int cnt;
	int l;

	l = 1024;
	charbuf[0] = 0;
	if(POVMSUtil_GetString(msg, kPOVAttrib_CoreVersion, charbuf, &l) == kNoErr)
		tsb->printf("%s\n", charbuf);

	l = 1024;
	charbuf[0] = 0;
	if(POVMSUtil_GetString(msg, kPOVAttrib_EnglishText, charbuf, &l) == kNoErr)
		tsb->printf("%s\n", charbuf);

	tsb->printf("\n");

	tsb->printf("Primary POV-Ray 3.7 Architects/Developers: (Alphabetically)\n");
	if(POVMSObject_Get(msg, &attrlist, kPOVAttrib_PrimaryDevs) == kNoErr)
	{
		cnt = 0;

		if(POVMSAttrList_Count(&attrlist, &cnt) == kNoErr)
		{
			for(i = 0, h = 1; h <= cnt; i++)
			{
				for(j = 0; (j < NUMBER_OF_AUTHORS_ACROSS) && (h <= cnt); j++, h++)
				{
					if(POVMSAttrList_GetNth(&attrlist, h, &item) == kNoErr)
					{
						l = 1023;
						charbuf[0] = 0;
						if(POVMSAttr_Get(&item, kPOVMSType_CString, charbuf, &l) == kNoErr)
							tsb->printf("  %-18s", charbuf);

						(void)POVMSAttr_Delete(&item);
					}
				}
				tsb->printf("\n");
			}
		}

		(void)POVMSAttrList_Delete(&attrlist);
	}

	tsb->printf("\n");
	tsb->printf("With Assistance From: (Alphabetically)\n");
	if(POVMSObject_Get(msg, &attrlist, kPOVAttrib_AssistingDevs) == kNoErr)
	{
		cnt = 0;

		if(POVMSAttrList_Count(&attrlist, &cnt) == kNoErr)
		{
			for(i = 0, h = 1; h <= cnt; i++)
			{
				for(j = 0; (j < NUMBER_OF_AUTHORS_ACROSS) && (h <= cnt); j++, h++)
				{
					if(POVMSAttrList_GetNth(&attrlist, h, &item) == kNoErr)
					{
						l = 1023;
						charbuf[0] = 0;
						if(POVMSAttr_Get(&item, kPOVMSType_CString, charbuf, &l) == kNoErr)
							tsb->printf("  %-18s", charbuf);

						(void)POVMSAttr_Delete(&item);
					}
				}
				tsb->printf("\n");
			}
		}

		(void)POVMSAttrList_Delete(&attrlist);
	}

	tsb->printf("\n");
	tsb->printf("Past Contributors: (Alphabetically)\n");
	if(POVMSObject_Get(msg, &attrlist, kPOVAttrib_ContributingDevs) == kNoErr)
	{
		cnt = 0;

		if(POVMSAttrList_Count(&attrlist, &cnt) == kNoErr)
		{
			for(i = 0, h = 1; h <= cnt; i++)
			{
				for(j = 0; (j < NUMBER_OF_AUTHORS_ACROSS) && (h <= cnt); j++, h++)
				{
					if(POVMSAttrList_GetNth(&attrlist, h, &item) == kNoErr)
					{
						l = 1023;
						charbuf[0] = 0;
						if(POVMSAttr_Get(&item, kPOVMSType_CString, charbuf, &l) == kNoErr)
							tsb->printf("  %-18s", charbuf);

						(void)POVMSAttr_Delete(&item);
					}
				}
				tsb->printf("\n");
			}
		}

		(void)POVMSAttrList_Delete(&attrlist);
	}

	tsb->printf("\n");
	tsb->printf("Other contributors are listed in the documentation.\n");

	tsb->printf("\n");
	if(POVMSObject_Get(msg, &attrlist, kPOVAttrib_ImageLibVersions) == kNoErr)
	{
		cnt = 0;

		if(POVMSAttrList_Count(&attrlist, &cnt) == kNoErr)
		{
			if(cnt > 0)
			{
				tsb->printf("Support libraries used by POV-Ray:\n");

				for(i = 1; i <= cnt; i++)
				{
					if(POVMSAttrList_GetNth(&attrlist, i, &item) == kNoErr)
					{
						l = 1023;
						charbuf[0] = 0;
						if(POVMSAttr_Get(&item, kPOVMSType_CString, charbuf, &l) == kNoErr)
							tsb->printf("  %s\n", charbuf);

						(void)POVMSAttr_Delete(&item);
					}
				}
			}
		}

		(void)POVMSAttrList_Delete(&attrlist);
	}

	POVMSObject_Delete(msg);
}

void ParserOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;
	POVMSAttribute attr;
	POVMSFloat f;
	UCS2 ucs2buf[1024];
	int l;

	tsb->printf("Parser Options\n");

	f = 0.0;
	l = sizeof (ucs2buf);
	ucs2buf[0] = 0;
	(void)POVMSUtil_GetUCS2String(msg, kPOVAttrib_InputFile, ucs2buf, &l);
	if(POVMSUtil_GetFloat(msg, kPOVAttrib_Version, &f) != kNoErr)
		tsb->printf("  Input file: %s\n", UCS2toASCIIString(ucs2buf).c_str());
	else
		tsb->printf("  Input file: %s (compatible to version %1.2f)\n", UCS2toASCIIString(ucs2buf).c_str(), (double)f);
	tsb->printf("  Remove bounds.......%s\n  Split unions........%s\n",
	              GetOptionSwitchString(msg, kPOVAttrib_RemoveBounds, true),
	              GetOptionSwitchString(msg, kPOVAttrib_SplitUnions, false));

	tsb->printf("  Library paths:\n");
	if(POVMSObject_Get(msg, &attr, kPOVAttrib_LibraryPath) == kNoErr)
	{
		int cnt = 0;

		if(POVMSAttrList_Count(&attr, &cnt) == kNoErr)
		{
			POVMSAttribute item;
			int ii;

			for(ii = 1; ii <= cnt; ii++)
			{
				if(POVMSAttrList_GetNth(&attr, ii, &item) == kNoErr)
				{
					l = sizeof(ucs2buf);
					ucs2buf[0] = 0;
					(void)POVMSAttr_Get(&item, kPOVMSType_UCS2String, ucs2buf, &l);
					tsb->printf("    %s\n", UCS2toASCIIString(ucs2buf).c_str());

					(void)POVMSAttr_Delete(&item);
				}
			}
		}

		(void)POVMSAttr_Delete(&attr);
	}

	POVMSObject_Delete(msg);
	AnimationOptions(cppmsg,tsb);
}

void RenderOptions(POVMS_Object& obj, TextStreamBuffer *tsb)
{
	tsb->printf("----------------------------------------------------------------------------\n");
	tsb->printf("Render Options\n");

	tsb->printf("  Quality: %2d\n", clip(obj.TryGetInt(kPOVAttrib_Quality, 9), 0, 9));

	if(obj.TryGetBool (kPOVAttrib_Bounding, true))
		tsb->printf("  Bounding boxes.......On   Bounding threshold: %d\n", obj.TryGetInt (kPOVAttrib_BoundingThreshold, 3));
	else
		tsb->printf("  Bounding boxes.......Off\n");

	/*
	tsb->printf("  Light Buffer........%s\n", GetOptionSwitchString(msg, kPOVAttrib_LightBuffer, true));
	tsb->printf("  Vista Buffer........%-3s", GetOptionSwitchString(msg, kPOVAttrib_VistaBuffer, true));
	b = false;
	(void)POVMSUtil_GetBool(msg, kPOVAttrib_VistaBuffer, &b);
	if(b == true)
		tsb->printf("  Draw Vista Buffer...%s", GetOptionSwitchString(msg, kPOVAttrib_DrawVistas, false));
	tsb->printf("\n");
*/

	if(obj.TryGetBool(kPOVAttrib_Antialias, false) == true)
	{
		int method = 0;
		if(obj.TryGetBool(kPOVAttrib_Antialias, false) == true)
			method = clip(obj.TryGetInt(kPOVAttrib_SamplingMethod, 1), 0, 2);
		int depth = clip(obj.TryGetInt(kPOVAttrib_AntialiasDepth, 3), 1, 9);
		float threshold = clip(obj.TryGetFloat(kPOVAttrib_AntialiasThreshold, 0.3f), 0.0f, 1.0f);
		float aagamma = obj.TryGetFloat(kPOVAttrib_AntialiasGamma, 2.5f);
		float jitter = 0.0f;
		if(obj.TryGetBool(kPOVAttrib_Jitter, true))
			jitter = clip(obj.TryGetFloat(kPOVAttrib_JitterAmount, 1.0f), 0.0f, 1.0f);
		if(jitter > 0.0f)
			tsb->printf("  Antialiasing.........On  (Method %d, Threshold %.3f, Depth %d, Jitter %.2f, Gamma %.2f)\n",
			               method, threshold, depth, jitter, aagamma);
		else
			tsb->printf("  Antialiasing.........On  (Method %d, Threshold %.3f, Depth %d, Jitter Off, Gamma %.2f)\n",
			               method, threshold, depth, aagamma);
	}
	else
		tsb->printf("  Antialiasing.........Off\n");
}

void OutputOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;
	POVMSInt i, i2;
	POVMSFloat f, f2, f3, f4;
	int startRow, startCol, endRow, endCol;
	POVMSBool b;
	UCS2 ucs2buf[1024];
	char *t;
	int outputQuality = 8; // default bits per pixel channel // TODO FIXME: Default values shouldn't be hard-coded in here!
	int outputCompression;
	int l;
	int outputFormat = kPOVList_FileType_PNG; // TODO FIXME: Default values shouldn't be hard-coded in here!
	bool outputGrayscale = false;

	tsb->printf("Image Output Options\n");

	(void)POVMSUtil_GetInt(msg, kPOVAttrib_Width, &i);
	(void)POVMSUtil_GetInt(msg, kPOVAttrib_Height, &i2);
	if (POVMSUtil_GetFloat(msg, kPOVAttrib_StartRow, &f) != kNoErr)
		f = 0.0;
	if (POVMSUtil_GetFloat(msg, kPOVAttrib_EndRow, &f2) != kNoErr)
		f2 = (POVMSFloat)i2;
	if (POVMSUtil_GetFloat(msg, kPOVAttrib_StartColumn, &f3) != kNoErr)
		f3 = 0.0;
	if (POVMSUtil_GetFloat(msg, kPOVAttrib_EndColumn, &f4) != kNoErr)
		f4 = (POVMSFloat)i;

	// TODO FIXME - interpretation of the render rectangle (integer vs float) should be implemented elsewhere;
	// currently there is redundancy with View::StartRender(...) in view.cpp

	if((f3 >= 0.0) && (f3 < 1.0))
		startCol = int(DBL(i) * f3);
	else
		startCol = int(f3) - 1;

	if((f >= 0.0) && (f < 1.0))
		startRow = int(DBL(i2) * f);
	else
		startRow = int(f - 1);

	if((f4 >= 0.0) && (f4 <= 1.0))
		endCol = int(DBL(i) * f4) - 1;
	else
		endCol = int(f4 - 1);

	if((f2 >= 0.0) && (f2 <= 1.0))
		endRow = int(DBL(i2) * f2) - 1;
	else
		endRow = int(f2 - 1);

	tsb->printf("  Image resolution.....%u by %u (rows %d to %d, columns %d to %d).\n",
	            (int)i, (int)i2, (int)(startRow+1), (int)(endRow+1), (int)(startCol+1), (int)(endCol+1));

	if(POVMSUtil_GetInt(msg, kPOVAttrib_OutputFileType, &i) == kNoErr)
		outputFormat = i;
	if(POVMSUtil_GetInt(msg, kPOVAttrib_BitsPerColor, &i) == kNoErr)
		outputQuality = i;
	if(POVMSUtil_GetBool(msg, kPOVAttrib_GrayscaleOutput, &b) == kNoErr)
		outputGrayscale = b;

	b = false;
	if(POVMSUtil_GetBool(msg, kPOVAttrib_OutputToFile, &b) != kNoErr || b == true) // TODO FIXME: Defaults (in this case b=true) shouldn't be hard-coded in here!
	{
		char *al = "";

		l = 1023;
		ucs2buf[0] = 0;
		(void)POVMSUtil_GetUCS2String(msg, kPOVAttrib_OutputFile, ucs2buf, &l);

		if(outputFormat == kPOVList_FileType_JPEG)
		{
			outputQuality = 85; // Default from base/image/jpeg.cpp // TODO FIXME: Default values shouldn't be hard-coded in here!
			if(POVMSUtil_GetInt(msg, kPOVAttrib_Compression, &i) == kNoErr)
			{
				outputQuality = i>1?i:85; // Catching default 0 & 1 from base/image/jpeg.cpp // TODO FIXME: Default values shouldn't be hard-coded in here!
				outputQuality = max(0, outputQuality);
				outputQuality = min(100, outputQuality);
			}
			outputCompression = outputQuality;
			outputQuality = 8;
		}

		b = false;
		(void)POVMSUtil_GetBool(msg, kPOVAttrib_OutputAlpha, &b);
		if(b == true)
		{
			outputQuality *= 4;
			al = " with alpha";
		}
		else
			outputQuality *= 3;

		switch(outputFormat)
		{
			// TODO FIXME - for easier maintenance, this should probably be part of the FileTypeTable.
			case kPOVList_FileType_Targa:           t = "Targa";           break;
			case kPOVList_FileType_CompressedTarga: t = "RLE Targa";       break;
			case kPOVList_FileType_PNG:             t = "PNG";             break;
			case kPOVList_FileType_JPEG:            t = "JPEG";            break;
			case kPOVList_FileType_PPM:             t = "PPM";             break;
			case kPOVList_FileType_BMP:             t = "BMP";             break;
			case kPOVList_FileType_OpenEXR:         t = "EXR";             break;
			case kPOVList_FileType_RadianceHDR:     t = "HDR";             break;
			case kPOVList_FileType_System:          t = "(system format)"; break;
			default:                                t = "(none)";          break;
		}

		if(outputFormat == kPOVList_FileType_JPEG)
			tsb->printf("  Output file..........%s, %d bpp, quality %d%s%s %s\n", UCS2toASCIIString(ucs2buf).c_str(), outputQuality, outputCompression, "%", al, t);
		else if (outputGrayscale)
			tsb->printf("  Output file..........%s, grayscale%s %s\n", UCS2toASCIIString(ucs2buf).c_str(), al, t);
		else
			tsb->printf("  Output file..........%s, %d bpp%s %s\n", UCS2toASCIIString(ucs2buf).c_str(), outputQuality, al, t);

		if ((outputFormat != kPOVList_FileType_JPEG) && (outputFormat != kPOVList_FileType_OpenEXR))
		{
			b = false;
			(void)POVMSUtil_GetBool(msg, kPOVAttrib_Dither, &b);
			if (b)
			{
				i = kPOVList_DitherMethod_FloydSteinberg;
				(void)POVMSUtil_GetInt(msg, kPOVAttrib_DitherMethod, &i);
				switch(i)
				{
					// TODO FIXME - for easier maintenance, this should probably be part of the DitherMethodTable.
					case kPOVList_DitherMethod_Bayer2x2:        t = "2x2 Bayer pattern";                break;
					case kPOVList_DitherMethod_Bayer3x3:        t = "3x3 Bayer pattern";                break;
					case kPOVList_DitherMethod_Bayer4x4:        t = "4x4 Bayer pattern";                break;
					case kPOVList_DitherMethod_Diffusion1D:     t = "simple 1-D error diffusion";       break;
					case kPOVList_DitherMethod_Diffusion2D:     t = "simple 2-D error diffusion";       break;
					case kPOVList_DitherMethod_FloydSteinberg:  t = "Floyd-Steinberg error diffusion";  break;
					default:                                    t = "(unknown)";                        break;
				}
				tsb->printf("  Dithering............%s\n", t);
			}
			else
				tsb->printf("  Dithering............Off\n");
		}
	}
	else
		tsb->printf("  Output file: Disabled\n");

	b = false;
	(void)POVMSUtil_GetBool(msg, kPOVAttrib_Display, &b);
	if(b == true)
	{
		i = DEFAULT_DISPLAY_GAMMA_TYPE;
		(void)POVMSUtil_GetInt(msg, kPOVAttrib_DisplayGammaType, &i);
		f = DEFAULT_DISPLAY_GAMMA;
		(void)POVMSUtil_GetFloat(msg, kPOVAttrib_DisplayGamma, &f);
		switch (i)
		{
			case kPOVList_GammaType_Neutral:
				tsb->printf("  Graphic display......On  (gamma: 1.0)\n");
				break;
			case kPOVList_GammaType_PowerLaw:
				tsb->printf("  Graphic display......On  (gamma: %g)\n", (float)f);
				break;
			case kPOVList_GammaType_SRGB:
				tsb->printf("  Graphic display......On  (gamma: sRGB)\n");
				break;
			default:
				throw POV_EXCEPTION_STRING("Unknown gamma mode in OutputOptions()");
		}
	}
	else
		tsb->printf("  Graphic display......Off\n");

	i = 0;
	(void)POVMSUtil_GetInt(msg, kPOVAttrib_PreviewStartSize, &i);
	if(i > 1)
	{
		i2 = 0;
		(void)POVMSUtil_GetInt(msg, kPOVAttrib_PreviewEndSize, &i2);
		tsb->printf("  Mosaic preview.......On  (pixel sizes %d to %d)\n", (int)i, (int)i2);
	}
	else
		tsb->printf("  Mosaic preview.......Off\n");

	tsb->printf("  Continued trace.....%s\n", GetOptionSwitchString(msg, kPOVAttrib_ContinueTrace, false));

	tsb->printf("Information Output Options\n");

	tsb->printf("  All Streams to console.........%s", GetOptionSwitchString(msg, kPOVAttrib_AllConsole, true));
//	if(streamnames[ALL_STREAM] != NULL)
//		tsb->printf("  and file %s\n", streamnames[ALL_STREAM]);
//	else
		tsb->printf("\n");

	tsb->printf("  Debug Stream to console........%s", GetOptionSwitchString(msg, kPOVAttrib_DebugConsole, true));
//	if(streamnames[DEBUG_STREAM] != NULL)
//		tsb->printf("  and file %s\n", streamnames[DEBUG_STREAM]);
//	else
		tsb->printf("\n");

	tsb->printf("  Fatal Stream to console........%s", GetOptionSwitchString(msg, kPOVAttrib_FatalConsole, true));
//	if(streamnames[FATAL_STREAM] != NULL)
//		tsb->printf("  and file %s\n", streamnames[FATAL_STREAM]);
//	else
		tsb->printf("\n");

	tsb->printf("  Render Stream to console.......%s", GetOptionSwitchString(msg, kPOVAttrib_RenderConsole, true));
//	if(streamnames[RENDER_STREAM] != NULL)
//		tsb->printf("  and file %s\n", streamnames[RENDER_STREAM]);
//	else
		tsb->printf("\n");

	tsb->printf("  Statistics Stream to console...%s", GetOptionSwitchString(msg, kPOVAttrib_StatisticsConsole, true));
//	if(streamnames[STATISTIC_STREAM] != NULL)
//		tsb->printf("  and file %s\n", streamnames[STATISTIC_STREAM]);
//	else
		tsb->printf("\n");

	tsb->printf("  Warning Stream to console......%s", GetOptionSwitchString(msg, kPOVAttrib_WarningConsole, true));
//	if(streamnames[WARNING_STREAM] != NULL)
//		tsb->printf("  and file %s\n", streamnames[WARNING_STREAM]);
//	else
		tsb->printf("\n");

	POVMSObject_Delete(msg);
}

void AnimationOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;
	POVMSInt i, i2;
	POVMSFloat f, f2;

	i = 1;
	i2 = 1;
	f = 0.0;
	(void)POVMSUtil_GetInt(msg, kPOVAttrib_InitialFrame, &i);
	(void)POVMSUtil_GetInt(msg, kPOVAttrib_FinalFrame, &i2);
	(void)POVMSUtil_GetFloat(msg, kPOVAttrib_Clock, &f);
	if((i != 1) || (i2 != 1) || (i != i2) || (f != 0.0))
	{
		tsb->printf("Animation Options\n");
		tsb->printf("  Initial Frame: %8d  Final Frame: %8d\n", (int)i, (int)i2);
		i2 = 1; // POVMSUtil_GetInt might fail, default to 1
		(void)POVMSUtil_GetInt(msg, kPOVAttrib_FrameStep, &i2);
		i2=max(1,i2);
		tsb->printf("  Frame Step: %11d\n",i2);
		f = 0.0;
		f2 = 0.0;
		(void)POVMSUtil_GetFloat(msg, kPOVAttrib_InitialClock, &f);
		(void)POVMSUtil_GetFloat(msg, kPOVAttrib_FinalClock, &f2);
		tsb->printf("  Initial Clock: %8.3f  Final Clock: %8.3f\n", (float)f, (float)f2);
		tsb->printf("  Cyclic Animation....%s  Field render........%s  Odd lines/frames....%s",
		              GetOptionSwitchString(msg, kPOVAttrib_CyclicAnimation, false),
		              GetOptionSwitchString(msg, kPOVAttrib_FieldRender, false),
		              GetOptionSwitchString(msg, kPOVAttrib_OddField, false));
	}
	else
		tsb->printf("  Clock value: %8.3f  (Animation off)", (float)f);
	tsb->printf("\n");

	POVMSObject_Delete(msg);
}

void ParserStatistics(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSLong ll = 0;
	int l = 0;
	int s = 0;
	int i = 0;

	tsb->printf("----------------------------------------------------------------------------\n");
	tsb->printf("Parser Statistics\n");
	tsb->printf("----------------------------------------------------------------------------\n");

	s = cppmsg.TryGetInt(kPOVAttrib_FiniteObjects, 0);
	i = cppmsg.TryGetInt(kPOVAttrib_InfiniteObjects, 0);
	l = cppmsg.TryGetInt(kPOVAttrib_LightSources, 0);

	tsb->printf("Finite Objects:   %10d\n", s);
	tsb->printf("Infinite Objects: %10d\n", i);
	tsb->printf("Light Sources:    %10d\n", l);
	tsb->printf("Total:            %10d\n", s + i + l);

	if(cppmsg.Exist(kPOVAttrib_BSPNodes) == true)
	{
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("BSP Split Nodes:  %10d\n", cppmsg.TryGetInt(kPOVAttrib_BSPSplitNodes, 0));
		tsb->printf("BSP Object Nodes: %10d\n", cppmsg.TryGetInt(kPOVAttrib_BSPObjectNodes, 0));
		tsb->printf("BSP Empty Nodes:  %10d\n", cppmsg.TryGetInt(kPOVAttrib_BSPEmptyNodes, 0));
		tsb->printf("BSP Total Nodes:  %10d\n", cppmsg.TryGetInt(kPOVAttrib_BSPNodes, 0));
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("BSP Objects/Node Average:       %8.2f          Maximum:      %10d\n",
		            cppmsg.TryGetFloat(kPOVAttrib_BSPAverageObjects, 0.0f), cppmsg.TryGetInt(kPOVAttrib_BSPMaxObjects, 0));
		tsb->printf("BSP Tree Depth Average:         %8.2f          Maximum:      %10d\n",
		            cppmsg.TryGetFloat(kPOVAttrib_BSPAverageDepth, 0.0f), cppmsg.TryGetInt(kPOVAttrib_BSPMaxDepth, 0));
		tsb->printf("BSP Max Depth Stopped Nodes:  %10d (%3.1f%%)   Objects/Node:   %8.2f\n",
		            cppmsg.TryGetInt(kPOVAttrib_BSPAborts, 0), cppmsg.TryGetFloat(kPOVAttrib_BSPAverageAborts, 0.0f) * 100.0f,
		            cppmsg.TryGetFloat(kPOVAttrib_BSPAverageAbortObjects, 0.0f));
	}

	tsb->printf("----------------------------------------------------------------------------\n");
}

void RenderStatistics(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;
	POVMSAttribute attr;
	POVMSLong l, l2, l3;
	POV_LONG Pixels_In_Image;
	int i, i2;

	tsb->printf("----------------------------------------------------------------------------\n");

	(void)POVMSUtil_GetInt(msg, kPOVAttrib_Width, &i);
	(void)POVMSUtil_GetInt(msg, kPOVAttrib_Height, &i2);
	Pixels_In_Image = (POV_LONG)i * (POV_LONG)i2;

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_Pixels, &l);
	if(Pixels_In_Image > POVMSLongToCDouble(l))
		tsb->printf("Render Statistics (Partial Image Rendered)\n");
	else
		tsb->printf("Render Statistics\n");

	tsb->printf("Image Resolution %d x %d\n", i, i2);

	tsb->printf("----------------------------------------------------------------------------\n");

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_Pixels, &l);
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_PixelSamples, &l2);
	if(POVMSLongToCDouble(l) > 0.5)
		tsb->printf("Pixels:  %15.0f   Samples: %15.0f   Smpls/Pxl: %.2f\n",
		            POVMSLongToCDouble(l), POVMSLongToCDouble(l2), POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
	else
		tsb->printf("Pixels:  %15.0f   Samples: %15.0f   Smpls/Pxl: -\n",
		            POVMSLongToCDouble(l), POVMSLongToCDouble(l2));

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_Rays, &l);
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_RaysSaved, &l2);
	(void)POVMSUtil_GetInt(msg, kPOVAttrib_TraceLevel, &i);
	(void)POVMSUtil_GetInt(msg, kPOVAttrib_MaxTraceLevel, &i2);
	tsb->printf("Rays:    %15.0f   Saved:   %15.0f   Max Level: %d/%d\n",
	            POVMSLongToCDouble(l), POVMSLongToCDouble(l2), i, i2);

	tsb->printf("----------------------------------------------------------------------------\n");
	tsb->printf("Ray->Shape Intersection          Tests       Succeeded  Percentage\n");
	tsb->printf("----------------------------------------------------------------------------\n");

	if(POVMSObject_Get(msg, &attr, kPOVAttrib_ObjectIStats) == kNoErr)
	{
		int cnt = 0;

		if(POVMSAttrList_Count(&attr, &cnt) == kNoErr)
		{
			POVMSObject obj;
			int ii, len;
			char str[40];

			for(ii = 1; ii <= cnt; ii++)
			{
				if(POVMSAttrList_GetNth(&attr, ii, &obj) == kNoErr)
				{
					len = 40;
					str[0] = 0;
					(void)POVMSUtil_GetString(&obj, kPOVAttrib_ObjectName, str, &len);
					(void)POVMSUtil_GetLong(&obj, kPOVAttrib_ISectsTests, &l);
					(void)POVMSUtil_GetLong(&obj, kPOVAttrib_ISectsSucceeded, &l2);

					if(POVMSLongToCDouble(l) > 0.5)
					{
						tsb->printf("%-22s  %14.0f  %14.0f  %8.2f\n", str,
						              POVMSLongToCDouble(l), POVMSLongToCDouble(l2),
						              100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
					}

					(void)POVMSAttr_Delete(&obj);
				}
			}
		}

		(void)POVMSAttr_Delete(&attr);
	}

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_IsoFindRoot, &l);
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_FunctionVMCalls, &l2);
	if((POVMSLongToCDouble(l) > 0.5) || (POVMSLongToCDouble(l2) > 0.5))
	{
		tsb->printf("----------------------------------------------------------------------------\n");
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Isosurface roots:   %15.0f\n", POVMSLongToCDouble(l));
		if(POVMSLongToCDouble(l2) > 0.5)
			tsb->printf("Function VM calls:  %15.0f\n", POVMSLongToCDouble(l2));
	}

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_CrackleCacheTest, &l);
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_CrackleCacheTestSuc, &l2);
	if((POVMSLongToCDouble(l) > 0.5) || (POVMSLongToCDouble(l2) > 0.5))
	{
		tsb->printf("----------------------------------------------------------------------------\n");
			if(POVMSLongToCDouble(l) > 0.5)
				tsb->printf("Crackle Cache Queries: %15.0f\n", POVMSLongToCDouble(l));
			if(POVMSLongToCDouble(l2) > 0.5)
				tsb->printf("Crackle Cache Hits:    %15.0f (%3.0f percent)\n", POVMSLongToCDouble(l2),
				            100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
	}

	tsb->printf("----------------------------------------------------------------------------\n");

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_PolynomTest, &l);
	if(POVMSLongToCDouble(l) > 0.5)
	{
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RootsEliminated, &l2);
		tsb->printf("Roots tested:       %15.0f   eliminated:      %15.0f\n",
		              POVMSLongToCDouble(l), POVMSLongToCDouble(l2));
	}

	// TODO FIXME
	//(void)POVMSUtil_GetLong(msg, kPOVAttrib_CallsToNoise, &l);
	//(void)POVMSUtil_GetLong(msg, kPOVAttrib_CallsToDNoise, &l2);
	//tsb->printf("Calls to Noise:     %15.0f   Calls to DNoise: %15.0f\n",
	//              POVMSLongToCDouble(l), POVMSLongToCDouble(l2));
	// tsb->printf("----------------------------------------------------------------------------\n");

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_MediaIntervals, &l);
	if(POVMSLongToCDouble(l) > 0.5)
	{
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_MediaSamples, &l2);
		tsb->printf("Media Intervals:    %15.0f   Media Samples:   %15.0f (%4.2f)\n",
		              POVMSLongToCDouble(l), POVMSLongToCDouble(l2), POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
	}

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_ShadowTest, &l);
	if(POVMSLongToCDouble(l) > 0.5)
	{
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_ShadowTestSuc, &l2);

		tsb->printf("Shadow Ray Tests:   %15.0f   Succeeded:       %15.0f\n",
		              POVMSLongToCDouble(l), POVMSLongToCDouble(l2));

		(void)POVMSUtil_GetLong(msg, kPOVAttrib_ShadowCacheHits, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Shadow Cache Hits:  %15.0f\n", POVMSLongToCDouble(l));
	}

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_ReflectedRays, &l);
	if(POVMSLongToCDouble(l) > 0.5)
	{
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_InnerReflectedRays, &l2);
		if(POVMSLongToCDouble(l2) > 0)
			tsb->printf("Reflected Rays:     %15.0f   Total Internal:  %15.0f\n",
			              POVMSLongToCDouble(l), POVMSLongToCDouble(l2));
		else
			tsb->printf("Reflected Rays:     %15.0f\n", POVMSLongToCDouble(l));
	}

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_RefractedRays, &l);
	if(POVMSLongToCDouble(l) > 0.5)
		tsb->printf("Refracted Rays:     %15.0f\n", POVMSLongToCDouble(l));

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_TransmittedRays, &l);
	if(POVMSLongToCDouble(l) > 0.5)
		tsb->printf("Transmitted Rays:   %15.0f\n", POVMSLongToCDouble(l));
/*
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_BoundingQueues, &l);
	if(POVMSLongToCDouble(l) > 0.5)
	{
		tsb->printf("Bounding Queues:    %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_BoundingQueueResets, &l);
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_BoundingQueueResizes, &l2);
		tsb->printf("Queue Resets:       %15.0f   Queue Resizes:   %15.0f\n",
		              POVMSLongToCDouble(l), POVMSLongToCDouble(l2));
	}
*/
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadGatherCount, &l);
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadReuseCount, &l2);
	if((POVMSLongToCDouble(l) > 0.5) || (POVMSLongToCDouble(l2) > 0.5))
	{
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("Radiosity samples calculated:  %15.0f (%.2f %%)\n", POVMSLongToCDouble(l), 100.0 * POVMSLongToCDouble(l) / (POVMSLongToCDouble(l) + POVMSLongToCDouble(l2)));

		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadUnsavedCount, &l3);
		if(POVMSLongToCDouble(l3) > 0.5)
		{
			l -= l3;
			tsb->printf("  discarded due to low quality:%15.0f\n", POVMSLongToCDouble(l3));
			tsb->printf("  retained for re-use:         %15.0f\n", POVMSLongToCDouble(l));
		}

		tsb->printf("Radiosity samples reused:      %15.0f\n", POVMSLongToCDouble(l2));

		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadRayCount, &l3);
		if(POVMSLongToCDouble(l3) > 0.5)
		{
			tsb->printf("Radiosity sample rays shot:    %15.0f\n", POVMSLongToCDouble(l3));
		}

		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeNodes, &l2);
		if(POVMSLongToCDouble(l2) > 0.5)
		{
			tsb->printf("Radiosity octree nodes:        %15.0f\n", POVMSLongToCDouble(l2));
			tsb->printf("Radiosity octree samples/node: %15.2f\n", POVMSLongToCDouble(l) / POVMSLongToCDouble(l2));
		}

		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeLookups, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Radiosity blocks examined:     %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeAccepts0, &l2);
		if(POVMSLongToCDouble(l2) > 0.5)
			tsb->printf("Radiosity blocks passed test 0:%15.0f (%.2f %%)\n", POVMSLongToCDouble(l2), 100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeAccepts1, &l2);
		if(POVMSLongToCDouble(l2) > 0.5)
			tsb->printf("Radiosity blocks passed test 1:%15.0f (%.2f %%)\n", POVMSLongToCDouble(l2), 100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeAccepts2, &l2);
		if(POVMSLongToCDouble(l2) > 0.5)
			tsb->printf("Radiosity blocks passed test 2:%15.0f (%.2f %%)\n", POVMSLongToCDouble(l2), 100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeAccepts3, &l2);
		if(POVMSLongToCDouble(l2) > 0.5)
			tsb->printf("Radiosity blocks passed test 3:%15.0f (%.2f %%)\n", POVMSLongToCDouble(l2), 100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeAccepts4, &l2);
		if(POVMSLongToCDouble(l2) > 0.5)
			tsb->printf("Radiosity blocks passed test 4:%15.0f (%.2f %%)\n", POVMSLongToCDouble(l2), 100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadOctreeAccepts5, &l2);
		if(POVMSLongToCDouble(l2) > 0.5)
			tsb->printf("Radiosity blocks passed test 5:%15.0f (%.2f %%)\n", POVMSLongToCDouble(l2), 100.0 * POVMSLongToCDouble(l2) / POVMSLongToCDouble(l));
		l3 = l - l2;
		if(POVMSLongToCDouble(l3) > 0.5)
			tsb->printf("Radiosity blocks rejected:     %15.0f (%.2f %%)\n", POVMSLongToCDouble(l3), 100.0 * POVMSLongToCDouble(l3) / POVMSLongToCDouble(l));

		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadTopLevelGatherCount, &l);
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadTopLevelReuseCount, &l2);
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadTopLevelRayCount, &l3);
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("Radiosity Depth 0 calculated:  %15.0f (%.2f %%)\n", POVMSLongToCDouble(l), 100.0 * POVMSLongToCDouble(l) / (POVMSLongToCDouble(l) + POVMSLongToCDouble(l2)));
		tsb->printf("Radiosity Depth 0 reused:      %15.0f\n", POVMSLongToCDouble(l2));
		tsb->printf("Radiosity Depth 0 rays shot:   %15.0f\n", POVMSLongToCDouble(l3));

		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadFinalGatherCount, &l);
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadFinalReuseCount, &l2);
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_RadFinalRayCount, &l3);
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("Radiosity (final) calculated:  %15.0f (%.2f %%)\n", POVMSLongToCDouble(l), 100.0 * POVMSLongToCDouble(l) / (POVMSLongToCDouble(l) + POVMSLongToCDouble(l2)));
		tsb->printf("Radiosity (final) reused:      %15.0f\n", POVMSLongToCDouble(l2));
		tsb->printf("Radiosity (final) rays shot:   %15.0f\n", POVMSLongToCDouble(l3));

		POVMSLong samples[5][5];
		POVMSLong sampleSumPerRecursion[5];
		POVMSLong sampleSumPerPass[5];
		POVMSLong samplesFinal[5];
		POVMSLong sampleSumFinal = 0;
		POVMSLong sampleSum = 0;
		POVMSFloat sampleWeight[5];
		for (int recursion = 0; recursion < 5; recursion ++)
			sampleSumPerRecursion[recursion] = 0;
		for(int pass = 1; pass <= 5; pass ++)
		{
			sampleSumPerPass[pass-1] = 0;
			for (int recursion = 0; recursion < 5; recursion ++)
			{
				unsigned int id = kPOVAttrib_RadSamplesP1R0 + (pass-1)*256 + recursion;
				(void)POVMSUtil_GetLong(msg, id, &l);
				samples[pass-1][recursion] = l;
				sampleSumPerPass[pass-1] += l;
				sampleSumPerRecursion[recursion] += l;
				sampleSum += l;
			}
		}
		for (int recursion = 0; recursion < 5; recursion ++)
		{
			unsigned int id = kPOVAttrib_RadSamplesFR0 + recursion;
			(void)POVMSUtil_GetLong(msg, id, &l);
			samplesFinal[recursion] = l;
			sampleSumFinal += l;
			sampleSumPerRecursion[recursion] += l;
			sampleSum += l;
			id = kPOVAttrib_RadWeightR0 + recursion;
			(void)POVMSUtil_GetFloat(msg, id, &(sampleWeight[recursion]));
		}
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("  Pass ");
		for (int recursion = 0; recursion < 5; recursion ++)
			if (POVMSLongToCDouble(sampleSumPerRecursion[recursion]) > 0.5)
				tsb->printf((recursion < 4? "    Depth %i" : "   Depth 4+") , recursion);
		tsb->printf("           Total\n");
		tsb->printf("----------------------------------------------------------------------------\n");
		for(int pass = 1; pass <= 5; pass ++)
		{
			if (sampleSumPerPass[pass-1] > 0)
			{
				tsb->printf("  %i%c   ", pass, (pass < 5 ? ' ' : '+'));
				for (int recursion = 0; recursion < 5; recursion ++)
				{
					if (POVMSLongToCDouble(samples[pass-1][recursion]) > 0.5)
						tsb->printf(" %10.0f", POVMSLongToCDouble(samples[pass-1][recursion]));
					else if (POVMSLongToCDouble(sampleSumPerRecursion[recursion]) > 0.5)
						tsb->printf("          -");
				}
				tsb->printf(" %15.0f\n", POVMSLongToCDouble(sampleSumPerPass[pass-1]));
			}
		}
		if (sampleSumFinal > 0)
		{
			tsb->printf("  Final");
			for (int recursion = 0; recursion < 5; recursion ++)
			{
				if (POVMSLongToCDouble(samplesFinal[recursion]) > 0.5)
					tsb->printf(" %10.0f", POVMSLongToCDouble(samplesFinal[recursion]));
				else if (POVMSLongToCDouble(sampleSumPerRecursion[recursion]) > 0.5)
					tsb->printf("          -");
			}
			tsb->printf(" %15.0f\n", POVMSLongToCDouble(sampleSumFinal));
		}
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("  Total");
		for (int recursion = 0; recursion < 5; recursion ++)
		{
			if (POVMSLongToCDouble(sampleSumPerRecursion[recursion]) > 0.5)
				tsb->printf(" %10.0f", POVMSLongToCDouble(sampleSumPerRecursion[recursion]));
		}
		tsb->printf(" %15.0f\n", POVMSLongToCDouble(sampleSum));
		tsb->printf("  Weight");
		for (int recursion = 0; recursion < 5; recursion ++)
		{
			if (sampleWeight[recursion] > 1e-6)
				tsb->printf("%10.3f ", sampleWeight[recursion]);
			else if (POVMSLongToCDouble(sampleSumPerRecursion[recursion]) > 0.5)
				tsb->printf("         - ");
		}
		tsb->printf("\n");
	}

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_PhotonsShot, &l);
	if(POVMSLongToCDouble(l) > 0.5)
	{
		tsb->printf("----------------------------------------------------------------------------\n");
		tsb->printf("Number of photons shot: %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_PhotonsStored, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Surface photons stored: %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_MediaPhotonsStored, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Media photons stored:   %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_GlobalPhotonsStored, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Global photons stored:  %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_PhotonsPriQInsert, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Priority queue insert:  %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_PhotonsPriQRemove, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Priority queue remove:  %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_GatherPerformedCnt, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Gather function called: %15.0f\n", POVMSLongToCDouble(l));
		(void)POVMSUtil_GetLong(msg, kPOVAttrib_GatherExpandedCnt, &l);
		if(POVMSLongToCDouble(l) > 0.5)
			tsb->printf("Gather radius expanded: %15.0f\n", POVMSLongToCDouble(l));
	}

	tsb->printf("----------------------------------------------------------------------------\n");

	(void)POVMSUtil_GetLong(msg, kPOVAttrib_MinAlloc, &l);
	if (l > 0) // don't count allocs of 0 anyhow
		tsb->printf("Smallest Alloc:     %15.0f bytes\n", POVMSLongToCDouble(l));
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_MaxAlloc, &l);
	if (l > 0)
		tsb->printf("Largest  Alloc:     %15.0f bytes\n", POVMSLongToCDouble(l));

	l = 0;
	l2 = 0;
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_CallsToAlloc, &l);
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_CallsToFree, &l2);
	if(POVMSLongToCDouble(l) > 0.5)
		tsb->printf("Total Alloc calls:  %15.0f         Free calls:%15.0f\n", POVMSLongToCDouble(l), POVMSLongToCDouble(l2));

	l = 0;
	(void)POVMSUtil_GetLong(msg, kPOVAttrib_PeakMemoryUsage, &l);
	if(POVMSLongToCDouble(l) > 0.5)
		tsb->printf("Peak memory used:   %15.0f bytes\n", POVMSLongToCDouble(l));

	tsb->printf("----------------------------------------------------------------------------\n");

	POVMSObject_Delete(msg);
}

void Warning(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;

	FileMessage(tsb, WARNING_STREAM, msg);

	POVMSObject_Delete(msg);
}

void Error(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;

	FileMessage(tsb, WARNING_STREAM, msg);

	POVMSObject_Delete(msg);
}

void FatalError(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POVMSObject msgobj(cppmsg());
	POVMSObjectPtr msg = &msgobj;
	int ret = kNoErr;
	int l = 0;
	int s = 0;

	if(ret == kNoErr)
		FileMessage(tsb, FATAL_STREAM, msg);

	if(ret != kNoErr)
		throw POV_EXCEPTION_CODE(ret);

	POVMSObject_Delete(msg);
}

void FileMessage(TextStreamBuffer *tsb, int stream, POVMSObjectPtr msg)
{
	const int Num_Echo_Lines = 5; // TODO FIXME
	const int output_string_buffer_size = 1024; // TODO FIXME
	char output_string_buffer[output_string_buffer_size]; // TODO FIXME
	UCS2 filename[2048];

	POVMSLong ll = 0;
	int ret = kNoErr;
	int l = sizeof(filename);

	if(POVMSUtil_GetUCS2String(msg, kPOVAttrib_FileName, filename, &l) == kNoErr)
	{
		// TODO FIXME: we ought to support UCS2 string output.
		POVMSUCS2String fn(filename);
		string asciiFN(UCS2toASCIIString(fn));

		if((POVMSUtil_GetLong(msg, kPOVAttrib_Line, &ll) == kNoErr) && ((stream == WARNING_STREAM) || (stream == FATAL_STREAM)))
		{
			if ((asciiFN.empty() == false) && (ll > 0))
				tsb->printf("File: %s  Line: %ld\n", asciiFN.c_str(), ll);
		}
		if(((POVMSUtil_GetLong(msg, kPOVAttrib_FilePosition, &ll) == kNoErr) && (Num_Echo_Lines > 0)) && (stream == FATAL_STREAM))
		{
			tsb->printf("File Context (%d lines):\n", Num_Echo_Lines);
			tsb->printfile(asciiFN.c_str(), ll, -Num_Echo_Lines);
			tsb->printf("\n");
		}
	}

	l = output_string_buffer_size;
	output_string_buffer[0] = 0;
	ret = POVMSUtil_GetString(msg, kPOVAttrib_EnglishText, output_string_buffer, &l);
	if(ret == kNoErr)
		tsb->printf("%s\n", output_string_buffer);

	if(ret != kNoErr)
		throw POV_EXCEPTION_CODE(ret);
}

const char *GetOptionSwitchString(POVMSObjectPtr msg, POVMSType key, bool defaultstate)
{
	POVMSBool b = false;

	if(POVMSUtil_GetBool(msg, key, &b) != kNoErr)
		b = defaultstate;

	if(b == true)
		return ".On ";

	return ".Off";
}

void ParserTime(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POV_LONG i = 0;
	int msec = 0;
	int sec = 0;

	tsb->printf("Parser Time\n");

	POVMS_Object parseTime;
	POVMS_Object boundingTime;

	if(cppmsg.Exist(kPOVAttrib_ParseTime))
	{
		cppmsg.Get(kPOVAttrib_ParseTime, parseTime);
		i = parseTime.TryGetLong(kPOVAttrib_RealTime, 0);
		sec = int(i / (POV_LONG)(1000));
		msec = int(i % (POV_LONG)(1000));
		tsb->printf("  Parse Time:     %3d hours %2d minutes %2d seconds (%d.%03d seconds)\n", int(sec / 3600), int((sec / 60) % 60), int(sec % 60), sec, msec);
		if(parseTime.Exist(kPOVAttrib_CPUTime) == true)
		{
			i = parseTime.TryGetLong(kPOVAttrib_CPUTime, 0);
			sec = int(i / (POV_LONG)(1000));
			msec = int(i % (POV_LONG)(1000));
			tsb->printf("              using %d thread(s) with %d.%03d CPU-seconds total\n", int(parseTime.TryGetInt(kPOVAttrib_TimeSamples, 1)), sec, msec);
		}
		else
			tsb->printf("              using %d thread(s)\n", int(parseTime.TryGetInt(kPOVAttrib_TimeSamples, 1)));
	}
	else
		tsb->printf("  Parse Time:       No parsing\n");

	if(cppmsg.Exist(kPOVAttrib_BoundingTime))
	{
		cppmsg.Get(kPOVAttrib_BoundingTime, boundingTime);
		i = boundingTime.TryGetLong(kPOVAttrib_RealTime, 0);
		sec = int(i / (POV_LONG)(1000));
		msec = int(i % (POV_LONG)(1000));
		tsb->printf("  Bounding Time:  %3d hours %2d minutes %2d seconds (%d.%03d seconds)\n", int(sec / 3600), int((sec / 60) % 60), int(sec % 60), sec, msec);
		if(boundingTime.Exist(kPOVAttrib_CPUTime) == true)
		{
			i = boundingTime.TryGetLong(kPOVAttrib_CPUTime, 0);
			sec = int(i / (POV_LONG)(1000));
			msec = int(i % (POV_LONG)(1000));
			tsb->printf("              using %d thread(s) with %d.%03d CPU-seconds total\n", int(boundingTime.TryGetInt(kPOVAttrib_TimeSamples, 1)), sec, msec);
		}
		else
			tsb->printf("              using %d thread(s)\n", int(boundingTime.TryGetInt(kPOVAttrib_TimeSamples, 1)));
	}
	else
		tsb->printf("  Bounding Time:    No bounding\n");
}

void RenderTime(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	POV_LONG i = 0;
	int msec = 0;
	int sec = 0;

	tsb->printf("Render Time:\n");

	POVMS_Object photonTime;
	POVMS_Object radiosityTime;
	POVMS_Object renderTime;

	if(cppmsg.Exist(kPOVAttrib_PhotonTime))
	{
		cppmsg.Get(kPOVAttrib_PhotonTime, photonTime);
		i = photonTime.TryGetLong(kPOVAttrib_RealTime, 0);
		sec = int(i / (POV_LONG)(1000));
		msec = int(i % (POV_LONG)(1000));
		tsb->printf("  Photon Time:    %3d hours %2d minutes %2d seconds (%d.%03d seconds)\n", int(sec / 3600), int((sec / 60) % 60), int(sec % 60), sec, msec);
		if(photonTime.Exist(kPOVAttrib_CPUTime) == true)
		{
			i = photonTime.TryGetLong(kPOVAttrib_CPUTime, 0);
			sec = int(i / (POV_LONG)(1000));
			msec = int(i % (POV_LONG)(1000));
			tsb->printf("              using %d thread(s) with %d.%03d CPU-seconds total\n", int(photonTime.TryGetInt(kPOVAttrib_TimeSamples, 1)), sec, msec);
		}
		else
			tsb->printf("              using %d thread(s)\n", int(photonTime.TryGetInt(kPOVAttrib_TimeSamples, 1)));
	}
	else
		tsb->printf("  Photon Time:      No photons\n");

	if(cppmsg.Exist(kPOVAttrib_RadiosityTime))
	{
		cppmsg.Get(kPOVAttrib_RadiosityTime, radiosityTime);
		i = radiosityTime.TryGetLong(kPOVAttrib_RealTime, 0);
		sec = int(i / (POV_LONG)(1000));
		msec = int(i % (POV_LONG)(1000));
		tsb->printf("  Radiosity Time: %3d hours %2d minutes %2d seconds (%d.%03d seconds)\n", int(sec / 3600), int((sec / 60) % 60), int(sec % 60), sec, msec);
		if(radiosityTime.Exist(kPOVAttrib_CPUTime) == true)
		{
			i = radiosityTime.TryGetLong(kPOVAttrib_CPUTime, 0);
			sec = int(i / (POV_LONG)(1000));
			msec = int(i % (POV_LONG)(1000));
			tsb->printf("              using %d thread(s) with %d.%03d CPU-seconds total\n", int(radiosityTime.TryGetInt(kPOVAttrib_TimeSamples, 1)), sec, msec);
		}
		else
			tsb->printf("              using %d thread(s)\n", int(radiosityTime.TryGetInt(kPOVAttrib_TimeSamples, 1)));
	}
	else
		tsb->printf("  Radiosity Time:   No radiosity\n");

	if(cppmsg.Exist(kPOVAttrib_TraceTime))
	{
		cppmsg.Get(kPOVAttrib_TraceTime, renderTime);
		i = renderTime.TryGetLong(kPOVAttrib_RealTime, 0);
		sec = int(i / (POV_LONG)(1000));
		msec = int(i % (POV_LONG)(1000));
		tsb->printf("  Trace Time:     %3d hours %2d minutes %2d seconds (%d.%03d seconds)\n", int(sec / 3600), int((sec / 60) % 60), int(sec % 60), sec, msec);
		if(renderTime.Exist(kPOVAttrib_CPUTime) == true)
		{
			i = renderTime.TryGetLong(kPOVAttrib_CPUTime, 0);
			sec = int(i / (POV_LONG)(1000));
			msec = int(i % (POV_LONG)(1000));
			tsb->printf("              using %d thread(s) with %d.%03d CPU-seconds total\n", int(renderTime.TryGetInt(kPOVAttrib_TimeSamples, 1)), sec, msec);
		}
		else
			tsb->printf("              using %d thread(s)\n", int(renderTime.TryGetInt(kPOVAttrib_TimeSamples, 1)));
	}
	else
		tsb->printf("  Trace Time:       No trace\n");
}

void DebugInfo(POVMS_Object& cppmsg, TextStreamBuffer *tsb)
{
	std::string str = cppmsg.TryGetString(kPOVAttrib_EnglishText, "<Error retrieving debug output>");
	tsb->printf("%s\n", str.c_str());
}

string GetProgressTime(POVMS_Object& obj, POVMSType key)
{
	int sec = int(obj.TryGetLong(kPOVAttrib_RealTime, 0) / (POV_LONG)(1000));
	char buffer[32];

	sprintf(buffer, "%3d:%02d:%02d", int(sec / 3600), int((sec / 60) % 60), int(sec % 60));

	return string(buffer);
}

#if 0
#pragma mark -
#endif

void RenderDone(TextStreamBuffer *tsb, POVMSObjectPtr msg)
{/*
	POVMSObject object;
	int ret = 0;
	int i = 0;

	ret = POVMSObject_Get(msg, &object, kPOVAttrib_AnimationTime);
	if(ret == kNoErr)
		tsb->printf("Total Scene Processing Times\n");

	if(ret == kNoErr)
		ret = POVMSUtil_GetInt(&object, kPOVAttrib_ParseTime, &i);
	if(ret == kNoErr)
		tsb->printf("  Parse Time:  %3d hours %2d minutes %2d seconds (%d seconds)\n", (int)(i / 3600), (int)((i / 60) % 60), (int)(i % 60), (int)i);

	if(ret == kNoErr)
		ret = POVMSUtil_GetInt(&object, kPOVAttrib_PhotonTime, &i);
	if(ret == kNoErr)
		tsb->printf("  Photon Time: %3d hours %2d minutes %2d seconds (%d seconds)\n", (int)(i / 3600), (int)((i / 60) % 60), (int)(i % 60), (int)i);

	if(ret == kNoErr)
		ret = POVMSUtil_GetInt(&object, kPOVAttrib_TraceTime, &i);
	if(ret == kNoErr)
		tsb->printf("  Render Time: %3d hours %2d minutes %2d seconds (%d seconds)\n", (int)(i / 3600), (int)((i / 60) % 60), (int)(i % 60), (int)i);

	if(ret == kNoErr)
		ret = POVMSUtil_GetInt(&object, kPOVAttrib_TotalTime, &i);
	if(ret == kNoErr)
		tsb->printf("  Total Time:  %3d hours %2d minutes %2d seconds (%d seconds)\n", (int)(i / 3600), (int)((i / 60) % 60), (int)(i % 60), (int)i);

	(void)POVMSObject_Delete(&object);

	if(ret != kNoErr)
		throw POV_EXCEPTION_CODE(ret);*/
}

void Progress(TextStreamBuffer *tsb, POVMSObjectPtr msg)
{/*
	POVMSLong ll = 0;
	POVMSBool b = false;
	int ret = kNoErr;
	int l = 0;
	int s = 0;

//	Flush(DEBUG_STREAM);

	ret = POVMSUtil_GetBool(msg, kPOVAttrib_ProgressStatus, &b);
	if(ret == kNoErr)
		ret = POVMSUtil_GetInt(msg, kPOVAttrib_TotalTime, &s);
	if(ret == kNoErr)
	{
		l = 80;

		if(b == false)
		{
			ret = POVMSUtil_GetString(msg, kPOVAttrib_EnglishText, status_string_buffer, &l);
			if(ret == kNoErr)
				tsb->printf("\n%3d:%02d:%02d %s", (int)(s / 3600), (int)((s / 60) % 60), (int)(s % 60), status_string_buffer);
		}
		else // if(opts.Options & VERBOSE) // Should this be part of verbose reporting only or not? I really don't know which way would be better... [trf]
		{
			(void)POVMSUtil_GetString(msg, kPOVAttrib_EnglishText, status_string_buffer, &l);
			tsb->printf("\r%3d:%02d:%02d %s", (int)(s / 3600), (int)((s / 60) % 60), (int)(s % 60), status_string_buffer);
		}

// FIXME		if(opts.Options & VERBOSE)
		{
			// animation frame progress
			if(POVMSUtil_GetInt(msg, kPOVAttrib_FrameCount, &l) == kNoErr)
			{
				if(POVMSUtil_GetInt(msg, kPOVAttrib_AbsoluteCurFrame, &s) == kNoErr)
					tsb->printf(" %d of %d", s, l);
			}
			// parsing progress
			else if((POVMSUtil_GetLong(msg, kPOVAttrib_CurrentTokenCount, &ll) == kNoErr) && (ll > 0))
			{
				tsb->printf(" %ldK tokens", long(((POV_LONG)(ll))/1000));
			}
			// rendering progress
			else if(POVMSUtil_GetInt(msg, kPOVAttrib_CurrentLine, &l) == kNoErr)
			{
				if(POVMSUtil_GetInt(msg, kPOVAttrib_LineCount, &s) == kNoErr)
					tsb->printf(" line %d of %d", l, s);
				if(POVMSUtil_GetInt(msg, kPOVAttrib_MosaicPreviewSize, &l) == kNoErr)
					tsb->printf(" at %dx%d", l, l);
				if(POVMSUtil_GetInt(msg, kPOVAttrib_SuperSampleCount, &l) == kNoErr)
					tsb->printf(", %d supersamples", l);
				if(POVMSUtil_GetInt(msg, kPOVAttrib_RadGatherCount, &l) == kNoErr)
					tsb->printf(", %d rad. samples", l);
			}
			// photon progress
			else if(POVMSUtil_GetInt(msg, kPOVAttrib_TotalPhotonCount, &l) == kNoErr)
			{
				// sorting
				if(POVMSUtil_GetInt(msg, kPOVAttrib_CurrentPhotonCount, &s) == kNoErr)
					tsb->printf(" %d of %d", s, l);
				// shooting
				else
				{
					tsb->printf(" Photons %d", l);
					l = 0;
					(void)POVMSUtil_GetInt(msg, kPOVAttrib_PhotonXSamples, &l);
					s = 0;
					(void)POVMSUtil_GetInt(msg, kPOVAttrib_PhotonYSamples, &s);
					tsb->printf(" (sampling %dx%d)", l, s);
				}
			}
		}
	}

	if(ret != kNoErr)
		throw POV_EXCEPTION_CODE(ret);*/
}

}

}
