#include "iptc.h"

#include <stdlib.h>
#include <string.h>


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


/* TODO: document the IPTC codes with a leading empty comment below (they are non-IIM 4.1) */
struct iptc_code_descr iptc_codes_descr[]=
{
	/* IPTC IIM 4.1 */
	/* envelope record */
	{	IPTC_MODEL_VERSION,										IPTC_DATASET_SPECIFIC,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"model version"  },
	{	IPTC_DESTINATION,										IPTC_DATASET_BINARY,	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	1024,	"destination"  },
	{	IPTC_FILE_FORMAT,										IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"file format"  },
	{	IPTC_FILE_FORMAT_VERSION,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"file format version"  },
	{	IPTC_SERVICE_ID,										IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	10,		"service ID"  },
	{	IPTC_ENVELOPE_NUMBER,									IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	8,		"envelope number"  },
	{	IPTC_PRODUCT_ID,										IPTC_DATASET_TEXT,		IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	32,		"product ID"  },
	{	IPTC_ENVELOPE_PRIORITY,									IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"envelope priority"  },
	{	IPTC_DATE_SENT,											IPTC_DATASET_DATE,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	8,		"date sent"  },
	{	IPTC_TIME_SENT,											IPTC_DATASET_TIME,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	11,		"time sent"  },
	{	IPTC_CODED_CHARACTER_SET,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"character set"  },
	{	IPTC_UNO,												IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	80,		"unique name of object"  },
	{	IPTC_ARM_ID,											IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"abstract relationship method ID"  },
	{	IPTC_ARM_VERSION,										IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"abstract relationship method version"  },
	/* application record */
	{	IPTC_APP_RECORD_VERSION,								IPTC_DATASET_SPECIFIC,  IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED, 2,		"record version"	},
	{	IPTC_APP_OBJECT_TYPE_REFERENCE,					    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	67,		"object type reference" },
	{	IPTC_APP_OBJECT_ATTRIBUTE_REFERENCE,			    	IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	68,		"object attribute reference"	},
	{	IPTC_APP_OBJECT_NAME,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	64,		"object name (title)"	},
	{	IPTC_APP_EDIT_STATUS,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	64,		"edit status"	},
	{	IPTC_APP_EDITORIAL_UPDATE,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"editorial update"  },
	{	IPTC_APP_URGENCY,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"urgency"	},
	{	IPTC_APP_SUBJECT_REFERENCE,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	236,	"subject reference" },
	{	IPTC_APP_CATEGORY,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	3,		"category"  },
/**/{	IPTC_APP_CATEGORY_DESCRIPTION,							IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	0,		"category description"  },
	{	IPTC_APP_SUPPLEMENTAL_CATEGORY,							IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	32,		"supplemental category"	},
/**/{	IPTC_APP_CATEGORY_CODE,									IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	0,		"category code" },
	{	IPTC_APP_FIXTURE_IDENTIFIER,					    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"fixture identifier"	},
/**/{	IPTC_APP_NUM,									    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"num"	},
	{	IPTC_APP_KEYWORDS,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	64,		"keywords"  },
	{	IPTC_APP_CONTENT_LOCATION_CODE,					    	IPTC_DATASET_TEXT,		IPTC_DATASET_REPEATABLE,	IPTC_DATASET_FIXED,	3,		"content location code" },
	{	IPTC_APP_CONTENT_LOCATION_NAME,					    	IPTC_DATASET_TEXT,		IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	64,		"content location name" },
	{	IPTC_APP_RELEASE_DATE,							    	IPTC_DATASET_DATE,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	8,		"release date"  },
	{	IPTC_APP_RELEASE_TIME,							    	IPTC_DATASET_TIME,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	11,		"release time"  },
	{	IPTC_APP_EXPIRATION_DATE,						    	IPTC_DATASET_DATE,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	8,		"expiration date"	},
	{	IPTC_APP_EXPIRATION_TIME,						    	IPTC_DATASET_TIME,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"expiration time"	},
	{	IPTC_APP_SPECIAL_INSTRUCTIONS,					    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	256,	"special instructions"  },
	{	IPTC_APP_ACTION_ADVISED,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"action advised"	},
	{	IPTC_APP_REFERENCE_SERVICE,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	10,		"reference service" },
	{	IPTC_APP_REFERENCE_DATE,						    	IPTC_DATASET_DATE,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_FIXED,	8,		"reference date"	},
	{	IPTC_APP_REFERENCE_NUMBER,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	8,		"reference number"  },
	{	IPTC_APP_DATE_CREATED,							    	IPTC_DATASET_DATE,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	8,		"date created"  },
/**/{	IPTC_APP_ARCHIVE_DATE,									IPTC_DATASET_DATE,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	8,		"archive date"  },
	{	IPTC_APP_TIME_CREATED,							    	IPTC_DATASET_TIME,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	11,		"time created"  },
	{	IPTC_APP_DIGITAL_CREATION_DATE,					    	IPTC_DATASET_DATE,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	8,		"digital creation date" },
	{	IPTC_APP_DIGITAL_CREATION_TIME,					    	IPTC_DATASET_TIME,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	11,		"digital creation time" },
	{	IPTC_APP_ORIGINATING_PROGRAM,					    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"originating program"	},
	{	IPTC_APP_PROGRAM_VERSION,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	10,		"program version"	},
	{	IPTC_APP_OBJECT_CYCLE,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	1,		"object cycle"  },
	{	IPTC_APP_BYLINE,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	32,		"byline (author)"	},
	{	IPTC_APP_BYLINE_TITLE,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	32,		"byline title (author position)"	},
/**/{	IPTC_APP_TITLE_ORIGINAL,								IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"title original"	},
/**/{	IPTC_APP_PRODUCTOR,										IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"productor" },
/**/{	IPTC_APP_ACTOR,											IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"actor" },
	{	IPTC_APP_CITY,									    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"city"  },
	{	IPTC_APP_SUBLOCATION,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"sub-location"  },
	{	IPTC_APP_PROVINCE_STATE,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"state/province"	},
	{	IPTC_APP_COUNTRY_CODE,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	3,		"country/primary location code"  },
	{	IPTC_APP_COUNTRY,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	64,		"country/primary location name"  },
	{	IPTC_APP_ORIGINAL_TRANSMISSION_REFERENCE,		    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"original transmission reference"	},
	{	IPTC_APP_HEADLINE,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	256,	"headline"  },
	{	IPTC_APP_CREDIT,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"credit"	},
/**/{	IPTC_APP_PHOTOGRAPHER_NUMBER,							IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"photographer number"	},
	{	IPTC_APP_SOURCE,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	32,		"source"	},
	{	IPTC_APP_COPYRIGHT_NOTICE,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	128,	"copyright notice"  },
	{	IPTC_APP_CONTACT,								    	IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	128,	"contact"	},
	{	IPTC_APP_CAPTION_ABSTRACT,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	2000,	"caption/abstract"	},
/**/{	IPTC_APP_PHOTOGRAPH_NUMBER,								IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"photograph number" },
	{	IPTC_APP_WRITER_EDITOR,									IPTC_DATASET_TEXT,  	IPTC_DATASET_REPEATABLE,	IPTC_DATASET_MAX,	32,		"writer/editor"	},
/**/{	IPTC_APP_SERIAL_NUMBER,									IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"serial number" },
	{	IPTC_APP_RASTERIZED_CAPTION,					    	IPTC_DATASET_BINARY,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	7360,	"rasterized caption"	},
	{	IPTC_APP_IMAGE_TYPE,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"image type"	},
	{	IPTC_APP_IMAGE_ORIENTATION,						    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"image orientation" },
/**/{	IPTC_APP_REFERENCE,										IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0,		"reference"  },
	{	IPTC_APP_LANGUAGE_IDENTIFIER,					    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	3,		"language identifier"	},
	{	IPTC_APP_AUDIO_TYPE,							    	IPTC_DATASET_TEXT,  	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"audio type"	},
	{	IPTC_APP_AUDIO_SAMPLING_RATE,					    	IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	6,		"audio sampling rate"	},
	{	IPTC_APP_AUDIO_SAMPLING_RESOLUTION,				    	IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"audio sampling resolution"	},
	{	IPTC_APP_AUDIO_DURATION,						    	IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	6,		"audio duration"	},
	{	IPTC_APP_AUDIO_OUTCUE,							    	IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	64,		"audio outcue"	},
	{	IPTC_APP_OBJECTDATA_PREVIEW_FILE_FORMAT,		    	IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"objectdata preview file format"	},
	{	IPTC_APP_OBJECTDATA_PREVIEW_FILE_FORMAT_VERSION,    	IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"objectdata preview file format version"	},
	{	IPTC_APP_OBJECTDATA_PREVIEW_PREVIEW_DATA,		    	IPTC_DATASET_TEXT,		IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	256000,	"objectdata preview data"	},
	/* newsphoto */
	{	IPTC_NP_RECORD_VERSION,									IPTC_DATASET_SPECIFIC,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"record version"	},
	{	IPTC_NP_PICTURE_NUMBER,									IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	16,		"picture numver"	},
	{	IPTC_NP_PIXELS_PER_LINE,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"pixels per line"	},
	{	IPTC_NP_NUMBER_OF_LINES,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"number of lines"	},
	{	IPTC_NP_PIXEL_SIZE_IN_SCANNING_DIRECTION,				IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"pixel size in scanning direction"	},
	{	IPTC_NP_PIXEL_SIZE_PERPENDICULAR_TO_SCANNING_DIRECTION,	IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"pixel size perpendicular to scanning direction"	},
	{	IPTC_NP_SUPPLEMENTARY_TYPE,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"supplementary type"	},
	{	IPTC_NP_COLOUR_REPRESENTATION,							IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"colour representation"	},
	{	IPTC_NP_INTERCHANGE_COLOUR_SPACE,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"inter-change colour space"	},
	{	IPTC_NP_COLOUR_SEQUENCE,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	4,		"colour sequence"	},
	{	IPTC_NP_ICC_INPUT_COLOUR_PROFILE,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	524288,	"ICC input colour profile"	},
	{	IPTC_NP_COLOUR_CALIBRATION_MATRIX_TABLE,				IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0xffff,	"colour calibration matrix table"	},
	{	IPTC_NP_LOOKUP_TABLE,									IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	131072,	"lookup table"	},
	{	IPTC_NP_NUMBER_OF_INDEX_ENTRIES,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	2,		"number of index entries"	},
	{	IPTC_NP_COLOUR_PALETTE,									IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	524288,	"colour palette"	},
	{	IPTC_NP_NUMBER_OF_BITS_PER_SAMPLE,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"number of bits per sample"	},
	{	IPTC_NP_SAMPLING_STRUCTURE,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"sampling structure"	},
	{	IPTC_NP_SCANNING_DIRECTION,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"scanning direction"	},
	{	IPTC_NP_IMAGE_ROTATION,									IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"image rotation"	},
	{	IPTC_NP_DATA_COMPRESSION_METHOD,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	4,		"data compression method"	},
	{	IPTC_NP_QUANTISATION_METHOD,							IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"quantisation method"	},
	{	IPTC_NP_END_POINTS,										IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0xffff,	"end points"	},
	{	IPTC_NP_EXCURSION_TOLERANCE,							IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"excursion tolerance"	},
	{	IPTC_NP_BITS_PER_COMPONENT,								IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	4,		"bits per component"	},
	{	IPTC_NP_MAXIMUM_DENSITY_RANGE,							IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"maximum density range"	},
	{	IPTC_NP_GAMMA_COMPENSATED_VALUE,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	2,		"gamma compensated value"	},
	/* pre-objectdata descriptor record */
 	{	IPTC_PRODD_SIZE_MODE,									IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_FIXED,	1,		"size mode"	},
 	{	IPTC_PRODD_MAX_SUBFILE_SIZE,							IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	4,		"max subfile size"	},
 	{	IPTC_PRODD_OBJECTDATA_SIZE_ANNOUNCED,					IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	4,		"objectdata size announced"	},
 	{	IPTC_PRODD_MAXIMAL_OBJECTDATA_SIZE,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	4,		"maximal objectdata size"	},
	/* objectdata descriptor record */
	/* here the 0x7fffffff limit (max size of long signed int) is the effective limit (not because undocumented) */
 	{	IPTC_ODD_SUBFILE,										IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	0x7fffffff,	"subfile"	},
	/* post-objectdata descriptor record */
 	{	IPTC_POODD_CONFIRMED_OBJECT_SIZE,						IPTC_DATASET_BINARY,	IPTC_DATASET_UNIQUE,		IPTC_DATASET_MAX,	4,	"confirmed object size"	},
	/* reserved */
	{	IPTC_EOT, IPTC_DATASET_UNKNOWN, IPTC_DATASET_UNIQUE, IPTC_DATASET_MAX, 0, NULL }
};

struct iptc_filter_match_data** iptc_filter_table=NULL;
unsigned short int iptc_filter_table_size=0;
unsigned short int iptc_filter_table_inc_size=0;
unsigned short int iptc_filter_table_filters=0;

unsigned char* iptc_extract_buffer;
unsigned long int iptc_extract_buffer_size;

struct iptc_edit_list_cell* iptc_edit_list=NULL;
unsigned short int iptc_edit_list_edits=0;

/* IPTC-NAA IM4.1 */
/* attempts to recognize all IPTC codes, and display data of known ones */
void iptc_parse(unsigned char* buffer, const long int bytes_left, const bool iptc_expected,
		struct parser_result* result)
{
	struct iptc_code_descr* code_descr;
	unsigned short int* iptc_datasets_history;
	unsigned char* ptr;
	unsigned char* value;
	unsigned long int size;
	unsigned long int written_bytes;
	unsigned long int iptc_datasets;
	long int missing;
	long int bytes;
	unsigned short int code;
	long int length;
	unsigned short int extended_length;
	unsigned char record;
	unsigned char last_record;
	unsigned char tag;
	bool ret;
	bool saw_warning_missing_record_version[256];
	bool saw_warning_duplicate_record_version[256];

	iptc_datasets=0;
	ptr=buffer;
	bytes=bytes_left;
	written_bytes=0;
	missing=0;
	ret=true;
	debug("IPTC parser: %ld byte(s) to parse", bytes);

	/* reset table of encountered datasets (to manage repeatable ones) */
	if (prefs.technical || (prefs.rewrite && (prefs.filter_mode&FILTER_EDIT)))
	{
		/* limit count to 65535 per-record occurrences */
		size=0x10000UL*sizeof(unsigned short int);
		iptc_datasets_history=malloc(size);
		if (!iptc_datasets_history)
			fatal_error("IPTC parser: could not allocate dataset history buffer");
		memset(iptc_datasets_history, 0, size);
	}
	else
		iptc_datasets_history=NULL;
	memset(saw_warning_missing_record_version, false, 256*sizeof(bool));
	memset(saw_warning_duplicate_record_version, false, 256*sizeof(bool));

	/* records must be encountered following a numerical value order */
	last_record=0;

	while (bytes>0)
	{
		if (bytes<5)
		{
			ret=false;
			error("IPTC parser: record underrun (%ld byte(s) remaining, 5 or more expected)",
					bytes);
			break;
		}

		/* IPTC tag marker expected */
		tag=getbyte(ptr, 0);
		if ((tag!=IPTC_MARKER) && iptc_expected)
		{
			ret=false;
			error("IPTC parser: expected tag marker not matched");
			break;
		}
		
		/* IPTC code (record number + code_descr number) expected */
		record=getbyte(ptr, 1);
		if (prefs.technical && (record==0))
			warning("IPTC parser: invalid record number 0");
		/* check if records are encountered following a numerical value order */
		if (prefs.technical && (record<last_record))
			warning("IPTC parser: invalid record order (got 0x%02xxx, last one was 0x%02xxx)",
					record, last_record);
		last_record=record;
		code=(record<<8)|getbyte(ptr, 2);
		code_descr=iptc_match_code((const enum IPTC_CODE)code);
		if (!code_descr)
			warning("IPTC parser: unknown IPTC code 0x%04x", code);
		else
		{
			debug("IPTC parser: code 0x%04x (%s)", code, code_descr->label);
			/* update datasets history, check if this dataset occurrence is expected */
			if (	prefs.technical
				&&	(code_descr->repeatable==IPTC_DATASET_UNIQUE)
				&&	iptc_datasets_history[code])
				error("IPTC parser: dataset not repeatable but already found %u time(s), this new one should be ignored",
						iptc_datasets_history[code]);
		}
		if (prefs.technical)
		{
			if ((code&0x00ff)==0)
			{
				/* is it a duplicate record version code? */
				if (	!saw_warning_duplicate_record_version[record]
					&&	(iptc_datasets_history[code]>0))
				{
					saw_warning_duplicate_record_version[record]=true;
					warning("IPTC parser: more than one record version found (code: 0x%04x)",
							code);
				}
			}
			else
			{
				/* did we meet a record version code for this record type yet? */
				if (	!saw_warning_missing_record_version[record]
					&&	(iptc_datasets_history[code&0xff00]==0))
				{
					saw_warning_missing_record_version[record]=true;
					warning("IPTC parser: no record version found yet for code 0x%04x (expected code: 0x%04x)",
							code, code&0xff00);
				}
			}
		}
		if (prefs.technical || (prefs.rewrite && (prefs.filter_mode&FILTER_EDIT)))
			iptc_stats_table_inc(code, iptc_datasets_history);
		iptc_datasets++;

		/* IPTC dataset length */
		length=getword(ptr, 3);
		/* extended dataset */
		if (length>0x7fff)
		{
			extended_length=length&0x7fff;
			switch (extended_length)
			{
				case 1:
					length=getbyte(ptr, 3+2);
					break;
				case 2:
					length=getword(ptr, 3+2);
					break;
				case 4:
					length=getlong(ptr, 3+2);
					break;
				default:
					/* TODO: manage extended datasets where extended_length is not 1, 2 or 4 */
					error("IPTC parser: extended dataset found with an unsupported length: %u",
							extended_length);
			}
		}
		else
			extended_length=0;
		/* warn if length doesn't match the expected one */
		if (prefs.technical && code_descr)
		{
			if (code_descr->fixed_length)
			{
				if (length!=code_descr->length)
					warning("IPTC parser: dataset 0x%04x: length (%lu byte(s)) doesn't match the expected length (%lu byte(s))",
							code, length, code_descr->length);
			}
			else
				if ((code_descr->length>0) && (length>code_descr->length))
					warning("IPTC parser: dataset 0x%04x: length (%lu byte(s)) exceeds the expected max length (%lu byte(s))",
							code, length, code_descr->length);
		}

		/* dump dataset header */
		if ((prefs.op==OP_DUMP) || (prefs.op==OP_DUMP_FULL))
		{
			printf("IPTC ");
#ifdef IPTC_SHOW_NUMERICAL_ID
			printf("%d:%03d", (code&0xff00U)>>8, code&0xffU);
#else
			printf("0x%04x", code);
#endif
			printf(" %s", code_descr?code_descr->label:"<unknown dataset>");
		}

		/* IPTC dataset value */
		missing=bytes-(1+2+2+extended_length+length);
		if (missing<0)
		{
			message(prefs.fix?SEV_WARNING:SEV_ERROR, "IPTC parser: dataset 0x%04x: underrun (%ld byte(s) left, %ld expected)%s",
					code, bytes, 1+2+2+extended_length+length, prefs.fix?" - about to be fixed":"");
			if (prefs.fix)
			{
				missing=abs(missing);
				prefs.context_fixes++;
				prefs.fixes++;
			}
			else
			{
				/* don't fix missing bytes, report an error */
				missing=0;
				ret=false;
			}
			if (bytes==0)
			{
				if ((prefs.op==OP_DUMP) || (prefs.op==OP_DUMP_FULL))
					printf("\n");
				break;
			}
			/* show remaining bytes even if dataset seems corrupted */
			length=bytes;
			length-=1+2+2+extended_length;
		}
		else
			missing=0;
		value=malloc((length+missing+1)*sizeof(unsigned char));
		if (!value)
		{
			if ((prefs.op==OP_DUMP) || (prefs.op==OP_DUMP_FULL))
				printf("\n");
			fatal_error("IPTC parser: malloc(%lu) for dataset 0x%04x",
					(length+1)*sizeof(unsigned char), code);
		}
		memcpy(value, ptr+1+2+2+extended_length, length);
		/* are there missing bytes to fix? */
		if (missing)
			/* if so, fill them with 0 */
			memset(value+(length*sizeof(unsigned char)), 0xBA,
					missing*sizeof(unsigned char));
		/* if the value is a string, it will be possible to use its value
		   as a 0-terminated string */
		value[length+missing]=0;

		/* filter edit: insertion of chunks whose code is lesser than current code */
		if (prefs.rewrite && (prefs.filter_mode&FILTER_EDIT))
			written_bytes+=iptc_edit_insert_chunks(false, code, iptc_datasets_history);

		/* conditional rewrite of the full dataset (incl. tag and header)
		   in include/exclude filter mode or in filter edit mode if the current
		   code has to be overwritten with a chunk in edit table */
		if (prefs.rewrite)
		{
			if (iptc_filter_match(code))
			{
				written_bytes+=dump_rewrite(ptr, MIN(1+2+2+extended_length+length, bytes));
				if (missing>0)
				{
					debug("IPTC parser: writing %ld byte(s) to fix the underrun",
							missing);
					written_bytes+=dump_rewrite(ptr+1+2+2+length, missing);
				}
			}
			else
			{
				/* not writing this record */
				if (prefs.filter_mode&FILTER_EDIT)
					/* update the IPTC code stats table (or filter edits will get tricked) */
					iptc_stats_table_dec(code, iptc_datasets_history);
			}
		}

		/* skip dataset (incl. tag and header) */
		ptr+=1+2+2+length;
		bytes-=1+2+2+length;

		/* dump full dataset values */
		if (prefs.op==OP_DUMP_FULL)
		{
			printf(" ");
			iptc_dump_value(code, code_descr, value, length, true, true);
		}
		else
			if (prefs.op==OP_DUMP_VALUE)
			{
				if (iptc_filter_match(code))
				{
					debug("IPTC parser: matching IPTC code 0x%04x", code);
					iptc_dump_value(code, code_descr, value, length, false, false);
				}
			}
			else
				if (prefs.op==OP_DUMP)
					printf("\n");

		free(value);
		value=NULL;
	}

	/* filter edit: insertion of remaining chunks */
	if (prefs.rewrite && (prefs.filter_mode&FILTER_EDIT))
		written_bytes+=iptc_edit_insert_chunks(true, 0, iptc_datasets_history);

	if (prefs.technical)
	{
		/* a posteriori check if record version code has been found when it should have been */
/*
		unsigned short int record_type;

		for (record_type=0x0100;
			record_type<=0x0300;
			record_type+=0x0100)
		{
			if (iptc_datasets_history[record_type]==0)
			{
				register unsigned short int dataset;

				for (dataset=1; dataset<=0xff; dataset++)
					if (iptc_datasets_history[record_type+dataset]>0)
					{
						warning("IPTC parser: no record version code found at all for record type 0x%02x",
								record_type>>8);
						break;
					}
			}
		}
*/
		register unsigned short int record_type;

		for (record_type=0x01; record_type<=0xff; record_type++)
			if (saw_warning_missing_record_version[record_type])
				warning("IPTC parser: no record version code found at all for record type 0x%02xxx",
						record_type);
	}
	if (prefs.technical || (prefs.rewrite && (prefs.filter_mode&FILTER_EDIT)))
	{
		free(iptc_datasets_history);
		iptc_datasets_history=NULL;
	}
	debug("IPTC parser: %u dataset(s) found, total length: %lu byte(s)",
			iptc_datasets, bytes_left-bytes);
	if (prefs.rewrite)
		debug("IPTC parser: %lu byte(s) rewritten", written_bytes);
	result->parsed_bytes=bytes_left-bytes;
	result->written_bytes=written_bytes;
	result->ret=ret;

	if (prefs.extract_iptc)
	{
		if (missing>0)
		{
			if (!prefs.fix)
				error("IPTC parser: unexpected state: fixing not enabled but there are %ld byte(s) marked for fixing (extraction)",
						missing);
			else
				debug("IPTC parser: there are %ld byte(s) marked for fixing (extraction)",
						missing);
		}

		if (prefs.rewrite_cached)
		{
			/* copy cached rewritten ITPC chunk for later saving */
			iptc_extract_buffer_size=result->written_bytes;
			iptc_extract_buffer=(unsigned char*)malloc(iptc_extract_buffer_size*sizeof(unsigned char));
			if (!iptc_extract_buffer)
				fatal_error("IPTC parser: couldn't allocate space for IPTC extract buffer");
			memcpy(iptc_extract_buffer,
					rewrite_cache+(rewrite_cache_offset-iptc_extract_buffer_size),
					iptc_extract_buffer_size);
			debug("IPTC parser: IPTC chunk extraction prepared from write cache: %lu bytes(s)",
					iptc_extract_buffer_size);
		}
		else
		{
			/* copy ITPC chunk from input flow for later saving */
			iptc_extract_buffer_size=result->parsed_bytes+missing;
			iptc_extract_buffer=(unsigned char*)malloc(iptc_extract_buffer_size*sizeof(unsigned char));
			if (!iptc_extract_buffer)
				fatal_error("IPTC parser: couldn't allocate space for IPTC extract buffer");
			memcpy(iptc_extract_buffer, buffer, iptc_extract_buffer_size);
			debug("IPTC parser: IPTC chunk extraction prepared from input flow: %lu bytes(s)",
					iptc_extract_buffer_size);
		}
	}
}

void iptc_stats_table_inc(unsigned int code, unsigned short int* iptc_datasets_history)
{
	/* update the IPTC code stats table */
	if (iptc_datasets_history[code]==0xffff)
		error("iptc_stats_table_inc: datasets history overflow (0x%04x)", code);
	else
		iptc_datasets_history[code]++;
}

void iptc_stats_table_dec(unsigned int code, unsigned short int* iptc_datasets_history)
{
	/* update the IPTC code stats table */
	if (iptc_datasets_history[code]==0)
		error("iptc_stats_table_inc: datasets history underflow (0x%04x)", code);
	else
		iptc_datasets_history[code]--;
}

struct iptc_code_descr* iptc_match_code(const enum IPTC_CODE code)
{
	register unsigned int i;

	i=0;
	while (iptc_codes_descr[i].code!=IPTC_EOT)
	{
		if (iptc_codes_descr[i].code==code)
			return(&iptc_codes_descr[i]);
		i++;
	}
	return(NULL);
}

void iptc_dump_value(unsigned short int code, struct iptc_code_descr* code_descr, unsigned char* value,
			unsigned short int length, bool dump_length, bool manage_newline)
{
	if (!code_descr)
		dump_hexa(value, length);
	else
	{
		/* dataset value formatting */
		switch (code_descr->format)
		{
			case IPTC_DATASET_UNKNOWN:
			case IPTC_DATASET_BINARY:
				dump_hexa(value, length);
				break;

			case IPTC_DATASET_TEXT:
				if (dump_length)
					printf("[%u byte(s)]: '%s'\n", length, value);
				else
					printf("%s\n", (char*)value);
				break;

			case IPTC_DATASET_DATE:
				/* TODO: decode and use a date format instead */
				if (dump_length)
					printf("[%u byte(s)]: '%s'\n", length, value);
				else
					printf("%s\n", (char*)value);
				break;

			case IPTC_DATASET_TIME:
				/* TODO: decode and use a time format instead */
				if (dump_length)
					printf("[%u byte(s)]: '%s'\n", length, value);
				else
					printf("%s\n", (char*)value);
				break;

			case IPTC_DATASET_NUMERICAL:
				if (length==1)
				{
					if (dump_length)
						printf("[%u byte(s)]: %d\n", length, (char)getbyte(value, 0));
					else
						printf("%d\n", (char)getbyte(value, 0));
				}
				else
				if (length==2)
				{
					if (dump_length)
						printf("[%u byte(s)]: %d\n", length, (short int)getword(value, 0));
					else
						printf("%d\n", (short int)getword(value, 0));
				}
				else
				if (length==4)
				{
					if (dump_length)
						printf("[%u byte(s)]: %ld\n", length, (long int)getlong(value, 0));
					else
						printf("%ld\n", (long int)getlong(value, 0));
				}
				else
				{
					if (manage_newline)
						printf("\n");
					warning("IPTC parser: dataset 0x%04x: don't know how to print numerical value (%u byte(s))\n",
							code, length);
				}
				break;

			case IPTC_DATASET_SPECIFIC:
				switch ((enum IPTC_CODE)code)
				{
					case IPTC_APP_RECORD_VERSION:
					{
						if (dump_length)
							printf("[%d byte(s)]: %d.%d\n", length,
									getbyte(value, 1), getbyte(value, 0));
						else
							printf("%d.%d\n", getbyte(value, 1), getbyte(value, 0));
						break;
					}
					default:
						if (manage_newline)
							printf("\n");
						warning("IPTC parser: dataset 0x%04x: don't know how to print specific value (%u byte(s))\n",
								code, length);
						break;
				}
				break;

			default:
				break;
		}
	}
}

void iptc_dump_list(void)
{
	int i;

	printf("== IPTC datasets ==\n");
	printf("code    description\n");
	for (i=0; iptc_codes_descr[i].code!=IPTC_EOT; i++)
	{
#ifdef IPTC_SHOW_NUMERICAL_ID
		printf("%3d:%03d",	(iptc_codes_descr[i].code&0xff00U)>>8,
				iptc_codes_descr[i].code&0xffU);
#else
		printf("0x%04x ", iptc_codes_descr[i].code);
#endif
		printf(" %s\n", iptc_codes_descr[i].label);
	}
}

void iptc_filter_table_init(void)
{
	unsigned int i;

	iptc_filter_table_size=50;
	iptc_filter_table_inc_size=50;
	iptc_filter_table_filters=0;

	iptc_filter_table=malloc(iptc_filter_table_size*sizeof(struct iptc_filter_match_data*));
	if (!iptc_filter_table)
		fatal_error("iptc_filter_table_init: couldn't allocate space for IPTC filter table");

	for (i=0; i<iptc_filter_table_size; i++)
		iptc_filter_table[i]=NULL;
}

static void iptc_filter_table_realloc(void)
{
	struct iptc_filter_match_data** new_ptr;
	unsigned int new_len;
	unsigned int i;

	new_len=iptc_filter_table_size+iptc_filter_table_inc_size;
	new_ptr=realloc(iptc_filter_table, new_len*sizeof(struct iptc_filter_match_data*));
	if (!new_ptr)
		fatal_error("iptc_filter_table_realloc: couldn't reallocate IPTC filter table");

	for (i=iptc_filter_table_size; i<new_len; i++)
		new_ptr[i]=NULL;

	iptc_filter_table=new_ptr;
	iptc_filter_table_size=new_len;
}

void iptc_filter_add_value(unsigned int code)
{
	struct iptc_filter_match_data* iptc_filter;

	debug("iptc_add_filter_value(%u/%u): 0x%04x", iptc_filter_table_filters,
			iptc_filter_table_size, code);

	/* dynamically realloc filter table */
	if (iptc_filter_table_filters+1>=iptc_filter_table_size)
		iptc_filter_table_realloc();

	iptc_filter=malloc(sizeof(struct iptc_filter_match_data));
	if (!iptc_filter)
		fatal_error("iptc_filter_add_value: couldn't allocate space for IPTC filter value");

	iptc_filter->type=IPTC_FET_VALUE;
	iptc_filter->code=code;
	iptc_filter_table[iptc_filter_table_filters++]=iptc_filter;
}

void iptc_filter_add_range(unsigned int code, unsigned int code2)
{
	struct iptc_filter_match_data* iptc_filter;

	debug("iptc_add_filter_range(%u/%u): 0x%04x-%04x", iptc_filter_table_filters,
			iptc_filter_table_size, code, code2);

	/* dynamically realloc filter table */
	if (iptc_filter_table_filters+1>=iptc_filter_table_size)
		iptc_filter_table_realloc();

	iptc_filter=malloc(sizeof(struct iptc_filter_match_data));
	if (!iptc_filter)
		fatal_error("iptc_filter_add_range: couldn't allocate space for IPTC filter range");

	iptc_filter->type=IPTC_FET_RANGE;
	iptc_filter->code=code;
	iptc_filter->code2=code2;
	iptc_filter_table[iptc_filter_table_filters++]=iptc_filter;
}

void iptc_filter_table_reset(void)
{
	if (iptc_filter_table)
	{
		register unsigned int i;

		for (i=0; i<iptc_filter_table_filters; i++)
		{
			free(iptc_filter_table[i]);
			iptc_filter_table[i]=NULL;
		}
		free(iptc_filter_table);
		iptc_filter_table=NULL;
		debug("iptc_filter_table_reset: %lu IPTC filter(s) unregistered", i);
	}
}

void iptc_filter_table_dump(void)
{
	if (iptc_filter_table)
	{
		register unsigned int i;

		debug("iptc_filter_table_dump: %lu element(s) follow", iptc_filter_table_filters);
		for (i=0; i<iptc_filter_table_filters; i++)
		{
			if (iptc_filter_table[i])
			{
				if (iptc_filter_table[i]->type==IPTC_FET_VALUE)
					debug("iptc_filter_table_dump: 0x%04x",
							iptc_filter_table[i]->code);
				else
					debug("iptc_filter_table_dump: 0x%04x-0x%04x",
							iptc_filter_table[i]->code, iptc_filter_table[i]->code2);
			}
			else
				error("iptc_filter_table_dump: invalid NULL table element at offset %lu", i);
		}
	}
}

bool iptc_filter_match(unsigned int code)
/* returns true if the IPTC chunk identified by 'code' matches any code in filter table,
   false otherwise
   when filtering (include/exclude), a matching code means that it has to be kept
   otherwise if it has to be filtered out. match is not applicable in filter edit mode */
{
	bool keep;

	if (!(prefs.filter_mode&FILTER_EXCLUDE) && !(prefs.filter_mode&FILTER_INCLUDE))
	{
		/* TODO: if code corresponds to a code in edit table and that this code is not repeatable
		   then we could set keep=false if we need to override. add the override switch to
		   the CLI and do the check here */
		/* debug("IPTC filter: record code 0x%04x to keep (filter mask not applicable for this filter mode)",
				code); */
		keep=true;
	}
	else
	{
		register unsigned int i;
		bool matched;

		i=0;
		matched=false;
		while (i<iptc_filter_table_filters)
		{
			if (iptc_filter_table[i]->type==IPTC_FET_VALUE)
			{
				if (iptc_filter_table[i]->code==code)
				{
					matched=true;
					break;
				}
			}
			else
				if ((iptc_filter_table[i]->code<=code) && (code<=iptc_filter_table[i]->code2))
				{
					matched=true;
					break;
				}
			i++;
		}
		keep=(prefs.filter_mode&FILTER_EXCLUDE)?!matched:matched;
		debug("IPTC filter: record code 0x%04x to %s (filter mask %smatched)",
				code, keep?"keep":"filter out", matched?"":"not ");
	}
	return(keep);
}

unsigned short int iptc_filters(void)
{
	return(iptc_filter_table_filters);
}

void iptc_edit_list_init(void)
{
	iptc_edit_list=NULL;
	iptc_edit_list_edits=0;
}

void iptc_edit_add_edit(unsigned int code, size_t len, unsigned char* data, bool* data_in_use)
/* add sorted, to a single-linked list.
   will be rejected:
    - records whose length doesn't match the record length restrictions
    - recoreds with extended datasets (> 32767) */
{
	struct iptc_edit_list_cell* new_cell;
	struct iptc_edit_list_cell* cell;
	struct iptc_edit_list_cell* prev;
	struct iptc_edit_data* iptc_edit;
	struct iptc_code_descr* code_descr;

	debug("IPTC filter edit (%u edit(s)): 0x%04x, %lu byte(s)",
			iptc_edit_list_edits, code, len);

	code_descr=iptc_match_code(code);
	if (code_descr)
	{
		/* length and value validity checks */
		if (code_descr->fixed_length==IPTC_DATASET_FIXED)
		{
			if ((code_descr->length!=0) && ((size_t)code_descr->length!=len))
			{
				error("IPTC filter edit: dataset value length to add (%lu byte(s)) doesn't match the size restriction for record 0x%04x (expected exactly %lu byte(s)), record skipped",
						len, code, code_descr->length);
				*data_in_use=false;
				return;
			}
		}
		else
		{
			if ((code_descr->length!=0) && ((size_t)code_descr->length<len))
			{
				error("IPTC filter edit: dataset value length to add (%lu byte(s)) doesn't match the size restriction for record 0x%04x (expected max %lu byte(s)), record skipped",
						len, code, code_descr->length);
				*data_in_use=false;
				return;
			}
		}
	}
	else
		warning("IPTC filter edit: attempting to add a non-standard IPTC dataset (record 0x%04x)", code);

	if (len>0x7fff)
	{
		error("IPTC filter edit: adding extended IPTC datasets (record 0x%04x, size > 32767) is not yet supported, record skipped",
				code, len);
		*data_in_use=false;
		return;
	}

	/* allocate the edit data */
	iptc_edit=malloc(sizeof(struct iptc_edit_data));
	if (!iptc_edit)
		fatal_error("IPTC filter edit: couldn't allocate space for IPTC edit value");

	/* populate the edit data */
	iptc_edit->enabled=true;
	iptc_edit->code=code;
	iptc_edit->length=len;
	iptc_edit->data=data;
	/* allocate the list cell */
	new_cell=malloc(sizeof(struct iptc_edit_list_cell));
	if (!new_cell)
		fatal_error("IPTC filter edit: couldn't allocate space for IPTC edit list cell");
	new_cell->edit_data=iptc_edit;

	/* insert cell (sorted) */
	prev=NULL;
	cell=iptc_edit_list;
	while (cell && cell->edit_data && (cell->edit_data->code<=code))
	{
		prev=cell;
		cell=cell->next;
	}
	if (prev)
	{
		/* if the added code already exists, check if that code
		   is repeatable */
		if (prev->edit_data
			&& (prev->edit_data->code==code)
			&& (prev->edit_data->code==IPTC_DATASET_UNIQUE))
		{
			error("IPTC filter edit: record 0x%04x is not repeatable and there's already a filter edit registered for it, record skipped",
					code);
			free(new_cell);
			new_cell=NULL;
			free(iptc_edit->data);
			iptc_edit->data=NULL;
			free(iptc_edit);
			iptc_edit=NULL;
			*data_in_use=false;
			return;
		}

		new_cell->next=prev->next;
		prev->next=new_cell;
	}
	else
	{
		new_cell->next=cell;
		iptc_edit_list=new_cell;
	}

	iptc_edit_list_edits++;
	*data_in_use=true;
}

void iptc_edit_list_reset(void)
{
	if (iptc_edit_list)
	{
		struct iptc_edit_list_cell* cell;
		struct iptc_edit_list_cell* next;

		for (cell=iptc_edit_list; cell; cell=next)
		{
			if (cell->edit_data)
			{
				if (cell->edit_data->enabled)
					warning("iptc_edit_list_reset: record 0x%04x has not been added. This might be because you're attempting to add IPTC records to a file that doesn't contain any IPTC chunk",
							cell->edit_data->code);
				if (cell->edit_data->data)
				{
					free(cell->edit_data->data);
					cell->edit_data->data=NULL;
				}
				free(cell->edit_data);
				cell->edit_data=NULL;
			}
			else
				error("iptc_edit_list_reset: invalid NULL edit_data in cell %p", cell);
			next=cell->next;
			free(cell);
			cell=NULL;
		}
		iptc_edit_list=NULL;
		iptc_edit_list_edits=0;
		debug("iptc_edit_list_reset: all IPTC edit(s) have been unregistered");
	}
}

void iptc_edit_list_dump(void)
{
	debug("iptc_edit_list_dump: %lu element(s) follow(s)", iptc_edit_list_edits);
	if (iptc_edit_list)
	{
		struct iptc_edit_list_cell* cell;
		unsigned int i;

		i=0;
		for (cell=iptc_edit_list; cell; cell=cell->next)
		{
			if (cell->edit_data)
				debug("iptc_edit_list_dump: 0x%04x, length: %lu",
						cell->edit_data->code, cell->edit_data->length);
			else
				error("iptc_edit_list_dump: invalid NULL edit_data in cell %p", cell);
			i++;
		}
		if (i!=iptc_edit_list_edits)
			error("iptc_edit_list_dump: list inconsistency: counted %u cell(s), expected %u",
					i, iptc_edit_list_edits);
	}
}

unsigned short int iptc_edits(void)
{
	return(iptc_edit_list_edits);
}

size_t iptc_edit_insert_chunks(bool flush, unsigned int code, unsigned short int* iptc_datasets_history)
/* process all enabled edits in list:
   if flush is false, insert all enabled edits in list
   otherwise, insert enabled chunks when their code is lesser than 'code' */
{
	size_t written_bytes=0;

	if (iptc_edit_list)
	{
		struct iptc_edit_list_cell* cell;
		struct iptc_edit_data* edit_data;
		unsigned char dataset_header[7]; /* IPTC marker + record code (2 bytes) + dataset size (2 bytes) + version (2 bytes)*/
		unsigned char* ptr;

		if (flush)
			debug("iptc_edit_insert_chunks: about to add all IPTC datasets remaining in filter edit list");
		else
			debug("iptc_edit_insert_chunks: about to add all IPTC datasets with record code < 0x%04x",
					code);

		for (cell=iptc_edit_list; cell; cell=cell->next)
		{
			edit_data=cell->edit_data;
			if (edit_data)
			{
				if (edit_data->enabled && (flush || (edit_data->code<code)))
				{
					struct iptc_code_descr* code_descr;
					bool store_it=true;

					code_descr=iptc_match_code(edit_data->code);
					if (code_descr)
					{
						/* if code already exist, if so, check it that code must be unique */
						if ((iptc_datasets_history[edit_data->code]!=0)
							&& (code_descr->repeatable==IPTC_DATASET_UNIQUE))
						{
							error("iptc_edit_insert_chunks: attempting to add an IPTC record that already exist (it's not repeatable): 0x%04x, record skipped",
									edit_data->code);
							store_it=false;
						}
						/* check length */
						if (code_descr->fixed_length==IPTC_DATASET_FIXED)
						{
							if ((code_descr->length!=0) && ((size_t)code_descr->length!=edit_data->length))
							{
								error("iptc_edit_insert_chunks: dataset value length to add (%lu byte(s)) doesn't match the size restriction for record 0x%04x (expected exactly %lu byte(s)), record skipped",
										edit_data->length, code, code_descr->length);
								store_it=false;
							}
						}
						else
						{
							if ((code_descr->length!=0) && ((size_t)code_descr->length<edit_data->length))
							{
								error("iptc_edit_insert_chunks: dataset value length to add (%lu byte(s)) doesn't match the size restriction for record 0x%04x (expected max %lu byte(s)), record skipped",
										edit_data->length, code, code_descr->length);
								store_it=false;
							}
						}
					}
					else
					{
						/* store non-standard IPTC record unconditionally */
						warning("iptc_edit_insert_chunks: adding non standard IPTC record 0x%04x",
								edit_data->code);
					}

					/* TODO: support writting extended datasets */
					if (edit_data->length>0x7fff)
					{
						error("iptc_edit_insert_chunks: adding extended IPTC datasets (length > 32767) is not supported yet, record skipped");
						store_it=false;
					}

					/* write chunk */
					if (store_it)
					{
						/* if no version record already found, insert it (if it's not a record version itself) */
						if (((edit_data->code&0x00ff)!=0)
							&& (0x0100<=(edit_data->code&0xff00) && (edit_data->code&0xff00)<=0x0300)
							&& (iptc_datasets_history[edit_data->code&0xff00]==0))
						{
							warning("iptc_edit_insert_chunks: no IPTC version record already exist for 0x%04x, adding record code 0x%04x",
									edit_data->code, edit_data->code&0xff00);
							ptr=dataset_header;
							*ptr++=IPTC_MARKER;
							*ptr++=(edit_data->code&0xff00)>>8;	/* record code (high bits) */
							*ptr++=0x00;						/* record code (low bits) */
							*ptr++=0x00;						/* dataset length (low bits) */
							*ptr++=0x02;						/* dataset length (high bits) */
							*ptr++=0x00;						/* minor version number (arbitrary) */
							*ptr++=0x02;						/* major version number (arbitrary) */
							written_bytes+=dump_rewrite(dataset_header, 7);
							iptc_stats_table_inc(edit_data->code&0xff00, iptc_datasets_history);
						}

						ptr=dataset_header;
						*ptr++=IPTC_MARKER;
						*ptr++=(edit_data->code&0xff00)>>8;
						*ptr++=edit_data->code&0x00ff;
						*ptr++=(edit_data->length&0xff00)>>8;
						*ptr++=edit_data->length&0x00ff;
						written_bytes+=dump_rewrite(dataset_header, 5);
						written_bytes+=dump_rewrite(edit_data->data, edit_data->length);
						iptc_stats_table_inc(edit_data->code, iptc_datasets_history);
						debug("iptc_edit_insert_chunks: IPTC dataset added (record 0x%04x, %lu byte(s))",
								edit_data->code, edit_data->length);
					}
					else
						debug("iptc_edit_insert_chunks: IPTC dataset NOT added (record 0x%04x), see error above",
								edit_data->code);

					/* disable edit in list */
					edit_data->enabled=false;
				}
			}
			else
				error("iptc_edit_insert_chunks: invalid NULL edit_data in cell %p", cell);
		}
	}

	return(written_bytes);
}
