/*******************************************************************************
 * processrenderoptions.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/processrenderoptions.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

#include <cctype>

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

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

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

namespace pov_frontend
{

using namespace pov_base;

/*****************************************************************************
* Local preprocessor defines
******************************************************************************/

#define kUseSpecialHandler   kPOVMSType_WildCard
#define kNoParameter         kPOVMSType_Null

/*****************************************************************************
* Local variables
******************************************************************************/

#ifdef SYS_HAS_GRAYSCALE
#define SYS_GRAYSCALE_FLAG true
#else // SYS_HAS_GRAYSCALE
#define SYS_GRAYSCALE_FLAG false
#endif // SYS_HAS_GRAYSCALE

#ifdef SYS_HAS_ALPHA
#define SYS_ALPHA_FLAG true
#else // SYS_HAS_ALPHA
#define SYS_ALPHA_FLAG false
#endif // SYS_HAS_ALPHA

/*
   Keyword table for the INI-file parser.
   The parser converts the INI-file options into a POVMS object using
   the specifications provided in this table. The first element is the
   INI-file keyword, the second element is the POVMS object attribute
   key, the third is the attribute type. Entries with a POVMS attribute
   key of 0 are superceded options that will generate a warning that the
   option no longer is supported and will generate an error in a later
   (unspecified) version of POV.
*/
struct ProcessOptions::INI_Parser_Table RenderOptions_INI_Table[] =
{
	{ "All_Console",         kPOVAttrib_AllConsole,         kPOVMSType_Bool },
	{ "All_File",            kPOVAttrib_AllFile,            kPOVMSType_UCS2String },
	{ "Antialias_Depth",     kPOVAttrib_AntialiasDepth,     kPOVMSType_Int },
	{ "Antialias",           kPOVAttrib_Antialias,          kPOVMSType_Bool },
	{ "Antialias_Threshold", kPOVAttrib_AntialiasThreshold, kPOVMSType_Float },
	{ "Antialias_Gamma",     kPOVAttrib_AntialiasGamma,     kPOVMSType_Float },
	{ "Append_File",         kPOVAttrib_AppendConsoleFiles, kPOVMSType_Bool },

	{ "Bits_Per_Color",      kPOVAttrib_BitsPerColor,       kPOVMSType_Int },
	{ "Bits_Per_Colour",     kPOVAttrib_BitsPerColor,       kPOVMSType_Int },
	{ "Bounding",            kPOVAttrib_Bounding,           kPOVMSType_Bool },
	{ "Bounding_Method",     kPOVAttrib_BoundingMethod,     kPOVMSType_Int },
	{ "Bounding_Threshold",  kPOVAttrib_BoundingThreshold,  kPOVMSType_Int },
	{ "BSP_BaseAccessCost",  kPOVAttrib_BSP_BaseAccessCost, kPOVMSType_Float },
	{ "BSP_ChildAccessCost", kPOVAttrib_BSP_ChildAccessCost,kPOVMSType_Float },
	{ "BSP_ISectCost",       kPOVAttrib_BSP_ISectCost,      kPOVMSType_Float },
	{ "BSP_MaxDepth",        kPOVAttrib_BSP_MaxDepth,       kPOVMSType_Int },
	{ "BSP_MissChance",      kPOVAttrib_BSP_MissChance,     kPOVMSType_Float },
	{ "Buffer_Output",       0,                             0 },
	{ "Buffer_Size",         0,                             0 },

	{ "Clock",               kPOVAttrib_Clock,              kPOVMSType_Float },
	{ "Clockless_Animation", kPOVAttrib_ClocklessAnimation, kPOVMSType_Bool },
	{ "Compression",         kPOVAttrib_Compression,        kPOVMSType_Int },
	{ "Continue_Trace",      kPOVAttrib_ContinueTrace,      kPOVMSType_Bool },
	{ "Create_Histogram",    0,                             0 },
	{ "Create_Ini",          kPOVAttrib_CreateIni,          kPOVMSType_UCS2String },
	{ "Cyclic_Animation",    kPOVAttrib_CyclicAnimation,    kPOVMSType_Bool },

	{ "Debug_Console",       kPOVAttrib_DebugConsole,       kPOVMSType_Bool },
	{ "Debug_File",          kPOVAttrib_DebugFile,          kPOVMSType_UCS2String },
	{ "Declare",             kPOVAttrib_Declare,            kUseSpecialHandler },
	{ "Display",             kPOVAttrib_Display,            kPOVMSType_Bool },
	{ "Display_Gamma",       kPOVAttrib_DisplayGamma,       kUseSpecialHandler },
	{ "Dither",              kPOVAttrib_Dither,             kPOVMSType_Bool },
	{ "Dither_Method",       kPOVAttrib_DitherMethod,       kUseSpecialHandler },
	{ "Draw_Vistas",         kPOVAttrib_DrawVistas,         kPOVMSType_Bool },

	{ "End_Column",          kPOVAttrib_Right,              kPOVMSType_Float },
	{ "End_Row",             kPOVAttrib_Bottom,             kPOVMSType_Float },

	{ "Fatal_Console",       kPOVAttrib_FatalConsole,       kPOVMSType_Bool },
	{ "Fatal_Error_Command", kPOVAttrib_FatalErrorCommand,  kUseSpecialHandler },
	{ "Fatal_Error_Return",  kPOVAttrib_FatalErrorCommand,  kUseSpecialHandler },
	{ "Fatal_File",          kPOVAttrib_FatalFile,          kPOVMSType_UCS2String },
	{ "Field_Render",        kPOVAttrib_FieldRender,        kPOVMSType_Bool },
	{ "File_Gamma",          kPOVAttrib_FileGamma,          kUseSpecialHandler },
	{ "Final_Clock",         kPOVAttrib_FinalClock,         kPOVMSType_Float },
	{ "Final_Frame",         kPOVAttrib_FinalFrame,         kPOVMSType_Int },
	{ "Frame_Step",          kPOVAttrib_FrameStep,          kPOVMSType_Int },

	{ "Grayscale_Output",    kPOVAttrib_GrayscaleOutput,    kPOVMSType_Bool },

	{ "Height",              kPOVAttrib_Height,             kPOVMSType_Int },
	{ "High_Reproducibility",kPOVAttrib_HighReproducibility,kPOVMSType_Bool },
	{ "Histogram_Name",      0,                             0 },
	{ "Histogram_Grid_Size", 0,                             0 },
	{ "Histogram_Type",      0,                             0 },

	{ "Initial_Clock",       kPOVAttrib_InitialClock,       kPOVMSType_Float },
	{ "Initial_Frame",       kPOVAttrib_InitialFrame,       kPOVMSType_Int },
	{ "Input_File_Name",     kPOVAttrib_InputFile,          kPOVMSType_UCS2String },
	{ "Include_Header",      kPOVAttrib_IncludeHeader,      kPOVMSType_UCS2String },
	{ "Include_Ini",         kPOVAttrib_IncludeIni,         kUseSpecialHandler },

	{ "Jitter_Amount",       kPOVAttrib_JitterAmount,       kPOVMSType_Float },
	{ "Jitter",              kPOVAttrib_Jitter,             kPOVMSType_Bool },

	{ "Library_Path",        kPOVAttrib_LibraryPath,        kUseSpecialHandler },
	{ "Light_Buffer",        kPOVAttrib_LightBuffer,        kPOVMSType_Bool },

	{ "Max_Image_Buffer_Memory", kPOVAttrib_MaxImageBufferMem, kPOVMSType_Int },

	{ "Odd_Field",           kPOVAttrib_OddField,           kPOVMSType_Bool },
	{ "Output_Alpha",        kPOVAttrib_OutputAlpha,        kPOVMSType_Bool },
	{ "Output_File_Name",    kPOVAttrib_OutputFile,         kPOVMSType_UCS2String },
	{ "Output_File_Type",    kPOVAttrib_OutputFileType,     kUseSpecialHandler },
	{ "Output_To_File",      kPOVAttrib_OutputToFile,       kPOVMSType_Bool },

	{ "Palette",             kPOVAttrib_Palette,            kUseSpecialHandler },
	{ "Pause_When_Done",     kPOVAttrib_PauseWhenDone,      kPOVMSType_Bool },
	{ "Post_Frame_Command",  kPOVAttrib_PostFrameCommand,   kUseSpecialHandler },
	{ "Post_Frame_Return",   kPOVAttrib_PostFrameCommand,   kUseSpecialHandler },
	{ "Post_Scene_Command",  kPOVAttrib_PostSceneCommand,   kUseSpecialHandler },
	{ "Post_Scene_Return",   kPOVAttrib_PostSceneCommand,   kUseSpecialHandler },
	{ "Preview_End_Size",    kPOVAttrib_PreviewEndSize,     kPOVMSType_Int },
	{ "Preview_Start_Size",  kPOVAttrib_PreviewStartSize,   kPOVMSType_Int },
	{ "Pre_Frame_Command",   kPOVAttrib_PreFrameCommand,    kUseSpecialHandler },
	{ "Pre_Frame_Return",    kPOVAttrib_PreFrameCommand,    kUseSpecialHandler },
	{ "Pre_Scene_Command",   kPOVAttrib_PreSceneCommand,    kUseSpecialHandler },
	{ "Pre_Scene_Return",    kPOVAttrib_PreSceneCommand,    kUseSpecialHandler },

	{ "Quality",             kPOVAttrib_Quality,            kPOVMSType_Int },

	{ "Radiosity_File_Name", kPOVAttrib_RadiosityFileName,  kPOVMSType_UCS2String },
	{ "Radiosity_From_File", kPOVAttrib_RadiosityFromFile,  kPOVMSType_Bool },
	{ "Radiosity_To_File",   kPOVAttrib_RadiosityToFile,    kPOVMSType_Bool },
	{ "Radiosity_Vain_Pretrace", kPOVAttrib_RadiosityVainPretrace, kPOVMSType_Bool },
	{ "Real_Time_Raytracing",kPOVAttrib_RealTimeRaytracing, kPOVMSType_Bool },
	{ "Remove_Bounds",       kPOVAttrib_RemoveBounds,       kPOVMSType_Bool },
	{ "Render_Block_Size",   kPOVAttrib_RenderBlockSize,    kPOVMSType_Int },
	{ "Render_Block_Step",   kPOVAttrib_RenderBlockStep,    kPOVMSType_Int },
	{ "Render_Console",      kPOVAttrib_RenderConsole,      kPOVMSType_Bool },
	{ "Render_File",         kPOVAttrib_RenderFile,         kPOVMSType_UCS2String },
	{ "Render_Pattern",      kPOVAttrib_RenderPattern,      kPOVMSType_Int },

	{ "Sampling_Method",     kPOVAttrib_SamplingMethod,     kPOVMSType_Int },
	{ "Split_Unions",        kPOVAttrib_SplitUnions,        kPOVMSType_Bool },
	{ "Start_Column",        kPOVAttrib_Left,               kPOVMSType_Float },
	{ "Start_Row",           kPOVAttrib_Top,                kPOVMSType_Float },
	{ "Statistic_Console",   kPOVAttrib_StatisticsConsole,  kPOVMSType_Bool },
	{ "Statistic_File",      kPOVAttrib_StatisticsFile,     kPOVMSType_UCS2String },
	{ "Subset_End_Frame",    kPOVAttrib_SubsetEndFrame,     kPOVMSType_Float },
	{ "Subset_Start_Frame",  kPOVAttrib_SubsetStartFrame,   kPOVMSType_Float },

	{ "Test_Abort_Count",    kPOVAttrib_TestAbortCount,     kPOVMSType_Int },
	{ "Test_Abort",          kPOVAttrib_TestAbort,          kPOVMSType_Bool },

	{ "User_Abort_Command",  kPOVAttrib_UserAbortCommand,   kUseSpecialHandler },
	{ "User_Abort_Return",   kPOVAttrib_UserAbortCommand,   kUseSpecialHandler },

	{ "Verbose",             kPOVAttrib_Verbose,            kPOVMSType_Bool },
	{ "Version",             kPOVAttrib_Version,            kPOVMSType_Float },
	{ "Video_Mode",          kPOVAttrib_VideoMode,          kUseSpecialHandler },
	{ "Vista_Buffer",        kPOVAttrib_VistaBuffer,        kPOVMSType_Bool },

	{ "Warning_Console",     kPOVAttrib_WarningConsole,     kPOVMSType_Bool },
	{ "Warning_File",        kPOVAttrib_WarningFile,        kPOVMSType_UCS2String },
	{ "Warning_Level",       kPOVAttrib_WarningLevel,       kPOVMSType_Int },
	{ "Width",               kPOVAttrib_Width,              kPOVMSType_Int },
	{ "Work_Threads",        kPOVAttrib_MaxRenderThreads,   kPOVMSType_Int },

	{ NULL, 0, 0 }
};

/*
   Keyword table for the command line parser.
   The parser converts the command line options into a POVMS object using
   the specifications provided in this table. The first element is the
   command keyword, the second element is the POVMS object attribute key
   of the parameter, the third is the attribute type and the last specifies
   if the +/- switch is used as boolean parameter if an attribute key is
   provided.
*/
struct ProcessOptions::Cmd_Parser_Table RenderOptions_Cmd_Table[] =
{
	{ "AG",  kPOVAttrib_AntialiasGamma,     kPOVMSType_Float,       kNoParameter },
	{ "AM",  kPOVAttrib_SamplingMethod,     kPOVMSType_Int,         kNoParameter },
	{ "A0",  kPOVAttrib_AntialiasThreshold, kPOVMSType_Float,       kPOVAttrib_Antialias },
	{ "A",   kNoParameter,                  kNoParameter,           kPOVAttrib_Antialias },

	{ "BM",  kPOVAttrib_BoundingMethod,     kPOVMSType_Int,         kNoParameter },
	{ "BS",  kPOVAttrib_RenderBlockSize,    kPOVMSType_Int,         kNoParameter },
	{ "B",   kNoParameter,                  kNoParameter,           kPOVAttrib_Bounding },

	{ "C",   kNoParameter,                  kNoParameter,           kPOVAttrib_ContinueTrace },

	{ "D",   kPOVAttrib_Display,            kUseSpecialHandler,     kPOVAttrib_Display },
	{ "D",   kNoParameter,                  kNoParameter,           kPOVAttrib_Display },

	{ "EC",  kPOVAttrib_Right,              kPOVMSType_Float,       kNoParameter },
	{ "EF0", kPOVAttrib_SubsetEndFrame,     kPOVMSType_Float,       kNoParameter },
	{ "EF",  kPOVAttrib_SubsetEndFrame,     kPOVMSType_Int,         kNoParameter },
	{ "EP",  kPOVAttrib_PreviewEndSize,     kPOVMSType_Int,         kNoParameter },
	{ "ER",  kPOVAttrib_Bottom,             kPOVMSType_Float,       kNoParameter },

	{ "F",   kPOVAttrib_OutputFileType,     kUseSpecialHandler,     kPOVAttrib_OutputToFile },
	{ "F",   kNoParameter,                  kNoParameter,           kPOVAttrib_OutputToFile },

	{ "GA",  kPOVAttrib_AllFile,            kPOVMSType_UCS2String,  kPOVAttrib_AllConsole },
	{ "GA",  kNoParameter,                  kNoParameter,           kPOVAttrib_AllConsole },
	{ "GD",  kPOVAttrib_DebugFile,          kPOVMSType_UCS2String,  kPOVAttrib_DebugConsole },
	{ "GD",  kNoParameter,                  kNoParameter,           kPOVAttrib_DebugConsole },
	{ "GF",  kPOVAttrib_FatalFile,          kPOVMSType_UCS2String,  kPOVAttrib_FatalConsole },
	{ "GF",  kNoParameter,                  kNoParameter,           kPOVAttrib_FatalConsole },
	{ "GI",  kPOVAttrib_CreateIni,          kPOVMSType_UCS2String,  kNoParameter },
	{ "GP",  kNoParameter,                  kNoParameter,           kPOVAttrib_AppendConsoleFiles },
	{ "GR",  kPOVAttrib_RenderFile,         kPOVMSType_UCS2String,  kPOVAttrib_RenderConsole },
	{ "GR",  kNoParameter,                  kNoParameter,           kPOVAttrib_RenderConsole },
	{ "GS",  kPOVAttrib_StatisticsFile,     kPOVMSType_UCS2String,  kPOVAttrib_StatisticsConsole },
	{ "GS",  kNoParameter,                  kNoParameter,           kPOVAttrib_StatisticsConsole },
	{ "GW",  kPOVAttrib_WarningFile,        kPOVMSType_UCS2String,  kPOVAttrib_WarningConsole },
	{ "GW",  kNoParameter,                  kNoParameter,           kPOVAttrib_WarningConsole },

	{ "HI",  kPOVAttrib_IncludeHeader,      kPOVMSType_UCS2String,  kNoParameter },
	{ "HR",  kNoParameter,                  kNoParameter,           kPOVAttrib_HighReproducibility },
	{ "H",   kPOVAttrib_Height,             kPOVMSType_Int,         kNoParameter },

	{ "I",   kPOVAttrib_InputFile,          kPOVMSType_UCS2String,  kNoParameter },

	{ "J",   kPOVAttrib_JitterAmount,       kPOVMSType_Float,       kPOVAttrib_Jitter },
	{ "J",   kNoParameter,                  kNoParameter,           kPOVAttrib_Jitter },

	{ "KC",  kNoParameter,                  kNoParameter,           kPOVAttrib_CyclicAnimation },
	{ "KI",  kPOVAttrib_InitialClock,       kPOVMSType_Float,       kNoParameter },
	{ "KFF", kPOVAttrib_FinalFrame,         kPOVMSType_Int,         kNoParameter },
	{ "KFI", kPOVAttrib_InitialFrame,       kPOVMSType_Int,         kNoParameter },
	{ "KF",  kPOVAttrib_FinalClock,         kPOVMSType_Float,       kNoParameter },
	{ "KLA", kNoParameter,                  kNoParameter,           kPOVAttrib_ClocklessAnimation },
	{ "K",   kPOVAttrib_Clock,              kPOVMSType_Float,       kNoParameter },

	{ "L",   kPOVAttrib_LibraryPath,        kUseSpecialHandler,     kNoParameter },

	{ "MB",  kPOVAttrib_BoundingThreshold,  kPOVMSType_Int,         kPOVAttrib_Bounding },
	{ "MB",  kNoParameter,                  kNoParameter,           kPOVAttrib_Bounding },
	{ "MI",  kPOVAttrib_MaxImageBufferMem,  kPOVMSType_Int,         kNoParameter },
	{ "MV",  kPOVAttrib_Version,            kPOVMSType_Float,       kNoParameter },

	{ "O",   kPOVAttrib_OutputFile,         kPOVMSType_UCS2String,  kNoParameter },

	{ "P",   kNoParameter,                  kNoParameter,           kPOVAttrib_PauseWhenDone },

	{ "Q",   kPOVAttrib_Quality,            kPOVMSType_Int,         kNoParameter },

	{ "RFI", kNoParameter,                  kNoParameter,           kPOVAttrib_RadiosityFromFile },
	{ "RFO", kNoParameter,                  kNoParameter,           kPOVAttrib_RadiosityToFile },
	{ "RF",  kPOVAttrib_RadiosityFileName,  kPOVMSType_UCS2String,  kNoParameter },
	{ "RS",  kPOVAttrib_RenderBlockStep,    kPOVMSType_Int,         kNoParameter }, 
	{ "RP",  kPOVAttrib_RenderPattern,      kPOVMSType_Int,         kNoParameter }, 
	{ "RTR", kNoParameter,                  kNoParameter,           kPOVAttrib_RealTimeRaytracing },
	{ "RVP", kNoParameter,                  kNoParameter,           kPOVAttrib_RadiosityVainPretrace },
	{ "R",   kPOVAttrib_AntialiasDepth,     kPOVMSType_Int,         kNoParameter },

	{ "SC",  kPOVAttrib_Left,               kPOVMSType_Float,       kNoParameter },
	{ "SF0", kPOVAttrib_SubsetStartFrame,   kPOVMSType_Float,       kNoParameter },
	{ "SF",  kPOVAttrib_SubsetStartFrame,   kPOVMSType_Int,         kNoParameter },
	{ "SP",  kPOVAttrib_PreviewStartSize,   kPOVMSType_Int,         kNoParameter },
	{ "SR",  kPOVAttrib_Top,                kPOVMSType_Float,       kNoParameter },
	{ "STP", kPOVAttrib_FrameStep,          kPOVMSType_Int,         kNoParameter },
	{ "SU",  kNoParameter,                  kNoParameter,           kPOVAttrib_SplitUnions },

	{ "TH",  kPOVAttrib_DitherMethod,       kUseSpecialHandler,     kPOVAttrib_Dither },
	{ "TH",  kNoParameter,                  kNoParameter,           kPOVAttrib_Dither },

	{ "UA",  kNoParameter,                  kNoParameter,           kPOVAttrib_OutputAlpha },
	{ "UD",  kNoParameter,                  kNoParameter,           kPOVAttrib_DrawVistas },
	{ "UF",  kNoParameter,                  kNoParameter,           kPOVAttrib_FieldRender },
	{ "UL",  kNoParameter,                  kNoParameter,           kPOVAttrib_LightBuffer },
	{ "UO",  kNoParameter,                  kNoParameter,           kPOVAttrib_OddField },
	{ "UR",  kNoParameter,                  kNoParameter,           kPOVAttrib_RemoveBounds },
	{ "UV",  kNoParameter,                  kNoParameter,           kPOVAttrib_VistaBuffer },

	{ "V",   kNoParameter,                  kNoParameter,           kPOVAttrib_Verbose },

	{ "WL",  kPOVAttrib_WarningLevel,       kPOVMSType_Int,         kNoParameter },
	{ "WT",  kPOVAttrib_MaxRenderThreads,   kPOVMSType_Int,         kNoParameter },
	{ "W",   kPOVAttrib_Width,              kPOVMSType_Int,         kNoParameter },

	{ "X",   kPOVAttrib_TestAbortCount,     kUseSpecialHandler,     kPOVAttrib_TestAbort },
	{ "X",   kNoParameter,                  kNoParameter,           kPOVAttrib_TestAbort },

	{ NULL, 0, 0, 0 }
};

// TODO FIXME - The following are hacks of some sort, no idea what they are good for. They certainly use wrong types and probably contain other mistakes [trf]
extern struct ProcessRenderOptions::Parameter_Code_Table DitherMethodTable[];

ProcessRenderOptions::ProcessRenderOptions() : ProcessOptions(RenderOptions_INI_Table, RenderOptions_Cmd_Table)
{
}

ProcessRenderOptions::~ProcessRenderOptions()
{
}

int ProcessRenderOptions::ReadSpecialOptionHandler(INI_Parser_Table *option, char *param, POVMSObjectPtr obj)
{
	POVMSAttributeList list;
	double floatval = 0.0;
	int intval = 0;
	int intval2 = 0;
	int err = kNoErr;

	switch(option->key)
	{
		case kPOVAttrib_Palette:
		case kPOVAttrib_VideoMode:
			while(isspace(*param))
				param++;
			err = POVMSUtil_SetInt(obj, option->key, tolower(*param));
			break;
		case kPOVAttrib_DitherMethod:
			while(isspace(*param))
				param++;
			err = ParseParameterCode(DitherMethodTable, param, &intval);
			if (err == kNoErr)
				err = POVMSUtil_SetInt(obj, option->key, intval);
			else
				ParseError("Unrecognized dither method '%s'.", param);
			break;
		case kPOVAttrib_OutputFileType:
			while(isspace(*param))
				param++;
			err = ParseFileType(*param, option->key, &intval);
			if (err == kNoErr)
				err = POVMSUtil_SetInt(obj, option->key, intval);
			break;
		case kPOVAttrib_IncludeIni:
		case kPOVAttrib_LibraryPath:
			POVMSAttribute attr;

			if(err == kNoErr)
			{
				// parse INI file (recursive)
				if(option->key == kPOVAttrib_IncludeIni)
					err = ParseFile(param, obj);

				// create list if it isn't there
				if(err == kNoErr)
				{
					if(POVMSObject_Exist(obj, option->key) == kFalseErr)
						err = POVMSAttrList_New(&list);
					else if(POVMSObject_Exist(obj, option->key) != kNoErr)
						err = kObjectAccessErr;
					else
						err = POVMSObject_Get(obj, &list, option->key);
				}
			}
			else
			{
				ParseError("File name or path parameter expected for option '%s', found '%s'.", option->keyword, param);
				err = kParamErr;
			}

			// add path or file to list
			if(err == kNoErr)
				err = POVMSAttr_New(&attr);
			if(err == kNoErr)
			{
				err = POVMSAttr_SetUTF8String(&attr, kPOVMSType_UCS2String, param);
				if(err == kNoErr)
					err = POVMSAttrList_Append(&list, &attr);
				else
					err = POVMSAttr_Delete(&attr);
			}
			if(err == kNoErr)
				err = POVMSObject_Set(obj, &list, option->key);
			break;
		case kPOVAttrib_Declare:
			POVMSObject decobj;

			// create list if it isn't there
			if(POVMSObject_Exist(obj, option->key) == kFalseErr)
				err = POVMSAttrList_New(&list);
			else if(POVMSObject_Exist(obj, option->key) != kNoErr)
				err = kObjectAccessErr;
			else
				err = POVMSObject_Get(obj, &list, option->key);

			// add value to list
			if(err == kNoErr)
				err = POVMSObject_New(&decobj, kPOVMSType_WildCard);
			if(err == kNoErr)
			{
				char *ptr = NULL;

				err = POVMSUtil_SetString(&decobj, kPOVAttrib_Identifier, strtok(param, "="));
				if(err == kNoErr)
				{
					ptr = strtok(NULL, "");
					if(ptr == NULL)
						err = kParseErr;
				}
				if(err == kNoErr)
				{
					if(strchr(ptr, '"') != NULL)
					{
						ptr = strchr(ptr, '"') + 1;
						strtok(ptr, "\"");
						err = POVMSUtil_SetString(&decobj, kPOVAttrib_Value, ptr);
					}
					else
						err = POVMSUtil_SetFloat(&decobj, kPOVAttrib_Value, atof(ptr));
				}
				if(err == kNoErr)
					err = POVMSAttrList_Append(&list, &decobj);
				else
					err = POVMSObject_Delete(&decobj);
			}
			if(err == kNoErr)
				err = POVMSObject_Set(obj, &list, option->key);
			break;
		case kPOVAttrib_FatalErrorCommand:
		case kPOVAttrib_PostFrameCommand:
		case kPOVAttrib_PostSceneCommand:
		case kPOVAttrib_PreFrameCommand:
		case kPOVAttrib_PreSceneCommand:
		case kPOVAttrib_UserAbortCommand:
			POVMSObject cmdobj;

			if(POVMSObject_Exist(obj, option->key) == kNoErr)
				err = POVMSObject_Get(obj, &cmdobj, option->key);
			else
				err = POVMSObject_New(&cmdobj, kPOVMSType_WildCard);
			if(toupper(*(option->keyword + strlen(option->keyword) - 1)) == 'D')
			{
				if(err == kNoErr)
					err = POVMSUtil_SetString(&cmdobj, kPOVAttrib_CommandString, param);
			}
			else
			{
				if(err == kNoErr)
				{
					int i = 0;

					if((*param == '-') || (*param == '!'))
						i = toupper(*(param + 1)); // use upper-case to indicate negation of process result
					else
						i = tolower(*param); // lower-case for normal interpretation of process result
					err = POVMSUtil_SetInt(&cmdobj, kPOVAttrib_ReturnAction, i);
				}
			}
			if(err == kNoErr)
				err = POVMSObject_Set(obj, &cmdobj, option->key);
			break;
		case kPOVAttrib_AntialiasGamma:
		case kPOVAttrib_DisplayGamma:
		case kPOVAttrib_FileGamma:
			POVMSType typeKey;
			switch (option->key)
			{
				case kPOVAttrib_AntialiasGamma:  typeKey = kPOVAttrib_AntialiasGammaType;    break;
				case kPOVAttrib_DisplayGamma:    typeKey = kPOVAttrib_DisplayGammaType;      break;
				case kPOVAttrib_FileGamma:       typeKey = kPOVAttrib_FileGammaType;         break;
			}
			floatval = atof(param);
			if (floatval == 1.0)
				intval = kPOVList_GammaType_Neutral;
			else if (floatval > 0)
				intval = kPOVList_GammaType_PowerLaw;
			else
				err = ParseGammaType(param, &intval);

			if (err == kNoErr)
				err = POVMSUtil_SetFloat(obj, option->key, fabs(floatval));
			if (err == kNoErr)
				err = POVMSUtil_SetInt(obj, typeKey, intval);
			break;
	}

	return err;
}

int ProcessRenderOptions::ReadSpecialSwitchHandler(Cmd_Parser_Table *option, char *param, POVMSObjectPtr obj, bool)
{
	int intval = 0;
	int intval2 = 0;
	int err = 0;
	char chr = 0;
	char file_type;
	bool has16BitGrayscale;

	switch(option->key)
	{
		case kPOVAttrib_Display:
			if(param[0] != '\0')
			{
				err = POVMSUtil_SetInt(obj, kPOVAttrib_VideoMode, (int)toupper(param[0]));
				if((param[1] != '\0') && (err == 0))
					err = POVMSUtil_SetInt(obj, kPOVAttrib_Palette, (int)toupper(param[1]));
			}
			break;
		case kPOVAttrib_DitherMethod:
			err = ParseParameterCode(DitherMethodTable, param, &intval);
			if (err == kNoErr)
				err = POVMSUtil_SetInt(obj, option->key, intval);
			else
				ParseError("Unrecognized dither method '%s'.", param);
			break;
		case kPOVAttrib_OutputFileType:
			err = ParseFileType(*param, option->key, &intval, &has16BitGrayscale);
			if (err == kNoErr)
			{
				err = POVMSUtil_SetInt(obj, option->key, intval);
				file_type = *param++;
			}
			if ((err == kNoErr) && (*param > ' '))
			{
				if (tolower(*param) == 'g')
				{
					if(!has16BitGrayscale)
					{
						ParseError("Grayscale not currently supported with output file format '%c'.", file_type);
						err = kParamErr;
					}
					else
					{
						if ((err = POVMSUtil_SetBool(obj, kPOVAttrib_GrayscaleOutput, true)) == kNoErr && *++param > ' ')
						{
							ParseError("Unexpected '%s' following grayscale flag in +F%c option.", param, file_type);
							err = kParamErr;
						}
					}
				}
				else if (isdigit(*param) != 0)
				{
					if (sscanf(param, "%d%n", &intval, &intval2) == 1)
					{
						if ((err = POVMSUtil_SetInt(obj, kPOVAttrib_BitsPerColor, intval)) == kNoErr)
						{
							param += intval2;
							if (*param > ' ')
							{
								ParseError("Unexpected '%s' following bits per color in +F%c option.", param, file_type);
								err = kParamErr;
							}
						}
					}
					else
					{
						ParseError("Invalid bits per color '%s' found in +F%c option.", param, file_type);
						err = kParamErr;
					}
				}
				else
				{
					ParseError("Invalid modifier '%s' following +F%c option.", param, file_type);
					err = kParamErr;
				}
			}
			break;
		case kPOVAttrib_LibraryPath:
			POVMSAttributeList list;
			POVMSAttribute attr;

			if(err == kNoErr)
			{
				// create list if it isn't there
				if(POVMSObject_Exist(obj, option->key) == kFalseErr)
					err = POVMSAttrList_New(&list);
				else if(POVMSObject_Exist(obj, option->key) != kNoErr)
					err = kObjectAccessErr;
				else
					err = POVMSObject_Get(obj, &list, option->key);
			}
			else
			{
				ParseError("File name or path parameter expected for switch '%s', found '%s'.", option->command, param);
				err = kParamErr;
			}

			// add path or file to list
			if(err == kNoErr)
				err = POVMSAttr_New(&attr);
			if(err == kNoErr)
			{
				err = POVMSAttr_SetUTF8String(&attr, kPOVMSType_UCS2String, param);
				if(err == kNoErr)
					err = POVMSAttrList_Append(&list, &attr);
				else
					err = POVMSAttr_Delete(&attr);
			}
			if(err == kNoErr)
				err = POVMSObject_Set(obj, &list, option->key);
			break;
		case kPOVAttrib_TestAbortCount:
			if((*param) == 0)
				break;
			if(sscanf(param, "%d", &intval) == 1)
				err = POVMSUtil_SetInt(obj, option->key, intval);
			else
			{
				ParseError("No or integer parameter expected for switch '%s', found '%s'.", option->command, param);
				err = kParamErr;
			}
			break;
	}

	return err;
}

int ProcessRenderOptions::WriteSpecialOptionHandler(INI_Parser_Table *option, POVMSObjectPtr obj, OTextStream *file)
{
	POVMSAttributeList list;
	POVMSFloat floatval;
	POVMSInt intval;
	int err = 0;
	int l;
	int i,imax;
	POVMSAttribute item;
	char *bufptr;
	char chr;

	switch(option->key)
	{
		case kPOVAttrib_Palette:
		case kPOVAttrib_VideoMode:
			if(POVMSUtil_GetInt(obj, option->key, &intval) == 0)
			{
				chr = intval;
				if(chr > 32)
					file->printf("%s=%c\n", option->keyword, chr);
			}
			break;
		case kPOVAttrib_OutputFileType:
			if(POVMSUtil_GetInt(obj, option->key, &intval) == 0)
			{
				chr = UnparseFileType(intval);
				if(chr > 32)
					file->printf("%s=%c\n", option->keyword, chr);
			}
			break;
		case kPOVAttrib_IncludeIni:
			break;
		case kPOVAttrib_Declare:
			POVMSObject decobj;

			err = POVMSObject_Get(obj, &list, option->key);
			if(err != 0)
				break;

			l = 0;
			err = POVMSAttrList_Count(&list, &l);
			if(err != 0)
				break;
			if(l == 0)
				break;

			imax = l;
			for(i = 1; i <= imax; i++)
			{
				err = POVMSAttrList_GetNth(&list, i, &decobj);
				if(err == 0)
					err = POVMSObject_Get(&decobj, &item, kPOVAttrib_Identifier);
				if(err == 0)
				{
					l = 0;
					err = POVMSAttr_Size(&item, &l);
					if(l > 0)
					{
						bufptr = new char[l];
						bufptr[0] = 0;
						if((POVMSUtil_GetFloat(&decobj, kPOVAttrib_Value, &floatval) == 0) &&
						   (POVMSAttr_Get(&item, kPOVMSType_CString, bufptr, &l) == 0))
							file->printf("%s=%s=%g\n", option->keyword, bufptr, (float)floatval);
						delete[] bufptr;
					}
					(void)POVMSAttr_Delete(&item);
				}
			}
			break;
		case kPOVAttrib_LibraryPath:
			err = POVMSObject_Get(obj, &list, option->key);
			if(err != 0)
				break;

			l = 0;
			err = POVMSAttrList_Count(&list, &l);
			if(err != 0)
				break;
			if(l == 0)
				break;

			imax = l;
			for(i = 1; i <= imax; i++)
			{
				err = POVMSAttrList_GetNth(&list, i, &item);
				if(err == 0)
				{
					l = 0;
					err = POVMSAttr_Size(&item, &l);
					if(l > 0)
					{
						bufptr = new char[l * 3];
						bufptr[0] = 0;
						if(POVMSAttr_GetUTF8String(&item, kPOVMSType_UCS2String, bufptr, &l) == 0)
							file->printf("%s=\"%s\"\n", option->keyword, bufptr);
						delete[] bufptr;
					}
					(void)POVMSAttr_Delete(&item);
				}
			}
			break;
		case kPOVAttrib_FatalErrorCommand:
		case kPOVAttrib_PostFrameCommand:
		case kPOVAttrib_PostSceneCommand:
		case kPOVAttrib_PreFrameCommand:
		case kPOVAttrib_PreSceneCommand:
		case kPOVAttrib_UserAbortCommand:
			POVMSObject cmdobj;

			err = POVMSObject_Get(obj, &cmdobj, option->key);
			if(err != 0)
				break;

			err = POVMSObject_Get(&cmdobj, &item, kPOVAttrib_CommandString);
			if(err == 0)
			{
				if(toupper(*(option->keyword + strlen(option->keyword) - 1)) == 'D')
				{
					l = 0;
					err = POVMSAttr_Size(&item, &l);
					if(l > 0)
					{
						bufptr = new char[l];
						bufptr[0] = 0;
						if(POVMSAttr_Get(&item, kPOVMSType_CString, bufptr, &l) == 0)
							file->printf("%s=%s\n", option->keyword, bufptr);
						delete[] bufptr;
					}
				}
				else
				{
					if(POVMSUtil_GetInt(&cmdobj, kPOVAttrib_ReturnAction, &intval) == 0)
					{
						if(intval < 0)
						{
							chr = -intval;
							file->printf("%s=!%c\n", option->keyword, chr);
						}
						else
						{
							chr = intval;
							file->printf("%s=%c\n", option->keyword, chr);
						}
					}
				}
			}
			if(err == 0)
				err = POVMSObject_Delete(&cmdobj);
			break;
	}

	return err;
}

bool ProcessRenderOptions::WriteOptionFilter(INI_Parser_Table *table)
{
	// So that we don't get both Bits_Per_Color and Bits_Per_Colour in the INI file.
	return (strcmp(table->keyword, "Bits_Per_Colour") != 0);
}

int ProcessRenderOptions::ProcessUnknownString(char *str, POVMSObjectPtr obj)
{
	POVMSAttributeList list;
	POVMSAttribute attr;
	int state = 0; // INI file
	int err = kNoErr;

	if(str == NULL)
	{
		ParseError("Expected filename, nothing was found.");
		return kParamErr;
	}

	// add filename or path

	// see if it is a POV file
	if(state == 0)
	{
		char *ptr = strrchr(str, '.');
		if(ptr != NULL)
		{
			if(pov_stricmp(ptr, ".pov") == 0)
				state = 1; // POV file
		}
	}

	// see if it is a path
	if(state == 0)
	{
		if(strlen(str) > 0)
		{
			if(str[strlen(str) - 1] == POV_FILE_SEPARATOR)
				state = 2; // library path
#ifdef POV_FILE_SEPARATOR_2
			else if(str[strlen(str) - 1] == POV_FILE_SEPARATOR_2)
				state = 2; // library path
#endif
		}
	}

	switch(state)
	{
		// INI file
		case 0:
			// parse INI file (recursive)
			err = ParseFile(str, obj);
			if(err == kNoErr)
			{
				// create list if it isn't there
				if(POVMSObject_Exist(obj, kPOVAttrib_IncludeIni) == kFalseErr)
					err = POVMSAttrList_New(&list);
				else if(POVMSObject_Exist(obj, kPOVAttrib_IncludeIni) != kNoErr)
					err = kObjectAccessErr;
				else
					err = POVMSObject_Get(obj, &list, kPOVAttrib_IncludeIni);
			}

			// add INI file to list
			if(err == kNoErr)
				err = POVMSAttr_New(&attr);
			if(err == kNoErr)
			{
				err = POVMSAttr_SetUTF8String(&attr, kPOVMSType_UCS2String, str);
				if(err == kNoErr)
					err = POVMSAttrList_Append(&list, &attr);
				else
					err = POVMSAttr_Delete(&attr);
			}
			if(err == kNoErr)
				err = POVMSObject_Set(obj, &list, kPOVAttrib_IncludeIni);
			break;
		// POV file
		case 1:
			// set POV file
			err = POVMSUtil_SetUTF8String(obj, kPOVAttrib_InputFile, str);
			break;
		// library path
		case 2:
			// create list if it isn't there
			if(POVMSObject_Exist(obj, kPOVAttrib_LibraryPath) == kFalseErr)
				err = POVMSAttrList_New(&list);
			else if(POVMSObject_Exist(obj, kPOVAttrib_LibraryPath) != kNoErr)
				err = kObjectAccessErr;
			else
				err = POVMSObject_Get(obj, &list, kPOVAttrib_LibraryPath);

			// add library path to list
			if(err == kNoErr)
				err = POVMSAttr_New(&attr);
			if(err == kNoErr)
			{
				err = POVMSAttr_SetUTF8String(&attr, kPOVMSType_UCS2String, str);
				if(err == kNoErr)
					err = POVMSAttrList_Append(&list, &attr);
				else
					err = POVMSAttr_Delete(&attr);
			}
			if(err == kNoErr)
				err = POVMSObject_Set(obj, &list, kPOVAttrib_LibraryPath);
			break;
	}

	return err;
}

ITextStream *ProcessRenderOptions::OpenFileForRead(const char *filename, POVMSObjectPtr obj)
{
	return OpenINIFileStream(filename, pov_base::POV_File_Text_INI, obj);
}

OTextStream *ProcessRenderOptions::OpenFileForWrite(const char *filename, POVMSObjectPtr)
{
	return new OTextStream(ASCIItoUCS2String(filename).c_str(), pov_base::POV_File_Text_INI);
}

ITextStream *ProcessRenderOptions::OpenINIFileStream(const char *filename, unsigned int stype, POVMSObjectPtr obj) // TODO FIXME - Use new Path class!
{
	// TODO FIXME - we should join forces with SceneData::FindFile()
	// TODO FIXME - use proper C++ strings instead of C character arrays

	int i,ii,l[POV_FILE_EXTENSIONS_PER_TYPE];
	char pathname[1024];
	char file[1024];
	char file_x[POV_FILE_EXTENSIONS_PER_TYPE][1024];
	int cnt = 0;
	int ll;
	POVMSAttribute attr, item;
	const char *xstr = strrchr(filename, '.');
	bool hasextension = ((xstr != NULL) && (strlen(xstr) <= 4)); // TODO FIXME - we shouldn't rely on extensions being at most 1+3 chars long

	if(POV_ALLOW_FILE_READ(ASCIItoUCS2String(filename).c_str(),stype) == 0) // TODO FIXME - Remove dependency on this macro!!! [trf]
		return NULL;

	for(i = 0; i < POV_FILE_EXTENSIONS_PER_TYPE; i++)
	{
		if((l[i] = strlen(pov::gPOV_File_Extensions[stype].ext[i])) > 0)
		{
			strcpy(file_x[i], filename);
			strcat(file_x[i], pov::gPOV_File_Extensions[stype].ext[i]);
		}
	}

	// Check the current directory first

	if((hasextension == true) && (CheckIfFileExists(filename) == true))
	{
		return new ITextStream(ASCIItoUCS2String(filename).c_str(), stype);
	}

	for(i = 0; i < POV_FILE_EXTENSIONS_PER_TYPE; i++)
	{
		if(l[i])
		{
			if(CheckIfFileExists(file_x[i]) == true)
			{
				return new ITextStream(ASCIItoUCS2String(file_x[i]).c_str(), stype);
			}
		}
	}

	if(POVMSObject_Get(obj, &attr, kPOVAttrib_LibraryPath) != 0)
		return NULL;

	if(POVMSAttrList_Count(&attr, &cnt) != 0)
	{
		(void)POVMSAttrList_Delete(&attr);
		return NULL;
	}

	for (i = 1; i <= cnt; i++)
	{
		(void)POVMSAttr_New(&item);
		if(POVMSAttrList_GetNth(&attr, i, &item) != 0)
			continue;
		ll = 0;
		if(POVMSAttr_Size(&item, &ll) != 0)
		{
			(void)POVMSAttr_Delete(&item);
			continue;
		}
		if(ll <= 0)
		{
			(void)POVMSAttr_Delete(&item);
			continue;
		}
		if(POVMSAttr_GetUTF8String(&item, kPOVMSType_UCS2String, file, &ll) != 0) // TODO FIXME!!!
		{
			(void)POVMSAttr_Delete(&item);
			continue;
		}
		(void)POVMSAttr_Delete(&item);

		file[strlen(file)+1] = '\0';
		file[strlen(file)] = POV_FILE_SEPARATOR;

		strcpy(pathname, file);
		strcat(pathname, filename);
		if((hasextension == true) && (CheckIfFileExists(pathname) == true))
		{
			(void)POVMSAttrList_Delete(&attr);
			return new ITextStream(ASCIItoUCS2String(pathname).c_str(), stype);
		}

		for(ii = 0; ii < POV_FILE_EXTENSIONS_PER_TYPE; ii++)
		{
			if(l[ii])
			{
				strcpy(pathname, file);
				strcat(pathname, file_x[ii]);
				if(CheckIfFileExists(pathname) == true)
				{
					(void)POVMSAttrList_Delete(&attr);
					return new ITextStream(ASCIItoUCS2String(pathname).c_str(), stype);
				}
			}
		}
	}

	(void)POVMSAttrList_Delete(&attr);

	if(l[0])
		ParseError("Could not find file '%s%s'", filename, pov::gPOV_File_Extensions[stype].ext[0]);
	else
		ParseError("Could not find file '%s'", filename);

	return NULL;
}

// TODO - the following code might need reviewing, according to trf

/* Supported output file types */
struct ProcessRenderOptions::Output_FileType_Table FileTypeTable[] =
{
	// attribute-specific file types (must go first)
	// code, attribute,                     internalId,                         has16BitGrayscale   hasAlpha
	// { 'C',  kPOVAttrib_HistogramFileType,   kPOVList_FileType_CSV,              false,              false },

	// generic file types
	// code, attribute,                     internalId,                         has16BitGrayscale   hasAlpha
	{ 'T',  0,                              kPOVList_FileType_Targa,            false,              true },
	{ 'C',  0,                              kPOVList_FileType_CompressedTarga,  false,              true },
	{ 'N',  0,                              kPOVList_FileType_PNG,              true,               true },
	{ 'J',  0,                              kPOVList_FileType_JPEG,             false,              false },
	{ 'P',  0,                              kPOVList_FileType_PPM,              true,               false },
	{ 'B',  0,                              kPOVList_FileType_BMP,              false,              false /*[1]*/ },
	{ 'E',  0,                              kPOVList_FileType_OpenEXR,          false /*[2]*/,      true },
	{ 'H',  0,                              kPOVList_FileType_RadianceHDR,      false,              false },
#ifdef SYS_TO_STANDARD
	{ 'S',  0,                              kPOVList_FileType_System,           SYS_GRAYSCALE_FLAG, SYS_ALPHA_FLAG },
#endif // SYS_TO_STANDARD

	//  [1] Alpha support for BMP uses an inofficial extension to the BMP file format, which is not recognized by
	//      most image pocessing software.

	//  [2] While OpenEXR does support greyscale output at >8 bits, the variants currently supported by POV-Ray
	//      use 16-bit floating-point values with 10 bit mantissa, which might be insufficient for various purposes
	//      such as height fields.

	// end-of-list marker
	{ '\0', 0, 0, false }
};

/* Supported special gamma types */
struct ProcessRenderOptions::Parameter_Code_Table GammaTypeTable[] =
{

	// code,    internalId,
	{ "SRGB",   kPOVList_GammaType_SRGB },

	// end-of-list marker
	{ NULL,     0 }
};

/* Supported dither types */
struct ProcessRenderOptions::Parameter_Code_Table DitherMethodTable[] =
{

	// code,    internalId,
	{ "B2",     kPOVList_DitherMethod_Bayer2x2 },
	{ "B3",     kPOVList_DitherMethod_Bayer3x3 },
	{ "B4",     kPOVList_DitherMethod_Bayer4x4 },
	{ "D1",     kPOVList_DitherMethod_Diffusion1D },
	{ "D2",     kPOVList_DitherMethod_Diffusion2D },
	{ "FS",     kPOVList_DitherMethod_FloydSteinberg },

	// end-of-list marker
	{ NULL,     0 }
};

int ProcessRenderOptions::ParseFileType(char code, POVMSType attribute, int* pInternalId, bool* pHas16BitGreyscale)
{
	*pInternalId = kPOVList_FileType_Unknown;
	int err = kNoErr;
	for (int i = 0; FileTypeTable[i].code != '\0'; i ++)
	{
		if ( (toupper(code) == FileTypeTable[i].code) &&
		     ((FileTypeTable[i].attribute == 0) || (FileTypeTable[i].attribute == attribute )) )
		{
			if (pHas16BitGreyscale != NULL)
				*pHas16BitGreyscale = FileTypeTable[i].has16BitGrayscale;
			*pInternalId = FileTypeTable[i].internalId;
			break;
		}
	}
#ifdef OPENEXR_MISSING
	if (*pInternalId == kPOVList_FileType_OpenEXR)
	{
		ParseError(
"This unofficial POV-Ray binary was built without support for the OpenEXR \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the OpenEXR library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: HDR."
		);
		err = kParamErr;
	}
#endif // OPENEXR_MISSING
	if (*pInternalId == kPOVList_FileType_Unknown)
	{
		ParseError("Unrecognized output file format %c.", code);
		err = kParamErr;
	}
	return err;
}

char ProcessRenderOptions::UnparseFileType(int fileType)
{
	for (int i = 0; FileTypeTable[i].code != '\0'; i ++)
		if (fileType == FileTypeTable[i].internalId)
			return FileTypeTable[i].code;
	return '\0';
}

int ProcessRenderOptions::ParseGammaType(char* code, int* pInternalId)
{
	*pInternalId = kPOVList_GammaType_Unknown;
	int err = ParseParameterCode(GammaTypeTable, code, pInternalId);
	if (err == kParamErr)
		ParseError("Unrecognized gamma setting '%s'.", code);
	return err;
}

const char* ProcessRenderOptions::UnparseGammaType(int gammaType)
{
	return UnparseParameterCode(GammaTypeTable, gammaType);
}

int ProcessRenderOptions::ParseParameterCode(const ProcessRenderOptions::Parameter_Code_Table* codeTable, char* code, int* pInternalId)
{
	for (int i = 0; code[i] != '\0'; i ++)
		code[i] = toupper(code[i]);
	for (int i = 0; codeTable[i].code != NULL; i ++)
	{
		if ( strcmp(code, codeTable[i].code) == 0 )
		{
			*pInternalId = codeTable[i].internalId;
			return kNoErr;
		}
	}
	return kParamErr;
}

const char* ProcessRenderOptions::UnparseParameterCode(const ProcessRenderOptions::Parameter_Code_Table* codeTable, int internalId)
{
	for (int i = 0; codeTable[i].code != NULL; i ++)
		if (internalId == codeTable[i].internalId)
			return codeTable[i].code;
	return NULL;
}

}
