// ==========================================================
// G3 Fax Loader
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
// - Petr Pytelka (pyta@lightcomp.com)
//
// 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 "../LibTIFF/tiffiop.h"

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

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

static int s_format_id;

// ==========================================================
//   Constant/Macro declarations
// ==========================================================

#define G3_DEFAULT_WIDTH	1728

#define TIFFhowmany8(x) (((x)&0x07)?((uint32)(x)>>3)+1:(uint32)(x)>>3)

// ==========================================================
//   libtiff interface 
// ==========================================================

static tsize_t 
_g3ReadProc(thandle_t fd, tdata_t buf, tsize_t size) {
	// returns an error when reading the TIFF header
	return 0;
}

static tsize_t
_g3WriteProc(thandle_t fd, tdata_t buf, tsize_t size) {
	// returns ok when writing the TIFF header
	return size;
}

static toff_t
_g3SeekProc(thandle_t fd, toff_t off, int whence) {
	return 0;
}

static int
_g3CloseProc(thandle_t fd) {
	return 0;
}

static toff_t
_g3SizeProc(thandle_t fd) {
	return 0;
}

static int
_g3MapProc(thandle_t fd, tdata_t* pbase, toff_t* psize) {
	return 0;
}

static void
_g3UnmapProc(thandle_t fd, tdata_t base, toff_t size) {
}

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

static toff_t
G3GetFileSize(FreeImageIO *io, fi_handle handle) {
    long currentPos = io->tell_proc(handle);
    io->seek_proc(handle, 0, SEEK_END);
    long fileSize = io->tell_proc(handle);
    io->seek_proc(handle, currentPos, SEEK_SET);
    return fileSize;
}

static BOOL 
G3ReadFile(FreeImageIO *io, fi_handle handle, tidata_t tif_rawdata, tsize_t tif_rawdatasize) {
	return ((tsize_t)(io->read_proc(tif_rawdata, tif_rawdatasize, 1, handle) * tif_rawdatasize) == tif_rawdatasize);
}

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

static int 
copyFaxFile(FreeImageIO *io, fi_handle handle, TIFF* tifin, uint32 xsize, int stretch, FIMEMORY *memory) {
	BYTE *rowbuf = NULL;
	BYTE *refbuf = NULL;
	uint32 row;
	uint16 badrun;
	uint16	badfaxrun;
	uint32	badfaxlines;
	int ok;

	try {

		uint32 linesize = TIFFhowmany8(xsize);
		rowbuf = (BYTE*) _TIFFmalloc(linesize);
		refbuf = (BYTE*) _TIFFmalloc(linesize);
		if (rowbuf == NULL || refbuf == NULL) {
			throw FI_MSG_ERROR_MEMORY;
		}

		tifin->tif_rawdatasize = G3GetFileSize(io, handle);
		tifin->tif_rawdata = (tidata_t) _TIFFmalloc(tifin->tif_rawdatasize);
		if (tifin->tif_rawdata == NULL) {
			throw FI_MSG_ERROR_MEMORY;
		}
			
		if(!G3ReadFile(io, handle, tifin->tif_rawdata, tifin->tif_rawdatasize)) {
			throw "Read error at scanline 0";
		}
		tifin->tif_rawcp = tifin->tif_rawdata;
		tifin->tif_rawcc = tifin->tif_rawdatasize;

		(*tifin->tif_setupdecode)(tifin);
		(*tifin->tif_predecode)(tifin, (tsample_t) 0);
		tifin->tif_row = 0;
		badfaxlines = 0;
		badfaxrun = 0;

		_TIFFmemset(refbuf, 0, linesize);
		row = 0;
		badrun = 0;		// current run of bad lines 
		while (tifin->tif_rawcc > 0) {
			ok = (*tifin->tif_decoderow)(tifin, rowbuf, linesize, 0);
			if (!ok) {
				badfaxlines++;
				badrun++;
				// regenerate line from previous good line 
				_TIFFmemcpy(rowbuf, refbuf, linesize);
			} else {
				if (badrun > badfaxrun)
					badfaxrun = badrun;
				badrun = 0;
				_TIFFmemcpy(refbuf, rowbuf, linesize);
			}
			tifin->tif_row++;

			FreeImage_WriteMemory(rowbuf, linesize, 1, memory);
			row++;
			if (stretch) {
				FreeImage_WriteMemory(rowbuf, linesize, 1, memory);
				row++;
			}
		}
		if (badrun > badfaxrun)
			badfaxrun = badrun;

		_TIFFfree(tifin->tif_rawdata);
		tifin->tif_rawdata = NULL;

		_TIFFfree(rowbuf);
		_TIFFfree(refbuf);

		/*
		if (verbose) {
			fprintf(stderr, "%d rows in input\n", rows);
			fprintf(stderr, "%ld total bad rows\n", (long) badfaxlines);
			fprintf(stderr, "%d max consecutive bad rows\n", badfaxrun);
		}
		*/

	} catch(const char *message) {
		if(rowbuf) _TIFFfree(rowbuf);
		if(refbuf) _TIFFfree(refbuf);
		if(tifin->tif_rawdata) {
			_TIFFfree(tifin->tif_rawdata);
			tifin->tif_rawdata = NULL;
		}
		FreeImage_OutputMessageProc(s_format_id, message);

		return -1;
	}

	return (row);
}


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

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

static const char * DLL_CALLCONV 
Description() {
	return "Raw fax format CCITT G.3";
}

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

static const char * DLL_CALLCONV 
RegExpr() {
	return NULL; // there is now reasonable regexp for raw G3
}

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

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

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

static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
	TIFF *faxTIFF = NULL;
	FIBITMAP *dib = NULL;
	FIMEMORY *memory = NULL;

	//int verbose = 0;
	int	stretch = 0;
	int rows;
	float resX = 204.0;
	float resY = 196.0;

	uint32 xsize = G3_DEFAULT_WIDTH;
	int compression_in = COMPRESSION_CCITTFAX3;
	int fillorder_in = FILLORDER_LSB2MSB;
	uint32 group3options_in = 0;	// 1d-encoded 
	uint32 group4options_in = 0;	// compressed 
	int photometric_in = PHOTOMETRIC_MINISWHITE;

	if(handle==NULL) return NULL;

	try {
		// set default load options

		compression_in = COMPRESSION_CCITTFAX3;			// input is g3-encoded 
		group3options_in &= ~GROUP3OPT_2DENCODING;		// input is 1d-encoded (g3 only) 
		fillorder_in = FILLORDER_MSB2LSB;				// input has msb-to-lsb fillorder 

		/*
		Original input-related fax2tiff options

		while ((c = getopt(argc, argv, "R:X:o:1234ABLMPUW5678abcflmprsuvwz?")) != -1) {
			switch (c) {
					// input-related options 
				case '3':		// input is g3-encoded 
					compression_in = COMPRESSION_CCITTFAX3;
					break;
				case '4':		// input is g4-encoded 
					compression_in = COMPRESSION_CCITTFAX4;
					break;
				case 'U':		// input is uncompressed (g3 and g4) 
					group3options_in |= GROUP3OPT_UNCOMPRESSED;
					group4options_in |= GROUP4OPT_UNCOMPRESSED;
					break;
				case '1':		// input is 1d-encoded (g3 only) 
					group3options_in &= ~GROUP3OPT_2DENCODING;
					break;
				case '2':		// input is 2d-encoded (g3 only) 
					group3options_in |= GROUP3OPT_2DENCODING;
					break;
				case 'P':	// input has not-aligned EOL (g3 only) 
					group3options_in &= ~GROUP3OPT_FILLBITS;
					break;
				case 'A':		// input has aligned EOL (g3 only) 
					group3options_in |= GROUP3OPT_FILLBITS;
					break;
				case 'W':		// input has 0 mean white 
					photometric_in = PHOTOMETRIC_MINISWHITE;
					break;
				case 'B':		// input has 0 mean black 
					photometric_in = PHOTOMETRIC_MINISBLACK;
					break;
				case 'L':		// input has lsb-to-msb fillorder 
					fillorder_in = FILLORDER_LSB2MSB;
					break;
				case 'M':		// input has msb-to-lsb fillorder 
					fillorder_in = FILLORDER_MSB2LSB;
					break;
				case 'R':		// input resolution 
					resY = (float) atof(optarg);
					break;
				case 'X':		// input width 
					xsize = (uint32) atoi(optarg);
					break;

					// output-related options 
				case 's':		// stretch image by dup'ng scanlines 
					stretch = 1;
					break;
				case 'v':		// -v for info 
					verbose++;
					break;
			}
		}

		*/

		// open a temporary memory buffer to save decoded scanlines
		memory = FreeImage_OpenMemory();
		if(!memory) throw FI_MSG_ERROR_MEMORY;
		
		// wrap the raw fax file
		faxTIFF = TIFFClientOpen("(FakeInput)", "w",
			// TIFFClientOpen() fails if we don't set existing value here 
			NULL,
			_g3ReadProc, _g3WriteProc,
			_g3SeekProc, _g3CloseProc,
			_g3SizeProc, _g3MapProc,
			_g3UnmapProc);

		if (faxTIFF == NULL) {
			throw "Can not create fake input file";
		}
		TIFFSetMode(faxTIFF, O_RDONLY);
		TIFFSetField(faxTIFF, TIFFTAG_IMAGEWIDTH, xsize);
		TIFFSetField(faxTIFF, TIFFTAG_SAMPLESPERPIXEL, 1);
		TIFFSetField(faxTIFF, TIFFTAG_BITSPERSAMPLE, 1);
		TIFFSetField(faxTIFF, TIFFTAG_FILLORDER, fillorder_in);
		TIFFSetField(faxTIFF, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
		TIFFSetField(faxTIFF, TIFFTAG_PHOTOMETRIC, photometric_in);
		TIFFSetField(faxTIFF, TIFFTAG_YRESOLUTION, resY);
		TIFFSetField(faxTIFF, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);

		// NB: this must be done after directory info is setup 
		TIFFSetField(faxTIFF, TIFFTAG_COMPRESSION, compression_in);
		if (compression_in == COMPRESSION_CCITTFAX3)
			TIFFSetField(faxTIFF, TIFFTAG_GROUP3OPTIONS, group3options_in);
		else if (compression_in == COMPRESSION_CCITTFAX4)
			TIFFSetField(faxTIFF, TIFFTAG_GROUP4OPTIONS, group4options_in);
		
		resX = 204;
		if (!stretch) {
			TIFFGetField(faxTIFF, TIFFTAG_YRESOLUTION, &resY);
		} else {
			resY = 196;
		}

		// decode the raw fax data
		rows = copyFaxFile(io, handle, faxTIFF, xsize, stretch, memory);
		if(rows <= 0) throw "Error when decoding raw fax file : check the decoder options";


		// allocate the output dib
		dib = FreeImage_Allocate(xsize, rows, 1);
		unsigned pitch = FreeImage_GetPitch(dib);
		uint32 linesize = TIFFhowmany8(xsize);

		// fill the bitmap structure ...
		// ... palette
		RGBQUAD *pal = FreeImage_GetPalette(dib);
		if(photometric_in == PHOTOMETRIC_MINISWHITE) {
			pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 255;
			pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 0;
		} else {
			pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 0;
			pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255;
		}
		// ... resolution
		FreeImage_SetDotsPerMeterX(dib, (unsigned)(resX/0.0254000 + 0.5));
		FreeImage_SetDotsPerMeterY(dib, (unsigned)(resY/0.0254000 + 0.5));

		// read the decoded scanline and fill the bitmap data
		FreeImage_SeekMemory(memory, 0, SEEK_SET);
		BYTE *bits = FreeImage_GetScanLine(dib, rows - 1);
		for(int k = 0; k < rows; k++) {
			FreeImage_ReadMemory(bits, linesize, 1, memory);
			bits -= pitch;
		}

		// free the TIFF wrapper
		TIFFClose(faxTIFF);

		// free the memory buffer
		FreeImage_CloseMemory(memory);

	} catch(const char *message) {
		if(memory) FreeImage_CloseMemory(memory);
		if(faxTIFF) TIFFClose(faxTIFF);
		if(dib) FreeImage_Unload(dib);
		FreeImage_OutputMessageProc(s_format_id, message);
		return NULL;
	}

	return dib;

}

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

void DLL_CALLCONV
InitG3(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 = NULL;
	plugin->mime_proc = MimeType;
	plugin->supports_export_bpp_proc = SupportsExportDepth;
	plugin->supports_export_type_proc = NULL;
	plugin->supports_icc_profiles_proc = NULL;
}
