#include "adobe.h"
#include "iptc.h"
#include "jfif.h"


extern struct prefs_struct prefs;
extern unsigned char* rewrite_cache;
extern unsigned long int rewrite_cache_offset;


struct adobe_resource_descr adobe_resources_descr[]=
{
	{	ADOBE_RSC_RASTER_INFO,						    "raster info"	},
	{	ADOBE_RSC_MACINTOSH_PRINT_INFO,				    "macintosh print info"  },
	{	ADOBE_RSC_INDEXED_COLOR_TABLE,				    "indexed color table"	},
	{	ADOBE_RSC_RESOLUTION, 						    "resolution"	},
	{	ADOBE_RSC_ALPHA_CHANNEL_NAMES,				    "alpha channel names"	},
	{	ADOBE_RSC_DISPLAY,							    "display"	},
	{	ADOBE_RSC_CAPTION,							    "caption"	},
	{	ADOBE_RSC_BORDER_STYLE,						    "border style"	},
	{	ADOBE_RSC_BACRKGROUND_COLOR,  				    "bacrkground color"	},
	{	ADOBE_RSC_PRINT_FLAGS,						    "print flags"	},
	{	ADOBE_RSC_GRAYSCALE_MULTICHANNEL_HALFTONING,    "grayscale multichannel halftoning"	},
	{	ADOBE_RSC_COLOR_HALFTONING,					    "color halftoning"	},
	{	ADOBE_RSC_DUOTONE_HALFTONING, 				    "duotone halftoning"	},
	{	ADOBE_RSC_GRAYSCALE_MULTICHANNEL_TRANSFER,	    "grayscale multichannel transfer"	},
	{	ADOBE_RSC_COLOR_TRANSFER, 					    "color transfer"	},
	{	ADOBE_RSC_DUOTONE_TRANSFER,					    "duotone transfer"	},
	{	ADOBE_RSC_DUOTONE_IMAGE,  					    "duotone image"	},
	{	ADOBE_RSC_BW_VALUES,  						    "bw values"	},
	{	ADOBE_RSC_OBSOLETE1,  						    "obsolete1"	},
	{	ADOBE_RSC_EPS_OPTIONS,						    "eps options"	},
	{	ADOBE_RSC_QUICK_MASK, 						    "quick mask"	},
	{	ADOBE_RSC_OBSOLETE2,  						    "obsolete2"	},
	{	ADOBE_RSC_LAYER_STATE,						    "layer state"	},
	{	ADOBE_RSC_WORKING_PATH,						    "working path"	},
	{	ADOBE_RSC_LAYER_GROUP,						    "layer group"	},
	{	ADOBE_RSC_OBSOLETE3,  						    "obsolete3"	},
	{	ADOBE_RSC_IPTC_FILE_INFO,  					    "iptc file info"	},
	{	ADOBE_RSC_IMAGE_MODE, 						    "image mode"	},
	{	ADOBE_RSC_JPEG_QUALITY,						    "jpeg quality"	},
	{	ADOBE_RSC_GRID_GUIDES_INFO,					    "grid guides info"	},
	{	ADOBE_RSC_THUMBNAIL_RESOURCE1,				    "thumbnail resource1"	},
	{	ADOBE_RSC_COPYRIGHT_FLAG, 					    "copyright flag"	},
	{	ADOBE_RSC_UNIFORM_RESOURCE_LOCATOR,			    "uniform resource locator"	},
	{	ADOBE_RSC_THUMBNAIL_RESOURCE2,				    "thumbnail resource2"	},
	{	ADOBE_RSC_GLOBAL_ANGLE,						    "global angle"	},
	{	ADOBE_RSC_COLOR_SAMPLERS_RESOURCE1,			    "color samplers resource1"	},
	{	ADOBE_RSC_ICC_PROFILE,						    "icc profile"	},
	{	ADOBE_RSC_WATERMARK,  						    "watermark"	},
	{	ADOBE_RSC_ICC_UNTAGGER,						    "icc untagger"	},
	{	ADOBE_RSC_EFFECTS_VISIBLE,					    "effects visible"	},
	{	ADOBE_RSC_SPOT_HALFTONE,  					    "spot halftone"	},
	{	ADOBE_RSC_DOCUMENT_SPECIFIC_IDS,  			    "document specific ids"	},
	{	ADOBE_RSC_UNICODE_ALPHA_NAMES,				    "unicode alpha names"	},
	{	ADOBE_RSC_INDEXED_COLOR_TABLE_COUNT,  		    "indexed color table count"	},
	{	ADOBE_RSC_TRANSPARENT_INDEX,  				    "transparent index"	},
	{	ADOBE_RSC_GLOBAL_ALTITUDE,					    "global altitude"	},
	{	ADOBE_RSC_SLICES, 							    "slices"	},
	{	ADOBE_RSC_WORKFLOW_URL,						    "workflow url"	},
	{	ADOBE_RSC_XPEP_JUMP,  						    "xpep jump"	},
	{	ADOBE_RSC_ALPHA_IDENTIFIERS,  				    "alpha identifiers"	},
	{	ADOBE_RSC_URL_LIST,							    "url list"	},
	{	ADOBE_RSC_VERSION_INFO,						    "version info"	},
	{	ADOBE_RSC_CLIPPING_PATH,  					    "clipping path"	},
	{	ADOBE_RSC_PRINT_FLAGS_INFO,					    "print flags info"	},
	/* reserved */
	{	ADOBE_RSC_EOT, NULL }
};

struct adobe_resource_range_descr adobe_resource_ranges_descr[]=
{
	{	0x07d0,	0x0bb6,	"path info" },	/* ADOBE_RSC_PATH_INFO */
	/* reserved */
	{	ADOBE_RSC_EOT, ADOBE_RSC_EOT, NULL }
};


/* Photoshop 3.0 .psd */
/* 8BPS contents is being parsed, nested JFIF, Exif and 8BIM chunks are parsed,
   other data is skipped */
void adobe_8bps_parse(unsigned char* buffer, unsigned long int bytes_left,
		struct parser_result* result)
{
	struct parser_result res;
	unsigned char* ptr;
	unsigned long int chunks;
	unsigned long int written_bytes;
	long int bytes;
	bool ret;

	chunks=0;
	ptr=buffer;
	bytes=bytes_left;
	written_bytes=0;
	ret=true;
	debug("8BPS parser: %ld byte(s) to parse", bytes);

	/* parse 8BPS header */
	if (bytes<26)
	{
		ret=false;
		error("8BPS parser: chunk underrun (%ld byte(s) remaining, 26 or more expected)",
				bytes);
	}
	else
	{
		/* 8BPS marker expected */
		if (getlong(ptr, 0)!=ADOBE_8BPS_MARKER)
		{
			ret=false;
			error("8BPS parser: expected marker not matched");
		}
		else
		{
			if (prefs.rewrite)
				written_bytes+=dump_rewrite(ptr, 4);
			bytes-=4;
			ptr+=4;

			/* version (value 1 expected) */
			if (getword(ptr, 0)!=0x0001)
			{
				ret=false;
				error("8BPS parser: expected version not matched (found 0x%04x, expected 0x0001)",
						getword(ptr, 0), 0x0001);
			}
			else
			{
				if (prefs.rewrite)
					written_bytes+=dump_rewrite(ptr, 2);
				bytes-=2;
				ptr+=2;

				/* reserved bytes (value 0 expected) */
				if ((getlong(ptr, 0)!=0x00000000) || (getword(ptr, 4)!=0x0000))
				{
					ret=false;
					error("8BPS parser: expected reserved bytes value not matched (found 0x%08lx%04x, expected 0x000000000000)",
							getlong(ptr, 0), getword(ptr, 4));
				}
				else
				{
					if (prefs.rewrite)
						written_bytes+=dump_rewrite(ptr, 6);
					bytes-=6;
					ptr+=6;

					/* skip channels, rows, columns, depth, mode info */
					if (prefs.rewrite)
						written_bytes+=dump_rewrite(ptr, 2+4+4+2+2);
					bytes-=2+4+4+2+2;
					ptr+=2+4+4+2+2;
					chunks++;

					/* parse in-8BPS embedded possible 8BIM or JFIF chunks */
					while (bytes>0)
					{
						if (getlong(ptr, 0)==ADOBE_8BPS_MARKER)
						{
							debug("8BPS parser: 8BPS marker found");
							adobe_8bps_parse(ptr, bytes, &res);
							ret=res.ret;
							ptr+=res.parsed_bytes;
							bytes-=res.parsed_bytes;
							written_bytes+=res.written_bytes;
							chunks++;
						}
						else
						if (getlong(ptr, 0)==ADOBE_8BIM_MARKER)
						{
							debug("8BPS parser: 8BIM marker found");
							adobe_8bim_parse(ptr, bytes, 0, &res);
							ret=res.ret;
							ptr+=res.parsed_bytes;
							bytes-=res.parsed_bytes;
							written_bytes+=res.written_bytes;
							chunks++;
						}
						else
						if (getword(buffer, 0)==((JFIF_TAG<<8)|JFIF_SOI))
						{
							debug("8BPS parser: SOI tag found");
							jfif_parse(ptr, bytes, &res);
							ret=res.ret;
							ptr+=res.parsed_bytes;
							bytes-=res.parsed_bytes;
							written_bytes+=res.written_bytes;
							chunks++;
						}
						else
						{
							if (bytes>0)
							{
								ret=false;
								error("8BPS parser: unsupported chunk follows (%lu byte(s))",
										bytes);
							}
							break;
						}
						/* no need to change any chunk length back if we have rewritten
						   modified chunk in write cache */
					}
				}
			}
		}
	}

	debug("8BPS parser: %u dataset(s) inspected, total length: %lu byte(s)",
			chunks, bytes_left-bytes);
	if (prefs.rewrite)
		debug("8BPS parser: %lu byte(s) rewritten", written_bytes);
	result->parsed_bytes=bytes_left-bytes;
	result->written_bytes=written_bytes;
	result->ret=ret;
}

/* Photoshop ff0 file (some IPTC fields plus some extra Photoshop metadata) */
void adobe_ffo_parse(unsigned char* buffer, unsigned long int bytes_left,
		const unsigned short int photoshop_version, struct parser_result* result)
{
	struct parser_result res;
	unsigned char* ptr;
	unsigned long int chunks;
	long int bytes;
	unsigned long int written_bytes;
	bool ret;

	chunks=0;
	ptr=buffer;
	bytes=bytes_left;
	written_bytes=0;
	ret=true;
	debug("8BIM parser: %ld byte(s), version: %u", bytes, photoshop_version);

	/* parse ff0 chunk */
	if (bytes<16)
	{
		ret=false;
		error("FFO parser: chunk underrun (%ld byte(s) remaining, 16 or more expected)",
				bytes);
	}
	else
	{
		/* 8BIM marker expected */
		if (getbyte(ptr, 0)==ADOBE_FFO_MARKER)
		{
			unsigned char version;
			bool copyrighted;
			unsigned long int length;

			debug("8BIM parser: FFO tag found");

			/* version? */
			version=getbyte(ptr, 1);
			if (prefs.technical && (version!=1))
				warning("FFO parser: possibly unsupported FFO version %u (expected: 1)", version);

			/* copyrighted */
			copyrighted=(bool)getbyte(ptr, 2);
			debug("FFO parser: copyrighted flag is %sset", copyrighted?"":"not");

			if (prefs.rewrite)
				written_bytes+=dump_rewrite(ptr, 1);
			ptr+=3;
			bytes-=3;

			/* image URL (and its length, 1 minimum, and to multiply by 2) */
			length=getlong(ptr, 0);
			if (prefs.technical && (length==0))
				warning("FFO parser: zero length image URL field (expected: 1 minimum)");
			length*=2;

			if (prefs.rewrite)
				written_bytes+=dump_rewrite(ptr, length+4);
			ptr+=length+4;
			bytes-=length+4;

			/* IPTC application record version expected (7 bytes): */
			/* left for further sublevel parser */

			/* more IPTC datasets */
			iptc_parse(ptr, bytes, false, &res);
			ret=res.ret;
		}
		else
		{
			ret=false;
			error("FFO parser: expected marker not matched");
		}
	}

	result->parsed_bytes=bytes_left-bytes;
	result->written_bytes=written_bytes;
	result->ret=ret;
}

/* Photoshop basic image resource block */
/* 8BIM chunks are recognized and skipped, only IPTC data is being parsed */
void adobe_8bim_parse(unsigned char* buffer, unsigned long int bytes_left,
		const unsigned short int photoshop_version, struct parser_result* result)
{
	struct parser_result res;
	unsigned char* ptr;
	unsigned long int chunks;
	unsigned long int written_bytes;
	unsigned long int data_len;
	unsigned short int resource;
	long int bytes;
	short int str_len;
	bool ret;

	chunks=0;
	ptr=buffer;
	bytes=bytes_left;
	written_bytes=0;
	ret=true;
	debug("8BIM parser: %ld byte(s), version: %u", bytes, photoshop_version);

	/* parse 8BIM chunk(s) */
	while (bytes>0)
	{
		if (bytes<4)
		{
			ret=false;
			error("8BIM parser: chunk underrun (%ld byte(s) remaining, 4 or more expected)",
					bytes);
			break;
		}

		/* 8BIM marker expected */
		if (getlong(ptr, 0)==ADOBE_8BIM_MARKER)
		{
			bool parsed;

			debug("8BIM parser: 8BIM tag found");
			if (prefs.rewrite)
				written_bytes+=dump_rewrite(ptr, 4);
			ptr+=4;
			bytes-=4;

			if (bytes<2)
			{
				ret=false;
				error("8BIM parser: Photoshop generic chunk underrun (%ld byte(s) remaining, 2 or more expected)",
						bytes);
				break;
			}

			/* resource ID */
			resource=getword(ptr, 0);
			if (prefs.rewrite)
				written_bytes+=dump_rewrite(ptr, 2);
			ptr+=2;
			bytes-=2;

			debug("8BIM parser: Photoshop generic chunk assumed");

			if (bytes<6)
			{
				ret=false;
				error("8BIM parser: chunk underrun (%ld byte(s) remaining, 6 or more expected)",
						bytes);
				break;
			}

			/* resource name (usually empty, padded pstring) */
			str_len=getbyte(ptr, 0);
			str_len++;	/* count the size byte itself */
			/* pad the pstring size to be even */
			if (str_len&1)
				str_len++;

			/* skip the pstring */
			if (prefs.rewrite && (str_len>0))
				written_bytes+=dump_rewrite(ptr, str_len);
			ptr+=str_len;
			bytes-=str_len;

			/* chunk length */
			data_len=getlong(ptr, 0);
			if (prefs.rewrite)
				written_bytes+=dump_rewrite(ptr, 4);
			ptr+=4;
			bytes-=4;

			if (resource==ADOBE_RSC_IPTC_FILE_INFO)
			{
				/* parse ITPC datasets */
				debug("8BIM parser: IPTC signature found, invoking sublevel IPTC parser");
				iptc_parse(ptr, data_len, false, &res);
				ret=res.ret;
				parsed=true;
			}
			else
			{
				/* non-ITPC resource */
				struct adobe_resource_descr* resource_descr;

				resource_descr=adobe_match_resource((const enum ADOBE_RESOURCE)resource);
				if (resource_descr)
					debug("8BIM parser: Photoshop %s resource chunk found, skipped",
							resource_descr->label);
				else
				{
					struct adobe_resource_range_descr* resource_range_descr;

					resource_range_descr=adobe_match_resource_range((const enum ADOBE_RESOURCE)resource);
					if (resource_range_descr)
						debug("8BIM parser: Photoshop %s resource chunk found, skipped",
								resource_range_descr->label);
					else
						warning("8BIM parser: unknown Adobe resource code %04x", resource);
				}
				/* rewrite the whole chunk (not eaten here) */
				res.parsed_bytes=0;
				res.written_bytes=0;
				parsed=false;
			}

			if (prefs.rewrite)
			{
				written_bytes+=res.written_bytes;

				/* change back chunk length when written bytes != parsed bytes (which means
				   that some bytes have been filtered out or added */
				if (res.written_bytes!=res.parsed_bytes)
				{
					debug("8BIM parser: backward change of chunk length is necessary (%lu -> %lu)",
							res.parsed_bytes, res.written_bytes);
					if (prefs.rewrite_cached)
					{
						unsigned long int offset;

						/* reconstitute the offset of the length bytes to change back */
						offset=rewrite_cache_offset-res.written_bytes-4;
						debug("8BIM parser: chunk length to change backwardly at offset %lu: %lu (instead of %lu)",
								offset, res.written_bytes, getlong(rewrite_cache, offset));
						/* write modified length to cache */
						setlong(rewrite_cache, offset, res.written_bytes);
					}
					else
						error("8BIM parser: attempting to do backward changes to uncached write buffer");
				}
			}

			/* if any resource was parsed (and already re-written if enabled) */
			if (res.parsed_bytes>0)
			{
				/* check remaining length consistency (prevents overflow, theorical case) */
				if (res.parsed_bytes>(unsigned long int)bytes)
				{
					/* raise an error (for the caller to know) but continue */
					ret=false;
					error("8BIM parser: 8BIM chunk size inconsistency (%lu byte(s), %lu expected)",
							res.parsed_bytes, bytes);
				}
				else
				{
					/* is embedded data was smaller than the reserved chunk size then */
					/* write the missing extra bytes */
					if (prefs.rewrite && (res.parsed_bytes<data_len))
						written_bytes+=dump_rewrite(ptr+res.parsed_bytes,
								data_len-res.parsed_bytes);
				}
			}
			else
				if (prefs.rewrite && (data_len>0))
					written_bytes+=dump_rewrite(ptr, data_len);

			/* pad the read bytes (so that the size is even), if byte remains */
			if ((data_len<(unsigned long int)bytes) && data_len&1)
			{
				debug("8BIM parser: found padding byte");
				/* pad written bytes according to what we read (sinec ) */
				if (prefs.rewrite && (!prefs.rewrite_cached || !parsed))
				{
					written_bytes+=dump_rewrite(ptr+data_len, 1);
					debug("8BIM parser: written padding byte to file");
				}
				data_len++;
			}

			/* pad written bytes (if not already done) */
			if (prefs.rewrite && prefs.rewrite_cached && parsed && res.written_bytes&1)
			{
				/* we are using cached rewritting (so we are filtering in)
				   in any way), so we cannot use any padding byte from the read
				   flow. let's write a blank padding byte */
				unsigned char foo;

				foo='\0';
				written_bytes+=dump_rewrite(&foo, 1);
				debug("8BIM parser: written padding byte to cache");
			}

			ptr+=data_len;
			bytes-=data_len;
			chunks++;
		}
		else
		{
			ret=false;
			error("8BIM parser: expected marker not matched");
			break;
		}
	}

	debug("8BIM parser: %u chunk(s) inspected, total length: %lu byte(s)",
			chunks, bytes_left-bytes);
	if (prefs.rewrite)
		debug("8BIM parser: %lu byte(s) rewritten", written_bytes);
	result->parsed_bytes=bytes_left-bytes;
	result->written_bytes=written_bytes;
	result->ret=ret;
}

struct adobe_resource_descr* adobe_match_resource(const enum ADOBE_RESOURCE resource)
{
	int i;

	i=0;
	while (adobe_resources_descr[i].resource!=ADOBE_RSC_EOT)
	{
		if (adobe_resources_descr[i].resource==resource)
			return(&adobe_resources_descr[i]);
		i++;
	}
	return(NULL);
}

struct adobe_resource_range_descr* adobe_match_resource_range(const enum ADOBE_RESOURCE resource)
{
	int i;

	/* match ranges */
	i=0;
	while (adobe_resource_ranges_descr[i].lower_boundary!=ADOBE_RSC_EOT)
	{
		if (	(resource<=adobe_resource_ranges_descr[i].lower_boundary)
			||	(resource<=adobe_resource_ranges_descr[i].upper_boundary))
			return(&adobe_resource_ranges_descr[i]);
		i++;
	}
	return(NULL);
}

void adobe_dump_resource_list(void)
{
}
