#include "jfif.h"
#include "adobe.h"
#include "exif.h"

#include <string.h>


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


struct jfif_marker_descr jfif_markers_descr[]=
{
	/* frame markers, non-differential, huffman coding */
	{	JFIF_SOF0,	"SOF0",		"baseline DCT"	},
	{	JFIF_SOF1,	"SOF1",		"extended sequential DCT"	},
	{	JFIF_SOF2,	"SOF2",		"progressive DCT"	},
	{	JFIF_SOF3,	"SOF3",		"lossless (sequential)"	},
	/* frame markers, differential, huffman coding */
	{	JFIF_SOF5,	"SOF5",		"differential sequential DCT"	},
	{	JFIF_SOF6,	"SOF6",		"differential progressive DCT"	},
	{	JFIF_SOF7,	"SOF7",		"differential lossless (sequential)"	},
	/* frame markers, non-differential, arithmetic coding */
	{	JFIF_JPG,	"JPG",		"reserved for JPEG extensions"	},
	{	JFIF_SOF9,	"SOF9",		"extended sequential DCT"	},
	{	JFIF_SOF10,	"SOF10",	"progressive DCT"	},
	{	JFIF_SOF11,	"SOF11",	"lessless (sequential)"	},
	/* frame markers, differential, arithmetic coding */
	{	JFIF_SOF13,	"SOF13",	"differential sequential DCT"	},
	{	JFIF_SOF14,	"SOF14",	"differential progressive DCT"	},
	{	JFIF_SOF15,	"SOF15",	"differential lossless (sequential)"	},
	/* huffman table specification */
	{	JFIF_DHT,	"DHT",		"huffman table(s)"	},
	/* arithmetic coding conditioning specification */
	{	JFIF_DAC,	"DAC",		"arithmetic coding conditiong(s)"	},
	/* restart internal termination */
	{	JFIF_RST0,	"RST0",		"restart with modulo 8 count 0"	},
	{	JFIF_RST1,	"RST1",		"restart with modulo 8 count 1"	},
	{	JFIF_RST2,	"RST2",		"restart with modulo 8 count 2"	},
	{	JFIF_RST3,	"RST3",		"restart with modulo 8 count 3"	},
	{	JFIF_RST4,	"RST4",		"restart with modulo 8 count 4"	},
	{	JFIF_RST5,	"RST5",		"restart with modulo 8 count 5"	},
	{	JFIF_RST6,	"RST6",		"restart with modulo 8 count 6"	},
	{	JFIF_RST7,	"RST7",		"restart with modulo 8 count 7"	},
	/* other markers */
	{	JFIF_SOI,	"SOI",		"start of image"	},
	{	JFIF_EOI,	"EOI",		"end of image"	},
	{	JFIF_SOS,	"SOS",		"start of scan"	},
	{	JFIF_DQT,	"DQT",		"define quantization table(s)"	},
	{	JFIF_DNL,	"DNL",		"define number of lines"	},
	{	JFIF_DRI,	"DRI",		"define restart internal"	},
	{	JFIF_DHP,	"DHP",		"define hierarchical progression"	},
	{	JFIF_EXP,	"EXP",		"expand reference component(s)"	},
	{	JFIF_APP0,	"APP0",		"application segment 0"	},
	{	JFIF_APP1,	"APP1",		"application segment 1"	},
	{	JFIF_APP2,	"APP2",		"application segment 2"	},
	{	JFIF_APP3,	"APP3",		"application segment 3"	},
	{	JFIF_APP4,	"APP4",		"application segment 4"	},
	{	JFIF_APP5,	"APP5",		"application segment 5"	},
	{	JFIF_APP6,	"APP6",		"application segment 6"	},
	{	JFIF_APP7,	"APP7",		"application segment 7"	},
	{	JFIF_APP8,	"APP8",		"application segment 8"	},
	{	JFIF_APP9,	"APP9",		"application segment 9"	},
	{	JFIF_APP10,	"APP10",	"application segment 10"	},
	{	JFIF_APP11,	"APP11",	"application segment 11"	},
	{	JFIF_APP12,	"APP12",	"application segment 12"	},
	{	JFIF_APP13,	"APP13",	"application segment 13"	},
	{	JFIF_APP14,	"APP14",	"application segment 14"	},
	{	JFIF_APP15,	"APP15",	"application segment 15"	},
	{	JFIF_JPG0,	"JPG0",		"reserved for JPEG extensions"	},
	{	JFIF_JPG1,	"JPG1",		"reserved for JPEG extensions"	},
	{	JFIF_JPG2,	"JPG2",		"reserved for JPEG extensions"	},
	{	JFIF_JPG3,	"JPG3",		"reserved for JPEG extensions"	},
	{	JFIF_JPG4,	"JPG4",		"reserved for JPEG extensions"	},
	{	JFIF_JPG5,	"JPG5",		"reserved for JPEG extensions"	},
	{	JFIF_JPG6,	"JPG6",		"reserved for JPEG extensions"	},
	{	JFIF_JPG7,	"JPG7",		"reserved for JPEG extensions"	},
	{	JFIF_JPG8,	"JPG8",		"reserved for JPEG extensions"	},
	{	JFIF_JPG9,	"JPG9",		"reserved for JPEG extensions"	},
	{	JFIF_JPG10,	"JPG10",	"reserved for JPEG extensions"	},
	{	JFIF_JPG11,	"JPG11",	"reserved for JPEG extensions"	},
	{	JFIF_JPG12,	"JPG12",	"reserved for JPEG extensions"	},
	{	JFIF_JPG13,	"JPG13",	"reserved for JPEG extensions"	},
	{	JFIF_COM,	"COM",		"comment"	},
	/* reserved markers */
	{	JFIF_TEM,	"TEM",		"for temporary private use in arithmetic coding"	},
	/* reserved */
	{	JFIF_MARKER_EOT, NULL, NULL }
};

struct jfif_marker_range_descr jfif_marker_ranges_descr[]=
{
	{	0x02,	0xbf,	"RES",	"reserved"	},	/* RES */
	/* reserved */
	{	JFIF_MARKER_EOT, JFIF_MARKER_EOT, NULL, NULL }
};


/* JPEG/JFIF 1.02 */
/* SOI and leading APPn markers are found and skipped, in-APP13 Photoshop contents is
   parsed. Parsing stops when any other JFIF marker is met */
void jfif_parse(unsigned char* buffer, const unsigned long int bytes_left,
		struct parser_result* result)
{
	struct parser_result res;
	unsigned char* ptr;
	unsigned long int jfif_chunks;
	unsigned long int written_bytes;
	long int bytes;
	unsigned short int length;
	unsigned char tag;
	bool ret;
	bool saw_EOI;
	bool saw_SOS;

	jfif_chunks=0;
	ptr=buffer;
	bytes=bytes_left;
	written_bytes=0;
	ret=true;
	saw_EOI=false;
	saw_SOS=false;
	debug("JFIF parser: %ld byte(s) to parse", bytes);

	while ((bytes>0) && !saw_SOS && !saw_EOI)
	{
		if (bytes<2)
		{
			ret=false;
			error("JFIF parser: chunk underrun (%ld byte(s) remaining, 2 or more expected)",
					bytes);
			break;
		}

		/* JFIF tag marker expected */
		tag=getbyte(ptr, 0);
		if (tag!=JFIF_TAG)
		{
			ret=false;
			error("JFIF parser: expected tag marker not matched");
			break;
		}
		if (prefs.rewrite)
			written_bytes+=dump_rewrite(ptr, 1);
		bytes--;
		ptr++;

		jfif_chunks++;
		
		tag=getbyte(ptr, 0);
		switch (tag)
		{
			case JFIF_SOI:
				debug("JFIF parser: SOI tag found");
				if (prefs.rewrite)
					written_bytes+=dump_rewrite(ptr, 1);
				bytes--;
				ptr++;
				break;

			case JFIF_EOI:
				saw_EOI=true;
				debug("JFIF parser: EOI tag found");
				if (prefs.rewrite)
					written_bytes+=dump_rewrite(ptr, 1);
				bytes--;
				ptr++;
				if (bytes)
					warning("JFIF parser: unexpected trailing data: %ld byte(s), ignored",
							bytes);
				break;

			case JFIF_SOS:
			{
				ret=jfif_skip_chunk(jfif_tag_name((const enum JFIF_MARKER)tag),
						&ptr, &bytes, &written_bytes);

				saw_SOS=true;
				if (bytes<2)
				{
					ret=false;
					error("JFIF parser: SOS/image-data chunk underrun (%ld byte(s) remaining, 2 bytes expected)",
							tag, bytes);
					break;
				}

				/* skip image data (scans): jump to the end (expecting EOI) */
				if (prefs.rewrite && (bytes>2))
					written_bytes+=dump_rewrite(ptr, (bytes-2));
				ptr+=(bytes-2);
				bytes=2;
				break;
			}

			/* parse APPn chunks */
			case JFIF_APP0:
			case JFIF_APP1:
			case JFIF_APP2:
			case JFIF_APP3:
			case JFIF_APP4:
			case JFIF_APP5:
			case JFIF_APP6:
			case JFIF_APP7:
			case JFIF_APP8:
			case JFIF_APP9:
			case JFIF_APP10:
			case JFIF_APP11:
			case JFIF_APP12:
			case JFIF_APP13:
			case JFIF_APP14:
			case JFIF_APP15:
			{
				unsigned char* ptr_at_length;

				tag-=JFIF_APP0;	/* determine APP chunk number (APP0 -> 0, APP1 -> 1, etc.) */
				debug("JFIF parser: APP%d chunk found", tag);
				if (prefs.rewrite)
					written_bytes+=dump_rewrite(ptr, 1);
				bytes--;
				ptr++;

				if (bytes<2)
				{
					ret=false;
					error("JFIF parser: APP%d chunk underrun (%ld byte(s) remaining, 2 or more expected)",
							tag, bytes);
					break;
				}

				ptr_at_length=ptr;
				length=(getbyte(ptr, 0)<<8)|getbyte(ptr, 1);
				if (bytes<length)
				{
					ret=false;
					error("JFIF parser: APP%d chunk underrun (%ld byte(s) remaining, %ld byte(s) expected)",
							tag, bytes, length);
					break;
				}
				length-=2;	/* skip length bytes themselves */
				if (prefs.rewrite)
					written_bytes+=dump_rewrite(ptr, 2);
				bytes-=2;
				ptr+=2;

				tag+=JFIF_APP0;
				switch (tag)
				{
					case JFIF_APP0:
						/* TODO: detect JFIF/JFXX */
						if (prefs.rewrite && (length>0))
							written_bytes+=dump_rewrite(ptr, length);
						break;

					case JFIF_APP1:
					{	
						/* TODO: here parse Exif in */
						if (prefs.rewrite && (length>0))
							written_bytes+=dump_rewrite(ptr, length);
						break; /* disable exif sub-parsing until it works */
						if (bytes<4)
						{
							if (prefs.rewrite && (length>0))
								written_bytes+=dump_rewrite(ptr, length);
						}
						else
						{
							char exif_marker[5];

							if (strcmp((char*)getraw(ptr, 0, (unsigned char*)exif_marker, 6),
									EXIF_MARKER_STRING)!=0)
							{
								ret=false;
								error("JFIF parser: expected Exif tag marker not matched");
								if (prefs.rewrite && (length>0))
									written_bytes+=dump_rewrite(ptr, length);
								break;
							}
							res.parsed_bytes=0;
							res.written_bytes=0;
							exif_parse(ptr, length, &res);
							ret=res.ret;
							if (res.parsed_bytes!=length)
							{
								error("JFIF parser: Exif size inconsistency (%lu byte(s) read, %lu expected)",
										res.parsed_bytes, length);
								/* limit to length bytes */
								if (res.parsed_bytes>length)
								{
									ret=false; /* that situation might be critical */
									length=res.parsed_bytes;
								}
								else
									/* write extra trailing bytes if Exif chunk was shorter than expected */
									/* TODO: check if that situation has to be checked everywhere */
									if ((res.parsed_bytes<length) && prefs.rewrite)
										written_bytes+=dump_rewrite(ptr+length, length-res.parsed_bytes);
							}
							written_bytes+=res.written_bytes;
						}
						break;
					}

					case JFIF_APP6:
						/* TODO: detect NITF */
						if (prefs.rewrite && (length>0))
							written_bytes+=dump_rewrite(ptr, length);
						break;

					case JFIF_APP13:	/* Adobe IRB (commonly IPTC chunks from PhotoShop) */
					{
						unsigned char* p;
						unsigned long int len;
						char product[100];
						unsigned short int product_len;
						unsigned short int product_version;

						len=length;
						p=ptr;

						/* determine and skip product info */
						/* Photoshop 3.0, 0-terminated string */
						if (strcmp((char*)getraw(p, 0, (unsigned char*)product, 14),
							"Photoshop 3.0")==0)
						{
							product_len=14;
							product_version=300;
						}
						else
							/* Photoshop 4.0, 0-terminated string */
							if (strcmp((char*)getraw(p, 0, (unsigned char*)product, 14),
								"Photoshop 4.0")==0)
							{
								product_len=14;
								product_version=400;
							}
							else
								/* Photoshop 2.5, strange string plus 8 bytes */
								if (strncmp((char*)getraw(p, 0, (unsigned char*)product, 19),
									"Adobe_Photoshop2.5:", 19)==0)
								{
									product[19]='\0';
									product_len=19+8;
									product_version=250;
								}
								else
									/* Adobe Photoshop 7.0 thumbnail?, strange string plus 1 byte */
									if (strcmp((char*)getraw(p, 0, (unsigned char*)product, 9),
										"Adobe_CM")==0)
									{
										product_len=9+1;
										product_version=0;
									}
									else
									{
										warning("JFIF parser: unknown Adobe product name");
										*product='\0';
										product_len=0;
										product_version=0;
									}
						if (prefs.rewrite && (product_len>0))
							written_bytes+=dump_rewrite(ptr, product_len);
						len-=product_len;
						p+=product_len;
						debug("JFIF parser: Photoshop product: '%s' (version %u)", product, product_version);

						if (len>=4)
						{
							bool parsed;

							parsed=false;
							res.parsed_bytes=0;
							res.written_bytes=0;
							/* parse 8BIM chunk */
							if (getlong(p, 0)==ADOBE_8BIM_MARKER)
							{
								debug("JFIF parser: 8BIM marker found, invoking sublevel 8BIM parser");
								adobe_8bim_parse(p, len, product_version, &res);
								parsed=true;
								ret=res.ret;
								p+=res.parsed_bytes;
							}
							else
								/* parse 8BPS chunk */
								if (getlong(p, 0)==ADOBE_8BPS_MARKER)
								{
									debug("JFIF parser: 8BPS marker found, invoking sublevel 8BPS parser");
									adobe_8bps_parse(p, len, &res);
									parsed=true;
									ret=res.ret;
									p+=res.parsed_bytes;
								}
								else
								{
									debug("JFIF parser: neither 8BIM nor 8BPS marker found in APP13 chunk");
									ret=false;
									error("JFIF parser: unsupported or missing Adobe contents");
									/* all bytes from this unknown chunk will be written below */
								}

							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 (parsed && prefs.rewrite && (res.written_bytes!=res.parsed_bytes))
							{
								debug("JFIF parser: backward change of chunk length is necessary (%lu -> %lu)",
										res.parsed_bytes, res.written_bytes);
								if (prefs.rewrite_cached)
								{
									unsigned long int l;

									/* reconstitute the length value to write back */
									l=res.written_bytes+product_len+2;
									/* check length overflow */
									if (l>0xffff)
									{
										l=0xffff;
										error("JFIF parser: rewritten chunk size overflow (%lu)", l);
									}
									debug("JFIF parser: chunk length to change backwardly at offset %lu: %lu (instead of %lu)",
											rewrite_cache_offset-l, l, length+2);
									/* write modified length to cache */
									setword(rewrite_cache, rewrite_cache_offset-l, (unsigned short int)l);
								}
								else
									error("JFIF parser: attempting to do backward changes to uncached write buffer");
							}

							if (parsed && (res.parsed_bytes+product_len!=length))
							{
								error("JFIF parser: Adobe chunk size inconsistency (%lu byte(s) read, %lu expected)",
										res.parsed_bytes+product_len, length);
								/* don't write any bytes more */
								res.parsed_bytes=len;
							}
							/* remaining bytes to rewrite */
							len-=res.parsed_bytes;
						}
						else
							if (len>0)
								/* see if remaining bytes here should be supported (usual w/
								   embedded thumbnails w/ product "Adobe CM" */
								warning("JFIF parser: JFIF chunk size inconsistency (%lu remaining byte(s))",
										len);

						/* if there are 'len' bytes unwritten by any sublevel parser */
						if (prefs.rewrite && (len>0))
							written_bytes+=dump_rewrite(p, len);
						break;
					}

					default:
						if (prefs.rewrite && (length>0))
							written_bytes+=dump_rewrite(ptr, length);
						break;
				}
				bytes-=length;
				ptr+=length;
				break;
			}

			case JFIF_SOF0:
			case JFIF_SOF1:
			case JFIF_SOF2:
			case JFIF_SOF3:
			case JFIF_SOF5:
			case JFIF_SOF6:
			case JFIF_SOF7:
			case JFIF_SOF9:
			case JFIF_SOF10:
			case JFIF_SOF11:
			case JFIF_SOF13:
			case JFIF_SOF14:
			case JFIF_SOF15:
			case JFIF_DQT:
			case JFIF_DHT:
			case JFIF_DRI:
			case JFIF_DAC:
			case JFIF_COM:
			case JFIF_DNL:
			case JFIF_DHP:
			case JFIF_EXP:
/*			default: */
			{
				struct jfif_marker_descr* marker_descr;

				marker_descr=jfif_marker_match((const enum JFIF_MARKER)tag);
				if (!marker_descr)
				{
					struct jfif_marker_range_descr* marker_range_descr;

					marker_range_descr=jfif_marker_range_match((const enum JFIF_MARKER)tag);
					if (!marker_range_descr)
					{
						ret=false;
						error("JFIF parser: unknown tag 0x%02x", tag);
					}
				}
				/* skip chunk and retain the worst ret possible */
				ret&=jfif_skip_chunk(jfif_tag_name((const enum JFIF_MARKER)tag),
						&ptr, &bytes, &written_bytes);
				break;
			}
		}
	}

	if (!ret && (!saw_EOI && !saw_SOS))
	{
		ret=false;
		error("JFIF parser: neither found SOS nor EOI, possibly corrupted file or not a supported JPEG/JFIF file format");
	}
	debug("JFIF parser: %u dataset(s) inspected, total length: %lu byte(s)",
			jfif_chunks, bytes_left-bytes);
	if (prefs.rewrite)
		debug("JFIF parser: %lu byte(s) rewritten", written_bytes);
	result->parsed_bytes=bytes_left-bytes;
	result->written_bytes=written_bytes;
	result->ret=ret;
}

bool jfif_skip_chunk(char* label, unsigned char** ptr, long int* bytes,
		unsigned long int* written_bytes)
{
	unsigned char* p;
	long int b;
	unsigned short int l;

	b=*bytes;
	p=*ptr;

	debug("JFIF parser: %s chunk found", label);

	/* skip tag */
	if (prefs.rewrite)
		written_bytes+=dump_rewrite(p, 1);
	b--;
	p++;

	if (b<2)
	{
		error("JFIF parser: %s chunk underrun (%ld byte(s) remaining, 2 or more expected)",
				label, b);
		return(false);
	}

	l=(getbyte(p, 0)<<8)|getbyte(p, 1);
	if (b<l)
	{
		error("JFIF parser: %s chunk underrun (%ld byte(s) remaining, %ld byte(s) expected)",
				label, b, l);
		return(false);
	}
	l-=2;	/* skip length bytes themselves */
	b-=2;
	p+=2;

	/* skip chunk */
	b-=l;
	p+=l;

	if (prefs.rewrite)
		/* write chunk, including its length bytes */
		*written_bytes+=dump_rewrite(*ptr+1, l+2);

	*bytes=b;
	*ptr=p;
	return(true);
}

struct jfif_marker_descr* jfif_marker_match(const enum JFIF_MARKER tag)
{
	register unsigned int i;

	i=0;
	while (jfif_markers_descr[i].tag!=JFIF_MARKER_EOT)
	{
		if (jfif_markers_descr[i].tag==tag)
			return(&jfif_markers_descr[i]);
		i++;
	}
	return(NULL);
}

struct jfif_marker_range_descr* jfif_marker_range_match(const enum JFIF_MARKER tag)
{
	register unsigned int i;

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

char* jfif_tag_name(const enum JFIF_MARKER tag)
{
	struct jfif_marker_descr* marker_descr;
	struct jfif_marker_range_descr* marker_range_descr;

	marker_descr=jfif_marker_match(tag);
	if (marker_descr)
		return(marker_descr->name);
	marker_range_descr=jfif_marker_range_match(tag);
	if (marker_range_descr)
		return(marker_range_descr->name);
	return("<unknown>");
}

void jfif_dump_markers_list(void)
{
}
