/*******************************************************************************
 * renderfrontend.h
 *
 * ---------------------------------------------------------------------------
 * UberPOV Raytracer version 1.37.
 * Portions Copyright 2013 Christoph Lipka.
 *
 * UberPOV 1.37 is an experimental unofficial branch of POV-Ray 3.7, and is
 * subject to the same licensing terms and conditions.
 * ---------------------------------------------------------------------------
 * 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/clipka/upov/source/frontend/renderfrontend.h $
 * $Revision: #2 $
 * $Change: 5921 $
 * $DateTime: 2013/07/18 22:48:19 $
 * $Author: clipka $
 *******************************************************************************/

#ifndef POVRAY_FRONTEND_RENDERFRONTEND_H
#define POVRAY_FRONTEND_RENDERFRONTEND_H

#include "base/types.h"
#include "base/path.h"
#include "base/povms.h"
#include "base/povmscpp.h"
#include "base/povmsgid.h"
#include "base/textstreambuffer.h"
#include "base/image/image.h"

#include "frontend/configfrontend.h"
#include "frontend/console.h"
#include "frontend/display.h"
#include "frontend/imageprocessing.h"

#undef DISPLAY // TODO FIXME - Macro from povrayold.h - remove this line once povrayold.h is gone!!! [trf]

#include <string>
#include <vector>
#include <list>
#include <map>
#include <set>

#include <boost/scoped_ptr.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

namespace pov_frontend
{

using namespace pov_base;

enum
{
	BANNER_STREAM = 0,
	STATUS_STREAM,
	DEBUG_STREAM,
	FATAL_STREAM,
	RENDER_STREAM,
	STATISTIC_STREAM,
	WARNING_STREAM,
	ALL_STREAM,
	MAX_STREAMS
};

struct SceneData
{
	enum SceneState
	{
		Scene_Unknown,
		Scene_Created,
		Scene_Parsing,
		Scene_Paused,
		Scene_Stopping,
		Scene_Ready,
		Scene_Viewing,
		Scene_Closing,
		Scene_Failed,
		Scene_Invalid
	};

	SceneState state;

	mutable shared_ptr<Console> console;

	mutable list<POVMS_Object> readfiles;
	mutable list<POVMS_Object> createdfiles;

	Path scenepath;
	Path outputpath;

	list<Path> searchpaths;

	shared_ptr<TextStreamBuffer> streams[MAX_STREAMS];

	UCS2String streamnames[MAX_STREAMS];
	bool consoleoutput[MAX_STREAMS];

	bool verbose;

	struct
	{
		int legacyGammaMode;
		// TODO FIXME - conversion from working gamma to linear should be moved to back-end
		int workingGammaType;
		float workingGamma;
	} backwardCompatibilityData;
};

struct ViewData
{
	enum ViewState
	{
		View_Unknown,
		View_Created,
		View_Rendering,
		View_Paused,
		View_Stopping,
		View_Rendered,
		View_Closing,
		View_Failed,
		View_Invalid
	};

	ViewState state;

	mutable shared_ptr<Image> image;
	mutable shared_ptr<Display> display;
	mutable shared_ptr<OStream> imageBackup;

	Path imageBackupFile;
};

}

#include "frontend/parsermessagehandler.h"
#include "frontend/filemessagehandler.h"
#include "frontend/rendermessagehandler.h"
#include "frontend/imagemessagehandler.h"

namespace pov_frontend
{

using namespace pov_base;

namespace Message2Console
{

void ParserStatistics(POVMS_Object& cppmsg, TextStreamBuffer *tsb);
void RenderStatistics(POVMS_Object& cppmsg, TextStreamBuffer *tsb);

void ParserOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb);
void RenderOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb);
void OutputOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb);
void AnimationOptions(POVMS_Object& cppmsg, TextStreamBuffer *tsb);

void Warning(POVMS_Object& cppmsg, TextStreamBuffer *tsb);
void Error(POVMS_Object& cppmsg, TextStreamBuffer *tsb);
void FatalError(POVMS_Object& cppmsg, TextStreamBuffer *tsb);

void ParserTime(POVMS_Object& cppmsg, TextStreamBuffer *tsb);
void RenderTime(POVMS_Object& cppmsg, TextStreamBuffer *tsb);

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

string GetProgressTime(POVMS_Object& obj, POVMSType key);

}

#define RENDER_STATE_SIG "POV-Ray Render State File\0\0"
#define RENDER_STATE_VER "0001"

typedef struct
{
	unsigned char sig[28];
	unsigned char ver[4];
	unsigned char reserved[480];
} Backup_File_Header;

class RenderFrontendBase : public POVMS_MessageReceiver
{
		class Id
		{
			public:
				Id() : address(POVMSInvalidAddress), identifier(0) { }
				Id(POVMSAddress a, POVMSInt i) : address(a), identifier(i) { }
				/* operator< needed by map<> and set<>
				 * total order is required
				 * [JG] evaluation's order of expression is (ab)used:
				 *   first rank order is address, if equals, second rank is id
				 */
				bool operator<(const Id& o) const { return ((address <= o.address) && (identifier < o.identifier)); }
				// operator== needed by explicit compare
				bool operator==(const Id& o) const { return ((address == o.address) && (identifier == o.identifier)); }
				// no need of != so far
				bool operator!=(const Id& o) const { return ((address != o.address) || (identifier != o.identifier)); }
				POVMSAddress GetAddress() const { return address; }
				POVMSInt GetIdentifier() const { return identifier; }
			private:
				POVMSAddress address;
				POVMSInt identifier;
		};
	public:
		typedef Id SceneId;
		typedef Id ViewId;

		RenderFrontendBase(POVMSContext);
		virtual ~RenderFrontendBase();

		void ConnectToBackend(POVMSAddress, POVMS_Object&, POVMS_Object *, shared_ptr<Console>&);
		void DisconnectFromBackend(POVMSAddress);

		virtual shared_ptr<Console> GetConsole(SceneId) = 0;
		virtual shared_ptr<Image> GetImage(ViewId) = 0;
		virtual shared_ptr<Display> GetDisplay(ViewId) = 0;
	protected:
		set<POVMSAddress> backendaddresses;
		POVMSContext context;

		SceneId CreateScene(SceneData&, POVMSAddress, POVMS_Object&);
		void CloseScene(SceneData&, SceneId);

		void StartParser(SceneData&, SceneId, POVMS_Object&);
		void PauseParser(SceneData&, SceneId);
		void ResumeParser(SceneData&, SceneId);
		void StopParser(SceneData&, SceneId);

		ViewId CreateView(SceneData&, ViewData&, SceneId, POVMS_Object&);
		void CloseView(ViewData&, ViewId);

		void StartRender(ViewData&, ViewId, POVMS_Object&);
		void PauseRender(ViewData&, ViewId);
		void ResumeRender(ViewData&, ViewId);
		void StopRender(ViewData&, ViewId);

		void HandleMessage(POVMS_Message& msg, POVMS_Message& result, int mode);

		virtual void HandleParserMessage(SceneId, POVMSType, POVMS_Object&) = 0;
		virtual void HandleFileMessage(SceneId, POVMSType, POVMS_Object&, POVMS_Object&) = 0;
		virtual void HandleRenderMessage(ViewId, POVMSType, POVMS_Object&) = 0;
		virtual void HandleImageMessage(ViewId, POVMSType, POVMS_Object&) = 0;

		virtual void OutputFatalError(const string&, int) = 0;

		void MakeBackupPath(POVMS_Object& ropts, ViewData& vd, const Path& outputpath);
		void NewBackup(POVMS_Object& ropts, ViewData& vd, const Path& outputpath);
		void ContinueBackup(POVMS_Object& ropts, ViewData& vd, ViewId vid, POVMSInt& serial, vector<POVMSInt>& skip, const Path& outputpath);
};

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
class RenderFrontend : public RenderFrontendBase
{
		struct SceneHandler
		{
			SceneData data;

			PARSER_MH parser;
			FILE_MH file;
		};

		struct ViewHandler
		{
			ViewData data;

			RENDER_MH render;
			IMAGE_MH image;
		};
	public:
		RenderFrontend(POVMSContext ctx);
		~RenderFrontend();

		SceneId CreateScene(POVMSAddress backendaddress, POVMS_Object& obj, boost::function<Console *()> fn);
		void CloseScene(SceneId sid);

		SceneData::SceneState GetSceneState(SceneId sid);

		void StartParser(SceneId sid, POVMS_Object& obj);
		void PauseParser(SceneId sid);
		void ResumeParser(SceneId sid);
		void StopParser(SceneId sid);

		ViewId CreateView(SceneId sid, POVMS_Object& obj, shared_ptr<ImageProcessing>& imageProcessing, boost::function<Display *(unsigned int, unsigned int, GammaCurvePtr, float)> fn);
		void CloseView(ViewId vid);

		ViewData::ViewState GetViewState(ViewId vid);

		void StartRender(ViewId vid, POVMS_Object& obj);
		void PauseRender(ViewId vid);
		void ResumeRender(ViewId vid);
		void StopRender(ViewId vid);

		virtual shared_ptr<Console> GetConsole(SceneId sid);
		virtual shared_ptr<Image> GetImage(ViewId vid);
		virtual shared_ptr<Display> GetDisplay(ViewId vid);
	protected:
		virtual void HandleParserMessage(SceneId sid, POVMSType ident, POVMS_Object& msg);
		virtual void HandleFileMessage(SceneId sid, POVMSType ident, POVMS_Object& msg, POVMS_Object& result);
		virtual void HandleRenderMessage(ViewId vid, POVMSType ident, POVMS_Object& msg);
		virtual void HandleImageMessage(ViewId vid, POVMSType ident, POVMS_Object& msg);
		virtual void OutputFatalError(const string& msg, int err);
	private:
		map<SceneId, SceneHandler> scenehandler;
		map<ViewId, ViewHandler> viewhandler;

		map<SceneId, set<ViewId> > scene2views;
		map<ViewId, SceneId> view2scene;

		void GetBackwardCompatibilityData(SceneData& sd, POVMS_Object& msg);
};

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::RenderFrontend(POVMSContext ctx) :
	RenderFrontendBase(ctx)
{
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::~RenderFrontend()
{
	// nothing to do
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
RenderFrontendBase::SceneId RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::CreateScene(POVMSAddress backendaddress, POVMS_Object& obj, boost::function<Console*()> fn)
{
	SceneHandler sh;
	SceneId sid;

	try
	{
		sh.data.state = SceneData::Scene_Invalid;

		sid = RenderFrontendBase::CreateScene(sh.data, backendaddress, obj);

		sh.data.console = shared_ptr<Console>(fn());

		scenehandler[sid] = sh;
		scene2views[sid] = set<ViewId>();

		scenehandler[sid].data.console->Initialise();

		scenehandler[sid].data.state = SceneData::Scene_Created;
	}
	catch(pov_base::Exception&)
	{
		RenderFrontendBase::CloseScene(sh.data, sid);

		scenehandler.erase(sid);
		scene2views.erase(sid);

		throw;
	}

	return sid;
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::CloseScene(SceneId sid)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
	{
		SceneData& data = shi->second.data;
		if((data.state != SceneData::Scene_Created) && (data.state != SceneData::Scene_Ready) && (data.state != SceneData::Scene_Failed))
			throw POV_EXCEPTION_STRING("TODO"); // TODO FIXME

		data.state = SceneData::Scene_Closing;

		if(scene2views[sid].size() > 0)
			throw POV_EXCEPTION_STRING("TODO"); // TODO FIXME

		RenderFrontendBase::CloseScene(data, sid);

		scenehandler.erase(shi);
		scene2views.erase(sid);
	}
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
SceneData::SceneState RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::GetSceneState(SceneId sid)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
		return shi->second.data.state;
	else
		return SceneData::Scene_Unknown;
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::StartParser(SceneId sid, POVMS_Object& obj)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
	{
		RenderFrontendBase::StartParser(shi->second.data, sid, obj);
		HandleParserMessage(sid, kPOVMsgIdent_ParserOptions, obj);
	}
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::PauseParser(SceneId sid)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
		RenderFrontendBase::PauseParser(shi->second.data, sid);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::ResumeParser(SceneId sid)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
		RenderFrontendBase::ResumeParser(shi->second.data, sid);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::StopParser(SceneId sid)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
		RenderFrontendBase::StopParser(shi->second.data, sid);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
RenderFrontendBase::ViewId RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::CreateView(SceneId sid, POVMS_Object& obj, shared_ptr<ImageProcessing>& imageProcessing, boost::function<Display *(unsigned int,unsigned int,GammaCurvePtr,float)> fn)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));

	if(shi != scenehandler.end())
	{
		if(shi->second.data.state != SceneData::Scene_Ready)
			throw POV_EXCEPTION_STRING("TODO"); // TODO FIXME

		ViewHandler vh;
		ViewId vid;

		// NOTE: No method in this class should ever change options passed to
		// it as input. However, for backward compatibility and proper design,
		// this is the only way to do it at the moment.
		try
		{
			unsigned int width(obj.TryGetInt(kPOVAttrib_Width, 160));
			unsigned int height(obj.TryGetInt(kPOVAttrib_Height, 120));

			GammaCurvePtr gamma;

			// check if we need to deviate from the gamma handling mode as implied by the scene file
			switch (shi->second.data.backwardCompatibilityData.legacyGammaMode)
			{
				case kPOVList_GammaMode_None:
					// The scene didn't explicitly set assumed_gamma, and it ended up with a #version of less than 3.7,
					// which normally means no gamma correction is done whatsoever.

					if(obj.TryGetFloat(kPOVAttrib_Version, 0.0f) >= 3.7f)
					{
						// INI file or command line specify a version of 3.7 or greater, which we take to imply that
						// output gamma handling should follow the 3.7 model.

						shi->second.data.backwardCompatibilityData.legacyGammaMode = kPOVList_GammaMode_AssumedGamma37Implied;
						shi->second.data.backwardCompatibilityData.workingGammaType = DEFAULT_WORKING_GAMMA_TYPE;
						shi->second.data.backwardCompatibilityData.workingGamma = DEFAULT_WORKING_GAMMA;
						shi->second.data.console->Output("------------------------------------------------------------------------------\n"
						                                 "Warning: A version of 3.7 or greater was specified in an INI file or on the\n"
						                                 "command-line, but the scene finished parsing with a #version of 3.6x or\n"
						                                 "earlier and without assumed_gamma set. Output gamma correction is being turned\n"
						                                 "on as per the 3.7 default using an assumed_gamma default of " DEFAULT_WORKING_GAMMA_TEXT ",\n"
						                                 "rather than left off (which was the 3.6x and earlier default), because the INI\n"
						                                 "file or command-line specified version directive takes precedence.");
					}
					break;

				case kPOVList_GammaMode_AssumedGamma36:
				case kPOVList_GammaMode_AssumedGamma37:
				case kPOVList_GammaMode_AssumedGamma37Implied:
					// The scene explicitly set assumed_gamma, and/or ended up with a #version of 3.7 or higher,
					// which normally means output gamma handling should follow the 3.6 / 3.7 assumed_gamma model
					// (which differs only in input image gamma handling, which is irrelevant here).
					break;

				default:
					throw POV_EXCEPTION_STRING("Unknown gamma handling mode in CreateView()");
			}

			int     dispGammaType   = obj.TryGetInt   (kPOVAttrib_DisplayGammaType, DEFAULT_DISPLAY_GAMMA_TYPE);
			float   dispGamma       = obj.TryGetFloat (kPOVAttrib_DisplayGamma,     DEFAULT_DISPLAY_GAMMA);

			switch (shi->second.data.backwardCompatibilityData.legacyGammaMode)
			{
				case kPOVList_GammaMode_None:
					// Turn gamma handling off for both display and file output
					obj.SetInt      (kPOVAttrib_FileGammaType,      dispGammaType);
					obj.SetFloat    (kPOVAttrib_FileGamma,          dispGamma);
					obj.SetInt      (kPOVAttrib_WorkingGammaType,   dispGammaType);
					obj.SetFloat    (kPOVAttrib_WorkingGamma,       dispGamma);
					gamma = NeutralGammaCurve::Get();
					break;

				case kPOVList_GammaMode_AssumedGamma36:
				case kPOVList_GammaMode_AssumedGamma37:
				case kPOVList_GammaMode_AssumedGamma37Implied:
					// Adjust display gamma based on assumed_gamma and Display_Gamma
					// (note that here we also pass the information we got from the back-end onward to the front-end)
					obj.SetInt      (kPOVAttrib_WorkingGammaType,   shi->second.data.backwardCompatibilityData.workingGammaType);
					obj.SetFloat    (kPOVAttrib_WorkingGamma,       shi->second.data.backwardCompatibilityData.workingGamma);
					gamma = TranscodingGammaCurve::Get(GetGammaCurve(shi->second.data.backwardCompatibilityData.workingGammaType, shi->second.data.backwardCompatibilityData.workingGamma),
					                                   GetGammaCurve(dispGammaType, dispGamma));
					break;

				default:
					throw POV_EXCEPTION_STRING("Unknown gamma handling mode in CreateView()");
			}

			float glareDesaturation = obj.TryGetFloat(kPOVAttrib_GlareDesaturation, 0.0);

			vh.data.state = ViewData::View_Invalid;

			vid = RenderFrontendBase::CreateView(shi->second.data, vh.data, sid, obj);

			if(obj.TryGetBool(kPOVAttrib_OutputToFile, true))
			{
				if (imageProcessing == NULL)
					throw POV_EXCEPTION(kNullPointerErr, "Internal error: output to file is set, but no ImageProcessing object supplied");
				shared_ptr<Image> img(imageProcessing->GetImage());
				if(img != NULL)
				{
					if((img->GetWidth() != width) || (img->GetHeight() != height))
						throw POV_EXCEPTION_STRING("Invalid partial rendered image. Image size does not match!");

					vh.data.image = img;
				}
				else
					vh.data.image = shared_ptr<Image>(Image::Create(width, height, Image::RGBFT_Float));
			}

			if(obj.TryGetBool(kPOVAttrib_Display, true) == true)
				vh.data.display = shared_ptr<Display>(fn(width, height, gamma, glareDesaturation));

			viewhandler[vid] = vh;
			view2scene[vid] = sid;
			scene2views[sid].insert(vid);

			if(viewhandler[vid].data.display != NULL)
				viewhandler[vid].data.display->Initialise();

			shi->second.data.state = SceneData::Scene_Viewing;
			viewhandler[vid].data.state = ViewData::View_Created;
		}
		catch(pov_base::Exception&)
		{
			RenderFrontendBase::CloseView(vh.data, vid);

			scene2views[view2scene[vid]].erase(vid);
			viewhandler.erase(vid);
			view2scene.erase(vid);

			throw;
		}

		return vid;
	}
	else
		throw POV_EXCEPTION_STRING("TODO"); // FIXME TODO
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::CloseView(ViewId vid)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));

	if(vhi != viewhandler.end())
	{
		ViewData& data = vhi->second.data;
		if((data.state != ViewData::View_Created) && (data.state != ViewData::View_Rendered) && (data.state != ViewData::View_Failed))
			throw POV_EXCEPTION_STRING("TODO"); // TODO FIXME

		RenderFrontendBase::CloseView(data, vid);

		scene2views[view2scene[vid]].erase(vid);
		if(scene2views[view2scene[vid]].empty() == true)
		{
			typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(view2scene[vid]));
			shi->second.data.state = SceneData::Scene_Ready;
		}

		viewhandler.erase(vhi);
		view2scene.erase(vid);
	}
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
ViewData::ViewState RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::GetViewState(ViewId vid)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
		return vhi->second.data.state;
	else
		return ViewData::View_Unknown;
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::StartRender(ViewId vid, POVMS_Object& obj)
{
	bool continueOK = false;

	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
	{
		Path outputpath(obj.TryGetUCS2String(kPOVAttrib_OutputPath, ""));
		UCS2String filename = obj.TryGetUCS2String(kPOVAttrib_OutputFile, "");
		string fn(UCS2toASCIIString(filename));
		bool to_stdout = fn == "-" || fn == "stdout" || fn == "stderr";

		if(obj.TryGetBool(kPOVAttrib_ContinueTrace, false) == true)
		{
			if (to_stdout)
				if(obj.TryGetBool(kPOVAttrib_BackupTrace, true))
					throw POV_EXCEPTION(kCannotHandleRequestErr, "Cannot continue trace if output to STDOUT or STDERR is specified");

			// File_Length returns -1 if the file doesn't exist
			if ((filename.length() == 0) || (GetFileLength(filename.c_str()) <= 0))
			{
				if(obj.TryGetBool(kPOVAttrib_BackupTrace, true) == true)
				{
					try
					{
						POVMSInt serial;
						vector<POVMSInt> skip;

						ContinueBackup(obj, vhi->second.data, vid, serial, skip, outputpath);

						obj.SetInt(kPOVAttrib_PixelId, serial);
						if(skip.empty() == false)
							obj.SetIntVector(kPOVAttrib_PixelSkipList, skip);
						continueOK = true;
					}
					catch(pov_base::Exception&)
					{
						vhi->second.data.imageBackup.reset();
					}
				}
			}
			else
			{
				// TODO: we need to check to see if the image file is of an appropriate
				//       size and format. if so we must skip the entire render.
				//       this will more or less duplicate the pre-3.7 behaviour, except
				//       that those versions would first load the image and then display
				//       it if +d was set. I am not sure if it's important to replicate
				//       this behaviour, however from a user's point of view it might be
				//       confusing if a continue render finishes with nothing displayed.
				//       so we may need to add this.

				// Note that kImageAlreadyRenderedErr isn't really an 'error' per se, but
				// it is a clean way of telling the frontend not to proceed with rendering.
				// (I thought of making it a non-fatal error but that implies rendering
				// should proceed).
				throw POV_EXCEPTION_CODE(kImageAlreadyRenderedErr);
			}
		}

		// Logic: if the continue attempt above failed, or was not attempted, we attempt
		//        to create a render state ('backup') file, but only if output to file
		//        is on and is not to stdout.
		if(continueOK == false)
		{
			if(to_stdout == false && obj.TryGetBool(kPOVAttrib_BackupTrace, obj.TryGetBool(kPOVAttrib_OutputToFile, true)) == true)
			{
				try
				{
					NewBackup(obj, vhi->second.data, outputpath);
				}
				catch(pov_base::Exception&)
				{
					vhi->second.data.imageBackup.reset ();
					throw;
				}
			}
		}

		RenderFrontendBase::StartRender(vhi->second.data, vid, obj);
		HandleRenderMessage(vid, kPOVMsgIdent_RenderOptions, obj);
	}
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::PauseRender(ViewId vid)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
		RenderFrontendBase::PauseRender(vhi->second.data, vid);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::ResumeRender(ViewId vid)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
		RenderFrontendBase::ResumeRender(vhi->second.data, vid);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::StopRender(ViewId vid)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
		RenderFrontendBase::StopRender(vhi->second.data, vid);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
shared_ptr<Console> RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::GetConsole(SceneId sid)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
		return shi->second.data.console;
	else
		return shared_ptr<Console>();
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
shared_ptr<Image> RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::GetImage(ViewId vid)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
		return vhi->second.data.image;
	else
		return shared_ptr<Image>();
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
shared_ptr<Display> RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::GetDisplay(ViewId vid)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
		return vhi->second.data.display;
	else
		return shared_ptr<Display>();
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::HandleParserMessage(SceneId sid, POVMSType ident, POVMS_Object& msg)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
	{
		if(ident == kPOVMsgIdent_Done)
		{
			GetBackwardCompatibilityData(shi->second.data, msg);
			shi->second.data.state = SceneData::Scene_Ready;
		}
		else if(ident == kPOVMsgIdent_Failed)
		{
			string str("Fatal error in parser: ");
			str += msg.TryGetString(kPOVAttrib_EnglishText, "Unknown failure!");
			shi->second.data.console->Output(str);
			shi->second.data.state = SceneData::Scene_Failed;
		}
		else
			shi->second.parser.HandleMessage(shi->second.data, ident, msg);
	}
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::HandleFileMessage(SceneId sid, POVMSType ident, POVMS_Object& msg, POVMS_Object& result)
{
	typename map<SceneId, SceneHandler>::iterator shi(scenehandler.find(sid));
	if(shi != scenehandler.end())
		shi->second.file.HandleMessage(shi->second.data, ident, msg, result);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::HandleRenderMessage(ViewId vid, POVMSType ident, POVMS_Object& msg)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
	{
		SceneData& sceneData(scenehandler[view2scene[vid]].data);

		if(ident == kPOVMsgIdent_Done)
		{
			vhi->second.data.state = ViewData::View_Rendered;

			// close the state file if it's open
			if(vhi->second.data.imageBackup != NULL)
			{
				vhi->second.data.imageBackup.reset();
				POV_UCS2_REMOVE(vhi->second.data.imageBackupFile().c_str());
			}
		}
		else if(ident == kPOVMsgIdent_Failed)
		{
			string str("Fatal error in renderer: ");
			str += msg.TryGetString(kPOVAttrib_EnglishText, "Unknown failure!");
			sceneData.console->Output(str);
			vhi->second.data.state = ViewData::View_Failed;

			// close the state file if it's open
			if(vhi->second.data.imageBackup != NULL)
				vhi->second.data.imageBackup.reset();
		}
		else
			vhi->second.render.HandleMessage(sceneData, vhi->second.data, ident, msg);
	}
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::HandleImageMessage(ViewId vid, POVMSType ident, POVMS_Object& msg)
{
	typename map<ViewId, ViewHandler>::iterator vhi(viewhandler.find(vid));
	if(vhi != viewhandler.end())
		vhi->second.image.HandleMessage(scenehandler[view2scene[vid]].data, vhi->second.data, ident, msg);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::OutputFatalError(const string& msg, int err)
{
	// TODO FIXME  CONSOLE::OutputFatalError(msg, err);
}

template<class PARSER_MH, class FILE_MH, class RENDER_MH, class IMAGE_MH>
void RenderFrontend<PARSER_MH, FILE_MH, RENDER_MH, IMAGE_MH>::GetBackwardCompatibilityData(SceneData& sd, POVMS_Object& msg)
{
	sd.backwardCompatibilityData.legacyGammaMode = msg.TryGetInt(kPOVAttrib_LegacyGammaMode, kPOVList_GammaMode_None); // TODO FIXME - default shouldn't be hard-coded in here.
	sd.backwardCompatibilityData.workingGammaType = msg.TryGetInt(kPOVAttrib_WorkingGammaType, DEFAULT_WORKING_GAMMA_TYPE);
	sd.backwardCompatibilityData.workingGamma = msg.TryGetFloat(kPOVAttrib_WorkingGamma, DEFAULT_WORKING_GAMMA);
}

namespace Message2TSB
{
	void InitInfo(TextStreamBuffer *, POVMSObjectPtr);
	void RenderOptions(TextStreamBuffer *, POVMSObjectPtr);
	void RenderStarted(TextStreamBuffer *, POVMSObjectPtr);
	void FrameStatistics(TextStreamBuffer *, POVMSObjectPtr);
	void ParseStatistics(TextStreamBuffer *, POVMSObjectPtr);
	void RenderStatistics(TextStreamBuffer *, POVMSObjectPtr);
	void RenderDone(TextStreamBuffer *, POVMSObjectPtr);
	void Progress(TextStreamBuffer *, POVMSObjectPtr);
	void Warning(TextStreamBuffer *, POVMSObjectPtr);
	void Error(TextStreamBuffer *, POVMSObjectPtr);
	void FatalError(TextStreamBuffer *, POVMSObjectPtr);
	void DebugInfo(TextStreamBuffer *, POVMSObjectPtr);
	void FileMessage(TextStreamBuffer *, int stream, POVMSObjectPtr);
	const char *GetOptionSwitchString(POVMSObjectPtr, POVMSType, bool defaultstate = false);
}

}

#endif // POVRAY_FRONTEND_RENDERFRONTEND_H
