// ==========================================================
// Metadata functions implementation
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================

#ifdef _MSC_VER 
#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
#endif

#include "FreeImage.h"
#include "Utilities.h"
#include "FreeImageTag.h"

// ----------------------------------------------------------
//   IPTC JPEG / TIFF markers routines
// ----------------------------------------------------------

static const char* IPTC_DELIMITER = ";";	// keywords/supplemental category delimiter
/**
	Read and decode IPTC binary data
*/
BOOL 
read_iptc_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
	char defaultKey[16];
	size_t length = datalen;
	BYTE *profile = (BYTE*)dataptr;

	std::string Keywords;
	std::string SupplementalCategory;

	WORD tag_id;

	if(!dataptr || (datalen == 0)) {
		return FALSE;
	}

	// create a tag

	FITAG *tag = FreeImage_CreateTag();

	TagLib& tag_lib = TagLib::instance();

    // find start of the BIM portion of the binary data
    size_t offset = 0;
	while(offset < length - 1) {
		if((profile[offset] == 0x1C) && (profile[offset+1] == 0x02))
			break;
		offset++;
	}

    // for each tag
    while (offset < length) {

        // identifies start of a tag
        if (profile[offset] != 0x1c) {
            break;
        }
        // we need at least five bytes left to read a tag
        if ((offset + 5) >= length) {
            break;
        }

        offset++;

		int directoryType	= profile[offset++];
        int tagType			= profile[offset++];;
        int tagByteCount	= ((profile[offset] & 0xFF) << 8) | (profile[offset + 1] & 0xFF);
        offset += 2;

        if ((offset + tagByteCount) > length) {
            // data for tag extends beyond end of iptc segment
            break;
        }

		// process the tag

		tag_id = (WORD)(tagType | (directoryType << 8));

		FreeImage_SetTagID(tag, tag_id);
		FreeImage_SetTagLength(tag, tagByteCount);

		// allocate a buffer to store the tag value
		BYTE *iptc_value = (BYTE*)malloc((tagByteCount + 1) * sizeof(BYTE));
		memset(iptc_value, 0, (tagByteCount + 1) * sizeof(BYTE));

		// get the tag value

		switch (tag_id) {
			case TAG_RECORD_VERSION:
			{
				// short
				FreeImage_SetTagType(tag, FIDT_SSHORT);
				FreeImage_SetTagCount(tag, 1);
				short *pvalue = (short*)&iptc_value[0];
				*pvalue = (short)((profile[offset] << 8) | profile[offset + 1]);
				FreeImage_SetTagValue(tag, pvalue);
				break;
			}

			case TAG_RELEASE_DATE:
			case TAG_DATE_CREATED:
				// Date object
			case TAG_RELEASE_TIME:
			case TAG_TIME_CREATED:
				// time
			default:
			{
				// string
				FreeImage_SetTagType(tag, FIDT_ASCII);
				FreeImage_SetTagCount(tag, tagByteCount);
				for(int i = 0; i < tagByteCount; i++) {
					iptc_value[i] = profile[offset + i];
				}
				iptc_value[tagByteCount] = '\0';
				FreeImage_SetTagValue(tag, (char*)&iptc_value[0]);
				break;
			}
		}

		if(tag_id == TAG_SUPPLEMENTAL_CATEGORIES) {
			// concatenate the categories
			if(SupplementalCategory.length() == 0) {
				SupplementalCategory.append((char*)iptc_value);
			} else {
				SupplementalCategory.append(IPTC_DELIMITER);
				SupplementalCategory.append((char*)iptc_value);
			}
		}
		else if(tag_id == TAG_KEYWORDS) {
			// concatenate the keywords
			if(Keywords.length() == 0) {
				Keywords.append((char*)iptc_value);
			} else {
				Keywords.append(IPTC_DELIMITER);
				Keywords.append((char*)iptc_value);
			}
		}
		else {
			// get the tag key and description
			const char *key = tag_lib.getTagFieldName(TagLib::IPTC, tag_id, defaultKey);
			FreeImage_SetTagKey(tag, key);
			const char *description = tag_lib.getTagDescription(TagLib::IPTC, tag_id);
			FreeImage_SetTagDescription(tag, description);

			// store the tag
			if(key) {
				FreeImage_SetMetadata(FIMD_IPTC, dib, key, tag);
			}
		}

		free(iptc_value);

        // next tag
		offset += tagByteCount;

    }

	// store the 'keywords' tag
	if(Keywords.length()) {
		FreeImage_SetTagType(tag, FIDT_ASCII);
		FreeImage_SetTagID(tag, TAG_KEYWORDS);
		FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_KEYWORDS, defaultKey));
		FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_KEYWORDS));
		FreeImage_SetTagLength(tag, (DWORD)Keywords.length());
		FreeImage_SetTagCount(tag, (DWORD)Keywords.length());
		FreeImage_SetTagValue(tag, (char*)Keywords.c_str());
		FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
	}

	// store the 'supplemental category' tag
	if(SupplementalCategory.length()) {
		FreeImage_SetTagType(tag, FIDT_ASCII);
		FreeImage_SetTagID(tag, TAG_SUPPLEMENTAL_CATEGORIES);
		FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES, defaultKey));
		FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES));
		FreeImage_SetTagLength(tag, (DWORD)SupplementalCategory.length());
		FreeImage_SetTagCount(tag, (DWORD)SupplementalCategory.length());
		FreeImage_SetTagValue(tag, (char*)SupplementalCategory.c_str());
		FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
	}

	// delete the tag

	FreeImage_DeleteTag(tag);

	return TRUE;
}

// --------------------------------------------------------------------------

static BYTE* 
append_iptc_tag(BYTE *profile, unsigned *profile_size, WORD id, DWORD length, const void *value) {
	BYTE *buffer = NULL;

	// calculate the new buffer size
	size_t buffer_size = (5 + *profile_size + length) * sizeof(BYTE);
	buffer = (BYTE*)malloc(buffer_size);
	if(!buffer)
		return NULL;

	// add the header
	buffer[0] = 0x1C;
	buffer[1] = 0x02;
	// add the tag type
	buffer[2] = (BYTE)(id & 0x00FF);
	// add the tag length
	buffer[3] = (BYTE)(length >> 8);
	buffer[4] = (BYTE)(length & 0xFF);
	// add the tag value
	memcpy(buffer + 5, (BYTE*)value, length);
	// append the previous profile
	if(NULL == profile)	{
		*profile_size = (5 + length);
	}
	else {
		memcpy(buffer + 5 + length, profile, *profile_size);
		*profile_size += (5 + length);
		free(profile);
	}
	
	return buffer;
}

/**
Encode IPTC metadata into a binary buffer. 
The buffer is allocated by the function and must be freed by the caller. 
*/
BOOL 
write_iptc_profile(FIBITMAP *dib, BYTE **profile, unsigned *profile_size) {
	FITAG *tag = NULL;
	FIMETADATA *mdhandle = NULL;

	BYTE *buffer = NULL;
	unsigned buffer_size = 0;

	// parse all IPTC tags and rebuild a IPTC profile
	mdhandle = FreeImage_FindFirstMetadata(FIMD_IPTC, dib, &tag);

	if(mdhandle) {
		do {
			WORD tag_id	= FreeImage_GetTagID(tag);

			// append the tag to the profile

			switch(tag_id) {
				case TAG_RECORD_VERSION:
					// ignore (already handled)
					break;

				case TAG_SUPPLEMENTAL_CATEGORIES:
				case TAG_KEYWORDS:
					if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
						std::string value = (const char*)FreeImage_GetTagValue(tag);

						// split the tag value
						std::vector<std::string> output;
						std::string delimiter = IPTC_DELIMITER;		
						
						size_t offset = 0;
						size_t delimiterIndex = 0;

						delimiterIndex = value.find(delimiter, offset);
						while (delimiterIndex != std::string::npos) {
							output.push_back(value.substr(offset, delimiterIndex - offset));
							offset += delimiterIndex - offset + delimiter.length();
							delimiterIndex = value.find(delimiter, offset);
						}
						output.push_back(value.substr(offset));

						// add as many tags as there are comma separated strings
						for(int i = 0; i < (int)output.size(); i++) {
							std::string& tag_value = output[i];
							buffer = append_iptc_tag(buffer, &buffer_size, tag_id, (DWORD)tag_value.length(), tag_value.c_str());
						}

					}
					break;

				case TAG_URGENCY:
					if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
						DWORD length = 1;	// keep the first octet only
						buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
					}
					break;

				default:
					if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
						DWORD length = FreeImage_GetTagLength(tag);	
						buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
					}					
					break;
			}

		} while(FreeImage_FindNextMetadata(mdhandle, &tag));
		
		FreeImage_FindCloseMetadata(mdhandle);

		// add the DirectoryVersion tag
		const short version = 0x0200;
		buffer = append_iptc_tag(buffer, &buffer_size, TAG_RECORD_VERSION, sizeof(version), &version);
		
		*profile = buffer;
		*profile_size = buffer_size;

		return TRUE;
	}

	return FALSE;
}
