// ==========================================================
// XBM Loader
//
// Design and implementation by
// - Hervé Drolon <drolon@infonie.fr>
// part of the code adapted from the netPBM package (xbmtopbm.c)
//
// 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"

// ==========================================================
// Internal functions
// ==========================================================

#define MAX_LINE	512

static const char *ERR_XBM_SYNTAX	= "Syntax error";
static const char *ERR_XBM_LINE		= "Line too long";
static const char *ERR_XBM_DECL		= "Unable to find a line in the file containing the start of C array declaration (\"static char\" or whatever)";
static const char *ERR_XBM_EOFREAD	= "EOF / read error";
static const char *ERR_XBM_WIDTH	= "Invalid width";
static const char *ERR_XBM_HEIGHT	= "Invalid height";
static const char *ERR_XBM_MEMORY	= "Out of memory";

/**
Get a string from a stream. 
Read the string from the current stream to the first newline character. 
The result stored in str is appended with a null character.
@param str Storage location for data 
@param n Maximum number of characters to read 
@param io Pointer to the FreeImageIO structure
@param handle Handle to the stream
@return Returns str. NULL is returned to indicate an error or an end-of-file condition.
*/
static char* 
readLine(char *str, int n, FreeImageIO *io, fi_handle handle) {
	char c;
	int count, i = 0;
	do {
		count = io->read_proc(&c, 1, 1, handle);
		str[i++] = c;
	} while((c != '\n') && (i < n));
	if(count <= 0)
		return NULL;
	str[i] = '\0';
	return str;
}

/**
Get a char from the stream
@param io Pointer to the FreeImageIO structure
@param handle Handle to the stream
@return Returns the next character in the stream
*/
static int 
readChar(FreeImageIO *io, fi_handle handle) {
	BYTE c;
	io->read_proc(&c, 1, 1, handle);
	return c;
}

/**
Read an XBM file into a buffer
@param io Pointer to the FreeImageIO structure
@param handle Handle to the stream
@param widthP (return value) Pointer to the bitmap width
@param heightP (return value) Pointer to the bitmap height
@param dataP (return value) Pointer to the bitmap buffer
@return Returns NULL if OK, returns an error message otherwise
*/
static const char* 
readXBMFile(FreeImageIO *io, fi_handle handle, int *widthP, int *heightP, char **dataP) {
	char line[MAX_LINE], name_and_type[MAX_LINE];
	char* ptr;
	char* t;
	int version = 0;
	int raster_length, v;
	int bytes, bytes_per_line, padding;
	int c1, c2, value1, value2;
	int hex_table[256];
	BOOL found_declaration;
	/* in scanning through the bitmap file, we have found the first
	 line of the C declaration of the array (the "static char ..."
	 or whatever line)
	 */
	BOOL eof;	// we've encountered end of file while searching file

	*widthP = *heightP = -1;

	found_declaration = FALSE;    // haven't found it yet; haven't even looked
	eof = FALSE;                  // haven't encountered end of file yet 
	
	while(!found_declaration && !eof) {

		if( readLine(line, MAX_LINE, io, handle) == NULL) {
			eof = TRUE;
		}
		else {
			if( strlen( line ) == MAX_LINE - 1 )
				return( ERR_XBM_LINE );
			if( sscanf(line, "#define %s %d", name_and_type, &v) == 2 ) {
				if( ( t = strrchr( name_and_type, '_' ) ) == NULL )
					t = name_and_type;
				else
					t++;
				if ( ! strcmp( "width", t ) )
					*widthP = v;
				else if ( ! strcmp( "height", t ) )
					*heightP = v;
				continue;
			}

			if( sscanf( line, "static short %s = {", name_and_type ) == 1 ) {
				version = 10;
				found_declaration = TRUE;
			}
			else if( sscanf( line, "static char %s = {", name_and_type ) == 1 ) {
				version = 11;
				found_declaration = TRUE;
			}
			else if(sscanf(line, "static unsigned char %s = {", name_and_type ) == 1 ) {
				version = 11;
				found_declaration = TRUE;
			}
		}
	}

	if(!found_declaration) 
		return( ERR_XBM_DECL );

	if(*widthP == -1 )
		return( ERR_XBM_WIDTH );
	if( *heightP == -1 )
		return( ERR_XBM_HEIGHT );

	padding = 0;
	if ( ((*widthP % 16) >= 1) && ((*widthP % 16) <= 8) && (version == 10) )
		padding = 1;

	bytes_per_line = (*widthP + 7) / 8 + padding;

	raster_length =  bytes_per_line * *heightP;
	*dataP = (char*) malloc( raster_length );
	if ( *dataP == (char*) 0 )
		return( ERR_XBM_MEMORY );

	// initialize hex_table
	for ( c1 = 0; c1 < 256; c1++ ) {
		hex_table[c1] = 256;
	}
	hex_table['0'] = 0;
	hex_table['1'] = 1;
	hex_table['2'] = 2;
	hex_table['3'] = 3;
	hex_table['4'] = 4;
	hex_table['5'] = 5;
	hex_table['6'] = 6;
	hex_table['7'] = 7;
	hex_table['8'] = 8;
	hex_table['9'] = 9;
	hex_table['A'] = 10;
	hex_table['B'] = 11;
	hex_table['C'] = 12;
	hex_table['D'] = 13;
	hex_table['E'] = 14;
	hex_table['F'] = 15;
	hex_table['a'] = 10;
	hex_table['b'] = 11;
	hex_table['c'] = 12;
	hex_table['d'] = 13;
	hex_table['e'] = 14;
	hex_table['f'] = 15;

	if(version == 10) {
		for( bytes = 0, ptr = *dataP; bytes < raster_length; bytes += 2 ) {
			while( ( c1 = readChar(io, handle) ) != 'x' ) {
				if ( c1 == EOF )
					return( ERR_XBM_EOFREAD );
			}

			c1 = readChar(io, handle);
			c2 = readChar(io, handle);
			if( c1 == EOF || c2 == EOF )
				return( ERR_XBM_EOFREAD );
			value1 = ( hex_table[c1] << 4 ) + hex_table[c2];
			if ( value1 >= 256 )
				return( ERR_XBM_SYNTAX );
			c1 = readChar(io, handle);
			c2 = readChar(io, handle);
			if( c1 == EOF || c2 == EOF )
				return( ERR_XBM_EOFREAD );
			value2 = ( hex_table[c1] << 4 ) + hex_table[c2];
			if ( value2 >= 256 )
				return( ERR_XBM_SYNTAX );
			*ptr++ = (char)value2;
			if ( ( ! padding ) || ( ( bytes + 2 ) % bytes_per_line ) )
				*ptr++ = (char)value1;
		}
	}
	else {
		for(bytes = 0, ptr = *dataP; bytes < raster_length; bytes++ ) {
			/*
			** skip until digit is found
			*/
			for( ; ; ) {
				c1 = readChar(io, handle);
				if ( c1 == EOF )
					return( ERR_XBM_EOFREAD );
				value1 = hex_table[c1];
				if ( value1 != 256 )
					break;
			}
			/*
			** loop on digits
			*/
			for( ; ; ) {
				c2 = readChar(io, handle);
				if ( c2 == EOF )
					return( ERR_XBM_EOFREAD );
				value2 = hex_table[c2];
				if ( value2 != 256 ) {
					value1 = (value1 << 4) | value2;
					if ( value1 >= 256 )
						return( ERR_XBM_SYNTAX );
				}
				else if ( c2 == 'x' || c2 == 'X' ) {
					if ( value1 == 0 )
						continue;
					else return( ERR_XBM_SYNTAX );
				}
				else break;
			}
			*ptr++ = (char)value1;
		}
	}

	return NULL;
}

// ==========================================================
// Plugin Interface
// ==========================================================

static int s_format_id;

// ==========================================================
// Plugin Implementation
// ==========================================================

static const char * DLL_CALLCONV
Format() {
	return "XBM";
}

static const char * DLL_CALLCONV
Description() {
	return "X11 Bitmap Format";
}

static const char * DLL_CALLCONV
Extension() {
	return "xbm";
}

static const char * DLL_CALLCONV
RegExpr() {
	return NULL;
}

static const char * DLL_CALLCONV
MimeType() {
	return "image/x-xbitmap";
}

static BOOL DLL_CALLCONV
Validate(FreeImageIO *io, fi_handle handle) {
	char magic[8];
	if(readLine(magic, 7, io, handle)) {
		if(strcmp(magic, "#define") == 0)
			return TRUE;
	}
	return FALSE;
}

static BOOL DLL_CALLCONV
SupportsExportDepth(int depth) {
	return FALSE;
}

static BOOL DLL_CALLCONV 
SupportsExportType(FREE_IMAGE_TYPE type) {
	return FALSE;
}

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

static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
	char *buffer = NULL;
	int width, height;
	FIBITMAP *dib = NULL;

	try {

		// load the bitmap data
		const char* error = readXBMFile(io, handle, &width, &height, &buffer);
		// Microsoft doesn't implement throw between functions :(
		if(error) throw (char*)error;


		// allocate a new dib
		dib = FreeImage_Allocate(width, height, 1);
		if(!dib) throw (char*)ERR_XBM_MEMORY;

		// write the palette data
		RGBQUAD *pal = FreeImage_GetPalette(dib);
		pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 0;
		pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255;

		// copy the bitmap
		BYTE *bP = (BYTE*)buffer;
		for(int y = 0; y < height; y++) {
			BYTE count = 0;
			BYTE mask = 1;
			BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);

			for(int x = 0; x < width; x++) {
				if(count >= 8) {
					bP++;
					count = 0;
					mask = 1;
				}
				if(*bP & mask) {
					// Set bit(x, y) to 0
					bits[x >> 3] &= (0xFF7F >> (x & 0x7));
				} else {
					// Set bit(x, y) to 1
					bits[x >> 3] |= (0x80 >> (x & 0x7));
				}
				count++;
				mask <<= 1;
			}
			bP++;
		}

		free(buffer);
		return dib;

	} catch(const char *text) {
		if(buffer)	free(buffer);
		if(dib)		free(dib);
		FreeImage_OutputMessageProc(s_format_id, text);
		return NULL;
	}
}


// ==========================================================
//   Init
// ==========================================================

void DLL_CALLCONV
InitXBM(Plugin *plugin, int format_id) {
    s_format_id = format_id;

	plugin->format_proc = Format;
	plugin->description_proc = Description;
	plugin->extension_proc = Extension;
	plugin->regexpr_proc = RegExpr;
	plugin->open_proc = NULL;
	plugin->close_proc = NULL;
	plugin->pagecount_proc = NULL;
	plugin->pagecapability_proc = NULL;
	plugin->load_proc = Load;
	plugin->save_proc = NULL;
	plugin->validate_proc = Validate;
	plugin->mime_proc = MimeType;
	plugin->supports_export_bpp_proc = SupportsExportDepth;
	plugin->supports_export_type_proc = SupportsExportType;
	plugin->supports_icc_profiles_proc = NULL;
}

