/***************************************************************************************************
flt_ORA.cpp

Copyright © John Paul Chacha. All Rights Reserved.

This source code file, which has been provided by John Paul Chacha as part of his software product 
for use only by licensed users of that product, includes proprietary information of John Paul Chacha

USE OF THIS SOFTWARE IS GOVERNED BY THE TERMS AND CONDITIONS OF THE LICENSE AGREEMENT FURNISHED WITH
THE PRODUCT

IN PARTICULAR, JOHN PAUL CHACHA SHALL BE FREE FROM ANY CLAIMS OR LIABILITIES ARISING OUT OF THE USE
OR MISUSE OF THIS FILE AND/OR ITS CONTENTS
***************************************************************************************************/
#include "../common/system.h"
#include "../common/plugin.h"
#include "../common/win_util.h"
#include "../common/str_util.h"
#include "../common/path_util.h"
#include "../common/lang_util.h"
#include "../common/suite_defs.h"
#include "ora_util.h"
#include "resource.h"


//==================================================================================================
#define PLUGIN_NAME 		L"OpenRaster File-format Plug-in"
#define PLUGIN_AUTHOR		SUITE_CREATOR
#define PLUGIN_VERSION		SUITE_VER_UINT32

#define MY_STORE_NAME		L"flt_ORA.dll"
#define MY_STORE_UUID		(*((unsigned long*)("ORas")))
#define MY_TEMP_PREFIX		SUITE_PREFIX_PLUG_TMP L"flt_ora_"

#define STR_ERR_NEED_PNG	L"This plug-in is dependent on the PNG plug-in"
#define STR_ERR_ORACLE		L"Oracle DB config file"

//--------------------------------------------------------------------------------------------------
#define SETTINGS_VERSION	((1<<16)|(2)) //Feb 2021
struct my_plg_config
{
	unsigned long	uuid;
	unsigned long	version;
	bool			save_nonstandard;
	bool			unused_1;
};

//--------------------------------------------------------------------------------------------------
HINSTANCE api_inst;
my_plg_config settings={0};

//--------------------------------------------------------------------------------------------------
INT_PTR CALLBACK msg_dlg_about(HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar);
INT_PTR CALLBACK msg_dlg_config(HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar);

//==================================================================================================
//==================================================================================================


/***************************************************************************************************
This function is called during DLL startup/shutdown.
***************************************************************************************************/
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
	switch(ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		api_inst=(HINSTANCE)hModule;
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		break;
	}
	UNREFERENCED_PARAMETER(lpReserved);
	return TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
Load plug-in settings
***************************************************************************************************/
int my_settings_load(pi_STATESTORE* pi_StateStore)
{
	int loaded;
	my_plg_config tmp;

	memset(&settings,0,sizeof(my_plg_config));
	settings.save_nonstandard=false;
	loaded=0;
	if(pi_StateStore)
	{
		if(PLUGIN_TEST_PI_VERSION(pi_StateStore->version,PI_STATESTORE_VERSION))
		{
			if(pi_StateStore->permanent_read(MY_STORE_NAME,&tmp,sizeof(tmp)))
			{
				if((tmp.uuid==MY_STORE_UUID)&&(tmp.version==SETTINGS_VERSION))
				{
					memcpy(&settings,&tmp,sizeof(settings));
					loaded=1;
				}
			}
		}
	}
	return loaded;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
Save plug-in settings
***************************************************************************************************/
int my_settings_save(pi_STATESTORE* pi_StateStore)
{
	if(!pi_StateStore)
	{
		return 0;
	}
	settings.uuid=MY_STORE_UUID;
	settings.version=SETTINGS_VERSION;
	return pi_StateStore->permanent_write(MY_STORE_NAME,&settings,sizeof(settings));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
Read preamble
***************************************************************************************************/
int my_read_preamble(const wchar_t* file,unsigned char* p_mem,unsigned long sz_mem)
{
	unsigned long ul,read;
	HANDLE hf;

	if((!file)||(!p_mem)||(sz_mem<1))
	{
		return 0;
	}
	ul=FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN;
	hf=CreateFile(file,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,ul,0);
	if(hf==INVALID_HANDLE_VALUE)
	{
		return 0;
	}
	SetFilePointer(hf,0,0,FILE_BEGIN);
	read=(unsigned long)ReadFile(hf,p_mem,sz_mem,&ul,0);
	CloseHandle(hf);
	return ((read)? ((int)ul):0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This function is called to query plugin's name and type.
***************************************************************************************************/
int plg_GetInfo(plg_INFO* info)
{
	info->api_type=PLUGIN_APITYPE_FILE;
	info->api_version=PLUGIN_INTERFACE_VERSION;
	info->plg_version=PLUGIN_VERSION;
	StringCchCopy(info->plg_name  ,_countof(info->plg_name)  ,PLUGIN_NAME  );
	StringCchCopy(info->plg_author,_countof(info->plg_author),PLUGIN_AUTHOR);
	return 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This function is called to display plugin dialog boxes
***************************************************************************************************/
int plg_ShowDialog(plg_DIALOG* host)
{
	int i;

	if(!host)
	{
		return PLUGIN_ERR_BAD_PARAM;
	}
	if(host->pi_BasicUtils)
	{
		if(PLUGIN_TEST_PI_VERSION(host->pi_BasicUtils->version,PI_BASICUTILS_VERSION))
		{
			if((host->hwnd)&&(!(lang_IsConnected())))
			{
				lang_Connect(host->pi_BasicUtils->lang_TranslateCore);
			}
		}
	}

	//----------------------------------------------------------------------------------------------
	my_settings_load(host->pi_StateStore);

	switch(host->dialog_id)
	{
	case PLUGIN_DIALOG_ID_ABOUT:
		DialogBoxParam(api_inst,(LPCTSTR)IDD_ABOUT,host->hwnd,msg_dlg_about,0);
		return PLUGIN_OKAY;
	case PLUGIN_DIALOG_ID_CONFIG:
		i=DialogBoxParam(api_inst,(LPCTSTR)IDD_CONFIG,host->hwnd,msg_dlg_config,0);
		if(i>0)
		{
			if(!(my_settings_save(host->pi_StateStore)))
			{
				lang_MessageBox(host->hwnd,L"Error saving configuration data",
					L"Internal Error",MB_OK|MB_ICONERROR);
				break;
			}
		}
		return PLUGIN_OKAY;
	case PLUGIN_DIALOG_ID_HELP:
		return PLUGIN_ERR_NO_SUPPORT;
	}
	return PLUGIN_ERR_BAD_PARAM;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This is the plugin DLL function that Chasys Photo calls to check the load operation support level
for a specified file extension
***************************************************************************************************/
int flt_CheckLoad(flt_CHECK_L* info)
{
	if(_wcsicmp(info->extension,L".ora")==0)
	{
		info->level_image=255;
		info->level_layer=255;
		info->level_other=255;
		info->support_flags=0;
		return 1;
	}
	return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This is the plugin DLL function that Chasys Photo calls to check the save operation support level
for a specified file extension
***************************************************************************************************/
int flt_CheckSave(flt_CHECK_S* info)
{
	if(_wcsicmp(info->extension,L".ora")==0)
	{
		info->level_image=255;
		info->level_layer=255;
		info->level_other=255;
		info->support_flags=0;
		return 1;
	}
	return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This is the plugin DLL function that Chasys Photo calls to load images
***************************************************************************************************/
int flt_LoadImage(flt_IMAGE_L* host)
{
	unsigned long xx,yy,sz_ansi,layer_id,num_layers;
	bool flag,need_canvas,emit_base_props;
	unsigned long *p_pix;
	wchar_t str[256];
	char *p_ansi;
	int count;
	ora_Obj obj={0,};
	ora_Layer* p_layer;
	meta_PHYS_Obj* pm_PHYS;
	pibi_FILEINFO finf;
	pibi_IMAGE b_virt;
	pibi_IMAGE b_fram;

	//----------------------------------------------------------------------------------------------
	if(!host)
	{
		return PLUGIN_ERR_BAD_PARAM;
	}
	if(!(host->pi_BasicImage))
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(!(PLUGIN_TEST_PI_VERSION(host->pi_BasicImage->version,PI_BASICIMAGE_VERSION)))
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(host->pi_BasicUtils)
	{
		if(PLUGIN_TEST_PI_VERSION(host->pi_BasicUtils->version,PI_BASICUTILS_VERSION))
		{
			if((host->hwnd)&&(!(lang_IsConnected())))
			{
				lang_Connect(host->pi_BasicUtils->lang_TranslateCore);
			}
		}
	}

	//----------------------------------------------------------------------------------------------
	memset(&b_virt,0,sizeof(b_virt));
	memset(&b_fram,0,sizeof(b_fram));
	memset(&finf,0,sizeof(finf));

	//----------------------------------------------------------------------------------------------
	//load settings
	my_settings_load(host->pi_StateStore);

	//----------------------------------------------------------------------------------------------
	//initialize
	if(!(ora_Initialize(&obj,false)))
	{
		ora_Destroy(&obj);
		return obj.error_id;
	}
	obj.pi_BasicImage=host->pi_BasicImage;
	obj.pi_BasicColor=host->pi_BasicColor;

	//----------------------------------------------------------------------------------------------
	//read the file
	if(!(ora_LoadFromFile(&obj,host->file)))
	{
		ora_Destroy(&obj);
		switch(obj.error_id)
		{
		case PLUGIN_ERR_NO_SUPPORT:
			sz_ansi=16*1024;
			p_ansi=(char*)GlobalAlloc(GPTR,sz_ansi);
			if(!p_ansi)
			{
				break;
			}
			memset(p_ansi,0,sz_ansi);
			flag=false;
			count=my_read_preamble(host->file,(unsigned char*)p_ansi,sz_ansi);
			if(count>0)
			{
				p_ansi[min(count,(int)sz_ansi-1)]=0;
				flag=(
					(string_SegmentA(p_ansi,"oracle"      ,true))&&
					(string_SegmentA(p_ansi,"connect_data",true))&&
					(string_SegmentA(p_ansi,"host ="      ,true)) );
			}
			GlobalFree(p_ansi);
			if(flag)
			{
				if(host->set_infostr)
				{
					host->set_infostr(host,PLUGIN_INFOTYPE_ERROR,STR_ERR_ORACLE);
				}
				return PLUGIN_ERR_NOT_AN_IMAGE;
			}
			break;
		}
		return obj.error_id;
	}

	//----------------------------------------------------------------------------------------------
	//return thumbnail if that's the only thing that's needed
	num_layers=0;
	if(host->host_flags&PLUGIN_HOSTFLAG_WANT_THUMB)
	{
		if((obj.merge_data_buf)||(obj.thumb_data_buf))
		{
			goto merged_data;
		}
	}

	//----------------------------------------------------------------------------------------------
	//extract layers and send to host
	emit_base_props=true;
	need_canvas=false;
	for(layer_id=0;layer_id<obj.layer_count;layer_id++)
	{
		if((host->set_progress)&&(obj.layer_count>1))
		{
			if(host->set_progress(host,layer_id,obj.layer_count)==0)
			{
				obj.error_id=PLUGIN_ERR_USER_CANCEL;
				goto err;
			}
		}

		p_layer=&(obj.layer_stack[(obj.layer_count-1)-layer_id]);
		if(!(ora_GetLayerImage(&obj,p_layer,&b_fram)))
		{
			need_canvas=true;
			continue;
		}
		need_canvas|=
			((layer_id==0)&&
			((p_layer->x_pos!=0)||(b_fram.width !=obj.width )||
			( p_layer->y_pos!=0)||(b_fram.height!=obj.height)));

		//------------------------------------------------------------------------------------------
		//insert canvas if needed
		if((num_layers==0)&&(need_canvas))
		{
			host->x_pos =0;
			host->y_pos =0;
			host->width =obj.width;
			host->height=obj.height;
			StringCchCopy(host->name,_countof(host->name),L"Canvas Placeholder");
			host->options=LAYER_BLEND_NORM;
			if(host->create(host))
			{
				num_layers++;
				for(yy=0;yy<host->height;yy++)
				{
					p_pix=host->lp_pix+(yy*host->pitch);
					for(xx=0;xx<host->width;xx++)
					{
						*p_pix=0xff000000; //OpenRaster uses a black transparent canvas 
						p_pix++;
					}
				}
				if(host->meta_write)
				{
					if((obj.ppi_x>0)||(obj.ppi_y>0))
					{
						host->meta_data=0;
						if(host->meta_write(host,"PHYS",sizeof(meta_PHYS_Obj)))
						{
							__analysis_assume(host->meta_data!=0);
							pm_PHYS=(meta_PHYS_Obj*)(host->meta_data);
							pm_PHYS->version=META_PHYS_VERSION;
							pm_PHYS->units=META_PHYS_UNIT_PPI;
							pm_PHYS->x=obj.ppi_x;
							pm_PHYS->y=obj.ppi_y;
						}
					}
				}
			}
		}

		//------------------------------------------------------------------------------------------
		//read image hierarchy
		host->x_pos =(short)p_layer->x_pos;
		host->y_pos =(short)p_layer->y_pos;
		host->width =b_fram.width;
		host->height=b_fram.height;
		StringCchCopy(host->name,_countof(host->name),p_layer->name);
		host->transparency=(unsigned char)(255-p_layer->opacity);
		ora_GetLayerOptions(p_layer,host);
		host->host_flags&=~PLUGIN_HOSTFLAG_RESIZE;
		if(host->create(host)==0)
		{
			host->pi_BasicImage->destroy(&b_fram);
			host->warnings|=PLUGIN_WARN_LAYER_COUNT;
			obj.error_id=min(0,host->host_response);
			if(host->set_infostr)
			{
				StringCchPrintf(str,_countof(str),L"Layer size = %i x %i; Name = %s",
					(int)(host->width),(int)(host->height),host->name);
				host->set_infostr(host,PLUGIN_INFOTYPE_ERROR,str);
			}
			goto err;
			//goto next_layer;
		}
		num_layers++;
		host->pi_BasicImage->virtualize(&b_virt,host->width,host->height,host->pitch,host->lp_pix);
		host->pi_BasicImage->blit(
			&b_virt,0,0,b_virt.width-1,b_virt.height-1,
			&b_fram,0,0,b_fram.width-1,b_fram.height-1,PI_BI_BLIT_STRETCH,0);
		host->pi_BasicImage->destroy(&b_virt);
		if(host->meta_write)
		{
			if(emit_base_props)
			{
				emit_base_props=false;
				if(host->meta_write)
				{
					if((obj.ppi_x>0)||(obj.ppi_y>0))
					{
						host->meta_data=0;
						if(host->meta_write(host,"PHYS",sizeof(meta_PHYS_Obj)))
						{
							__analysis_assume(host->meta_data!=0);
							pm_PHYS=(meta_PHYS_Obj*)(host->meta_data);
							pm_PHYS->version=META_PHYS_VERSION;
							pm_PHYS->units=META_PHYS_UNIT_PPI;
							pm_PHYS->x=obj.ppi_x;
							pm_PHYS->y=obj.ppi_y;
						}
					}
				}
			}
			if((p_layer->blend_size)&&(p_layer->blend_ptr))
			{
				host->meta_data=p_layer->blend_ptr;
				host->meta_write(host,"BLND",p_layer->blend_size);
			}
		}
		host->pi_BasicImage->destroy(&b_fram);
	}

	//----------------------------------------------------------------------------------------------
	//insert merged image if we have no layers
	if((obj.layer_count<1)&&((obj.merge_data_buf)||(obj.thumb_data_buf)))
	{
merged_data:
		memset(&finf,0,sizeof(finf));
		finf.memfile_data=obj.merge_data_buf;
		finf.memfile_size=obj.merge_data_len;
		finf.memfile_type_hint=L".png";
		if((!obj.merge_data_buf)||(!obj.merge_data_len))
		{
			finf.memfile_data=obj.thumb_data_buf;
			finf.memfile_size=obj.thumb_data_len;
			finf.memfile_type_hint=L".png";
		}
		if(host->pi_BasicImage->load_file(&b_fram,&finf))
		{
			host->x_pos =0;
			host->y_pos =0;
			host->width =b_fram.width;
			host->height=b_fram.height;
			StringCchCopy(host->name,_countof(host->name),L"Merged Image");
			host->options=LAYER_BLEND_NORM;
			host->transparency=0;
			host->group_id=0;
			host->host_flags|=PLUGIN_HOSTFLAG_RESIZE;
			if(host->create(host))
			{
				if((num_layers<1)&&(obj.layer_count>0))
				{
					host->warnings|=PLUGIN_WARN_THUMB_ONLY;
				}
				num_layers++;
				host->pi_BasicImage->virtualize(&b_virt,host->width,host->height,host->pitch,
					host->lp_pix);
				host->pi_BasicImage->blit(
					&b_virt,0,0,b_virt.width-1,b_virt.height-1,
					&b_fram,0,0,b_fram.width-1,b_fram.height-1,PI_BI_BLIT_STRETCH_MAX_Q,0);
				host->pi_BasicImage->destroy(&b_virt);
				host->pi_BasicImage->destroy(&b_fram);
			}

			//add attachments
			if(host->meta_write)
			{
				//physical dimensions
				host->meta_data=0;
				if(host->meta_write(host,"PHYS",sizeof(meta_PHYS_Obj)))
				{
					__analysis_assume(host->meta_data!=0);
					pm_PHYS=(meta_PHYS_Obj*)(host->meta_data);
					pm_PHYS->version=META_PHYS_VERSION;
					pm_PHYS->units=META_PHYS_UNIT_PPI;
					pm_PHYS->x=obj.ppi_x;
					pm_PHYS->y=obj.ppi_y;
				}
			}
		}
		else
		{
			obj.error_id=min(0,host->host_response);
			if(host->set_infostr)
			{
				StringCchPrintf(str,_countof(str),L"Layer size = %i x %i; Name = %s",
					(int)(host->width),(int)(host->height),host->name);
				host->set_infostr(host,PLUGIN_INFOTYPE_ERROR,str);
			}
		}
	}

	//----------------------------------------------------------------------------------------------
	if(!num_layers)
	{
		goto err;
	}
	host->pi_BasicImage->destroy(&b_virt);
	host->pi_BasicImage->destroy(&b_fram);
	ora_Destroy(&obj);

	//----------------------------------------------------------------------------------------------
	if(host->set_infostr)
	{
		host->set_infostr(host,PLUGIN_INFOTYPE_CODEC,L"OpenRaster");
	}
	host->img_type=PLUGIN_IMGTYPE_COMPOSITE;
	host->img_subtype=PLUGIN_IMGSUBTYPE_CLIPPED;
	return num_layers;

	//----------------------------------------------------------------------------------------------
	//handle errors
err:
	host->pi_BasicImage->destroy(&b_virt);
	host->pi_BasicImage->destroy(&b_fram);
	ora_Destroy(&obj);
	if(host->set_infostr)
	{
		if(obj.error_str[0])
		{
			host->set_infostr(host,PLUGIN_INFOTYPE_ERROR,obj.error_str);
		}
		if(obj.error_id==PLUGIN_ERR_NO_COMPONENT)
		{
			host->set_infostr(host,PLUGIN_INFOTYPE_ERROR,STR_ERR_NEED_PNG);
		}
	}
	return min(0,obj.error_id);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This is the plugin DLL function that Chasys Photo calls to save images
***************************************************************************************************/
int flt_SaveImage(flt_IMAGE_S* host)
{
	int ret,ww,hh,layer_id,num_saved,layer_count;
	wchar_t tmp_dir[MAX_PATH];
	wchar_t name[MAX_PATH];
	unsigned long ul;
	double f;
	HANDLE hf;
	ora_Obj ora={0,};
	ora_Layer* p_layer=0;
	meta_PHYS_Obj* pm_PHYS;
	pibi_FILEINFO finf;
	pibi_IMAGE b_img;
	pibi_IMAGE b_thm;

	//----------------------------------------------------------------------------------------------
	if(!host)
	{
		return PLUGIN_ERR_BAD_PARAM;
	}
	if(!(host->pi_BasicImage))
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(!(PLUGIN_TEST_PI_VERSION(host->pi_BasicImage->version,PI_BASICIMAGE_VERSION)))
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(host->pi_BasicUtils)
	{
		if(PLUGIN_TEST_PI_VERSION(host->pi_BasicUtils->version,PI_BASICUTILS_VERSION))
		{
			if((host->hwnd)&&(!(lang_IsConnected())))
			{
				lang_Connect(host->pi_BasicUtils->lang_TranslateCore);
			}
		}
	}

	//----------------------------------------------------------------------------------------------
	//check and validate image setup
	if(host->img_type!=PLUGIN_IMGTYPE_COMPOSITE)
	{
		return PLUGIN_ERR_BAD_MODE;
	}
	if(max(host->width,host->height)>ORA_MAX_WIDTH)
	{
		return PLUGIN_ERR_BITMAP_BIG;
	}
	if(host->layer_count>ORA_MAX_LAYERS)
	{
		return PLUGIN_ERR_BAD_IMAGE;
	}

	//----------------------------------------------------------------------------------------------
	//load settings, initialize
	my_settings_load(host->pi_StateStore);
	memset(&b_img,0,sizeof(b_img));
	memset(&b_thm,0,sizeof(b_thm));
	memset(&finf,0,sizeof(finf));

	//----------------------------------------------------------------------------------------------
	//create temporary folder
	tmp_dir[0]=0;
	if(host->pi_BasicQuery)
	{
		if(PLUGIN_TEST_PI_VERSION(host->pi_BasicQuery->version,PI_BASICQUERY_VERSION))
		{
			ret=host->pi_BasicQuery->get_str(host->pi_BasicQuery,PI_BQ_STR_HOST_TEMP_DIR,
				tmp_dir,_countof(tmp_dir));
			if(ret<1)
			{
				tmp_dir[0]=0;
			}
		}
	}
	if(wcsnlen(tmp_dir,8)<3)
	{
		GetTempPath(_countof(tmp_dir),tmp_dir);
	}
	StringCchPrintf(name,_countof(name),MY_TEMP_PREFIX L"%08x",GetCurrentThreadId());
	path_Append(tmp_dir,_countof(tmp_dir),name);
	path_ValidateFolder(tmp_dir);

	//----------------------------------------------------------------------------------------------
	//initialize OpenRaster object
	if(!(ora_Initialize(&ora,true)))
	{
		ora_Destroy(&ora);
		return ora.error_id;
	}
	if(host->select)
	{
		host->select(host,0);
	}
	ora.width =host->width;
	ora.height=host->height;

	//----------------------------------------------------------------------------------------------
	//write layers in reverse order as required by OpenRaster
	num_saved=0;
	layer_count=(host->select)?(host->layer_count):1;
	for(layer_id=layer_count-1;layer_id>=0;layer_id--)
	{
		if(host->select)
		{
			if(host->select(host,layer_id)==0)
			{
				//host->warnings|=PLUGIN_WARN_LAYER_COUNT;
				//break;
				ora_Destroy(&ora);
				return PLUGIN_ERR_BAD_PARAM;
			}
		}
		if((host->set_progress)&&(layer_count>1))
		{
			if(host->set_progress(host,num_saved,layer_count)==0)
			{
				ora_Destroy(&ora);
				return PLUGIN_ERR_USER_CANCEL;
			}
		}
		p_layer=&(ora.layer_stack[num_saved]);
		num_saved++;

		//------------------------------------------------------------------------------------------
		p_layer->x_pos=host->x_pos;
		p_layer->y_pos=host->y_pos;
		p_layer->width =host->width;
		p_layer->height=host->height;
		StringCchCopy(p_layer->name,_countof(p_layer->name),host->name);
		p_layer->opacity=(unsigned char)(255-host->transparency);
		if(host->meta_read)
		{
			p_layer->blend_size=host->meta_read(host,"BLND");
			p_layer->blend_ptr =host->meta_data;
		}
		ora_SetLayerOptions(p_layer,host);
		if(host->meta_read)
		{
			if(layer_id==0)
			{
				if(host->meta_read(host,"PHYS")>=sizeof(meta_PHYS_Obj))
				{
					pm_PHYS=(meta_PHYS_Obj*)(host->meta_data);
					switch(pm_PHYS->units)
					{
					default:
					case META_PHYS_UNIT_PPI:
						ora.ppi_x=pm_PHYS->x;
						ora.ppi_y=pm_PHYS->y;
						break;
					case META_PHYS_UNIT_PPCM:
						ora.ppi_x=pm_PHYS->x*2.54;
						ora.ppi_y=pm_PHYS->y*2.54;
						break;
					case META_PHYS_UNIT_PPM:
						ora.ppi_x=pm_PHYS->x*0.0254;
						ora.ppi_y=pm_PHYS->y*0.0254;
						break;
					}
				}
			}
			/*
			len=host->meta_read(host,"TEXT");
			if(len)
			{
				xcf_textdata_set((char*)host->meta_data,len,&layer);
			}
			*/
			if(host->meta_read(host,"ADJU"))
			{
				host->warnings|=PLUGIN_WARN_LAYER_TYPE;
				p_layer->hidden=true;
			}
		}

		StringCchPrintf(name,_countof(name),L"lyr%03i.png",layer_id);
		StringCchCopy(p_layer->src_file,_countof(p_layer->src_file),tmp_dir);
		path_Append(p_layer->src_file,_countof(p_layer->src_file),name);
		path_Validate(p_layer->src_file);
		host->pi_BasicImage->virtualize(&b_img,host->width,host->height,host->pitch,host->lp_pix);
		memset(&finf,0,sizeof(finf));
		finf.file=p_layer->src_file;
		ret=host->pi_BasicImage->save_file(&b_img,&finf);
		host->pi_BasicImage->destroy(&b_img);
		if(!ret)
		{
			ora_Destroy(&ora);
			if(host->set_infostr)
			{
				host->set_infostr(host,PLUGIN_INFOTYPE_ERROR,STR_ERR_NEED_PNG);
			}
			return PLUGIN_ERR_NO_COMPONENT;
		}
	}
	ora.layer_count=num_saved;

	//----------------------------------------------------------------------------------------------
	//generate merged image and thumbnail; use select(-1) if needed and supported
	ora.merge_data_buf=0;
	ora.merge_data_len=0;
	ora.thumb_data_buf=0;
	ora.thumb_data_len=0;
	if(host->select)
	{
		if(host->select(host,-1))
		{
			host->pi_BasicImage->virtualize(&b_img,host->width,host->height,host->pitch,
				host->lp_pix);
			StringCchCopy(name,_countof(name),tmp_dir);
			path_Append(name,_countof(name),L"mergedimage.png");
			path_Validate(name);
			memset(&finf,0,sizeof(finf));
			finf.file=name;
			ret=host->pi_BasicImage->save_file(&b_img,&finf);
			if(ret)
			{
				ul=FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN;
				hf=CreateFile(name,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,ul,0);
				if(hf!=INVALID_HANDLE_VALUE)
				{
					ora.merge_data_len=GetFileSize(hf,0);
					ora.merge_data_buf=(unsigned char*)GlobalAlloc(GPTR,ora.merge_data_len);
					if(ora.merge_data_buf)
					{
						ret=ReadFile(hf,ora.merge_data_buf,ora.merge_data_len,&ul,0);
					}
					CloseHandle(hf);
				}
				path_DeleteFile(name);
			}
			f=256.0/(0.001+max(host->width,host->height));
			if(f<1.0)
			{
				ww=(int)((f*host->width )+0.5);
				hh=(int)((f*host->height)+0.5);
				if(host->pi_BasicImage->create(&b_thm,ww,hh))
				{
					host->pi_BasicImage->blit(
						&b_thm,0,0,b_thm.width-1,b_thm.height-1,
						&b_img,0,0,b_img.width-1,b_img.height-1,PI_BI_BLIT_STRETCH_MAX_Q,0);
					StringCchCopy(name,_countof(name),tmp_dir);
					path_Append(name,_countof(name),L"thumbnail.png");
					path_Validate(name);
					memset(&finf,0,sizeof(finf));
					finf.file=name;
					ret=host->pi_BasicImage->save_file(&b_thm,&finf);
					if(ret)
					{
						ul=FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN;
						hf=CreateFile(name,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,ul,0);
						if(hf!=INVALID_HANDLE_VALUE)
						{
							ora.thumb_data_len=GetFileSize(hf,0);
							ora.thumb_data_buf=(unsigned char*)GlobalAlloc(GPTR,ora.thumb_data_len);
							if(ora.thumb_data_buf)
							{
								ret=ReadFile(hf,ora.thumb_data_buf,ora.thumb_data_len,&ul,0);
							}
							CloseHandle(hf);
						}
						path_DeleteFile(name);
					}
				}
			}
			host->pi_BasicImage->destroy(&b_thm);
			host->pi_BasicImage->destroy(&b_img);
			host->select(host,0);
		}
	}

	//----------------------------------------------------------------------------------------------
	//write the file, clean up, then return
	ret=ora_SaveToFile(&ora,host->file,settings.save_nonstandard);
	ora_Destroy(&ora);
	path_RemoveDirectory(tmp_dir);
	if(!ret)
	{
		return ora.error_id;
	}
	if(settings.save_nonstandard)
	{
		host->warnings|=PLUGIN_WARN_NONSTANDARD;
	}
	return num_saved;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This function processes messages for config dialog
***************************************************************************************************/
INT_PTR CALLBACK msg_dlg_config(HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar)
{
	wchar_t str[MAX_PATH];

	switch(msg)
	{
	case WM_INITDIALOG:
		window_Center(hDlg,GetParent(hDlg));
		lang_TranslateWindow(hDlg);
		lang_TranslateText(str,_countof(str),L"Configuration:");
		StringCchCat(str,_countof(str),L" " PLUGIN_NAME);
		SetWindowText(hDlg,str);

		CheckDlgButton(hDlg,IDC_CHECK1,(UINT)(settings.save_nonstandard!=0));
		return 1|lpar;
	case WM_COMMAND:
		switch(LOWORD(wpar))
		{
		case IDOK:
			settings.version=SETTINGS_VERSION;
			settings.save_nonstandard=(IsDlgButtonChecked(hDlg,IDC_CHECK1)!=0);
			EndDialog(hDlg,1);
			break;
		case IDCANCEL:
			EndDialog(hDlg,0);
			break;
		}
		break;
	}
	return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////


/***************************************************************************************************
This function processes messages for the about dialog
***************************************************************************************************/
INT_PTR CALLBACK msg_dlg_about(HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar)
{
	wchar_t* pstr;

	//----------------------------------------------------------------------------------------------
	switch(msg)
	{
	case WM_INITDIALOG:
		SetWindowText(hDlg,L"About " PLUGIN_NAME);
		lang_TranslateWindow(hDlg);
		pstr=string_ReadTextResource(api_inst,(LPCTSTR)IDR_README,L"TEXT");
		if(pstr)
		{
			string_SetNewline(pstr,GlobalSize(pstr)/sizeof(wchar_t),true);
			SetDlgItemText(hDlg,IDC_EDIT1,pstr);
			GlobalFree(pstr);
		}
		window_Center(hDlg,GetParent(hDlg));
		return 1|lpar;
	case WM_COMMAND:
		switch(LOWORD(wpar))
		{
		case IDOK:
			EndDialog(hDlg,1);
			break;
		case IDCANCEL:
			EndDialog(hDlg,0);
			break;
		}
		break;
	}
	return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
