// ==========================================================
// Memory Input/Output functions
//
// Design and implementation by
// - Ryan Rubley <ryan@lostreality.org> 
// - 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!
// ==========================================================

#include "FreeImage.h"
#include "Utilities.h"
#include "FreeImageIO.h"

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


// =====================================================================
// Open and close a memory handle
// =====================================================================

FIMEMORY * DLL_CALLCONV 
FreeImage_OpenMemory(BYTE *data, DWORD size_in_bytes) {
	// allocate a memory handle
	FIMEMORY *stream = (FIMEMORY*)malloc(sizeof(FIMEMORY));
	if(stream) {
		stream->data = (BYTE*)malloc(sizeof(FIMEMORYHEADER));

		if(stream->data) {
			FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(stream->data);

			// initialize the memory header
			memset(mem_header, 0, sizeof(FIMEMORYHEADER));
			
			if(data && size_in_bytes) {
				// wrap a user buffer
				mem_header->delete_me = FALSE;
				mem_header->data = (BYTE*)data;
				mem_header->data_length = mem_header->file_length = size_in_bytes;
			} else {
				mem_header->delete_me = TRUE;
			}

			return stream;
		}
		free(stream);
	}

	return NULL;
}


void DLL_CALLCONV
FreeImage_CloseMemory(FIMEMORY *stream) {
	if(stream && stream->data) {
		FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(stream->data);
		if(mem_header->delete_me) {
			free(mem_header->data);
		}
		free(mem_header);
		free(stream);
	}
}

// =====================================================================
// Memory stream load/save functions
// =====================================================================

FIBITMAP * DLL_CALLCONV
FreeImage_LoadFromMemory(FREE_IMAGE_FORMAT fif, FIMEMORY *stream, int flags) {
	if (stream && stream->data) {
		FreeImageIO io;
		SetMemoryIO(&io);

		return FreeImage_LoadFromHandle(fif, &io, (fi_handle)stream, flags);
	}

	return NULL;
}


BOOL DLL_CALLCONV
FreeImage_SaveToMemory(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, FIMEMORY *stream, int flags) {
	if (stream) {
		FreeImageIO io;
		SetMemoryIO(&io);

		FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(stream->data);

		if(mem_header->delete_me == TRUE) {
			return FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)stream, flags);
		} else {
			// do not save in a user buffer
			FreeImage_OutputMessageProc(fif, "Memory buffer is read only");
		}
	}

	return FALSE;
}

// =====================================================================
// Memory stream buffer access
// =====================================================================

BOOL DLL_CALLCONV
FreeImage_AcquireMemory(FIMEMORY *stream, BYTE **data, DWORD *size_in_bytes) {
	if (stream) {
		FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(stream->data);

		*data = (BYTE*)mem_header->data;
		*size_in_bytes = mem_header->file_length;
		return TRUE;
	}

	return FALSE;
}

// =====================================================================
// Memory stream file type access
// =====================================================================

FREE_IMAGE_FORMAT DLL_CALLCONV
FreeImage_GetFileTypeFromMemory(FIMEMORY *stream, int size) {
	FreeImageIO io;
	SetMemoryIO(&io);

	if (stream != NULL) {
		return FreeImage_GetFileTypeFromHandle(&io, (fi_handle)stream, size);
	}

	return FIF_UNKNOWN;
}

// =====================================================================
// Seeking in Memory stream
// =====================================================================

/**
Moves the memory pointer to a specified location
@param stream Pointer to FIMEMORY structure
@param offset Number of bytes from origin
@param origin Initial position
@return Returns TRUE if successful, returns FALSE otherwise
*/
BOOL DLL_CALLCONV
FreeImage_SeekMemory(FIMEMORY *stream, long offset, int origin) {
	FreeImageIO io;
	SetMemoryIO(&io);

	if (stream != NULL) {
		int success = io.seek_proc((fi_handle)stream, offset, origin);
		return (success == 0) ? TRUE : FALSE;
	}

	return FALSE;
}

/**
Gets the current position of a memory pointer
@param stream Target FIMEMORY structure
@return Returns the current file position if successful, -1 otherwise
*/
long DLL_CALLCONV
FreeImage_TellMemory(FIMEMORY *stream) {
	FreeImageIO io;
	SetMemoryIO(&io);

	if (stream != NULL) {
		return io.tell_proc((fi_handle)stream);
	}

	return -1L;
}

// =====================================================================
// Reading or Writing in Memory stream
// =====================================================================

/**
Reads data from a memory stream
@param buffer Storage location for data
@param size Item size in bytes
@param count Maximum number of items to be read
@param stream Pointer to FIMEMORY structure
@return Returns the number of full items actually read, which may be less than count if an error occurs
*/
unsigned DLL_CALLCONV 
FreeImage_ReadMemory(void *buffer, unsigned size, unsigned count, FIMEMORY *stream) {
	FreeImageIO io;
	SetMemoryIO(&io);

	if (stream != NULL) {
		return io.read_proc(buffer, size, count, stream);
	}

	return 0;
}

/**
Writes data to a memory stream.
@param buffer Pointer to data to be written
@param size Item size in bytes
@param count Maximum number of items to be written
@param stream Pointer to FIMEMORY structure
@return Returns the number of full items actually written, which may be less than count if an error occurs
*/
unsigned DLL_CALLCONV 
FreeImage_WriteMemory(const void *buffer, unsigned size, unsigned count, FIMEMORY *stream) {
	if (stream != NULL) {
		FreeImageIO io;
		SetMemoryIO(&io);

		FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(((FIMEMORY*)stream)->data);

		if(mem_header->delete_me == TRUE) {
			return io.write_proc((void *)buffer, size, count, stream);
		} else {
			// do not write in a user buffer
			FreeImage_OutputMessageProc(FIF_UNKNOWN, "Memory buffer is read only");
		}
	}

	return 0;
}