
// This file is #include'd directly into TranslationUtils.cpp
// (i.e. hack on top of a hack...
//  some day I'll port the full Translation Kit instead, roster and add-ons included).


// Much of the below is from Haiku "translator" code over at
//		http://xref.plausible.coop/source/xref/haiku/src/add-ons/translators/jpeg/JPEGTranslator.cpp
// which also relies on ijg.org's JPEG decoder, like here in Genode.



// genode/base.lib.a
#include <base/log.h>

// jpeg.lib.so
#include "jpeglib.h"

/**************/
static int debug = 0;



static
status_t /*JPEGTranslator::*/Error(j_common_ptr cinfo, status_t error)
{
	jpeg_destroy(cinfo);
	return error;
}

static bool
x_flipped(int32 orientation)
{
	return orientation == 2 || orientation == 3
		|| orientation == 6 || orientation == 7;
}


static bool
y_flipped(int32 orientation)
{
	return orientation == 3 || orientation == 4
		|| orientation == 7 || orientation == 8;
}

static int32
dest_index(uint32 width, uint32 height, uint32 x, uint32 y, int32 orientation)
{
	if (orientation > 4) {
		uint32 temp = x;
		x = y;
		y = temp;
	}
	if (y_flipped(orientation))
		y = height - 1 - y;
	if (x_flipped(orientation))
		x = width - 1 - x;

	return y * width + x;
}

//!	RGB24 8:8:8 to xRGB 8:8:8:8
inline void
convert_from_24_to_32(uint8* in, uint8* out, int32 inRowBytes, int32 xStep)
{
	for (int32 i = 0; i < inRowBytes; i += 3) {
		out[0] = in[2];
		out[1] = in[1];
		out[2] = in[0];
		out[3] = 255;

		in += 3;
		out += xStep;
	}
}

static
int jpeg( const char * path, BBitmapStream * outstream )
{
	struct jpeg_decompress_struct cinfo;
	
	//struct jpeg_error_mgr jerr;
	struct my_error_mgr {
		struct jpeg_error_mgr pub;	/* "public" fields */
	
		//jmp_buf setjmp_buffer;	/* for return to caller */
	};
	my_error_mgr jerr;
	
	FILE * infile = NULL;		/* source file */
//	JSAMPARRAY buffer = NULL;		/* Output row buffer */
	
	if ((infile = fopen(path, "rb")) == NULL)
	{
		Genode::error("jpeg: can't open file: ", path);
		
		fprintf(stderr, "can't open %s\n", path);
		return 0;
	}
	
	cinfo.err = jpeg_std_error(&jerr.pub);
#if 0
	jerr.pub.error_exit = my_error_exit;
	/* Establish the setjmp return context for my_error_exit to use. */
	if (setjmp(jerr.setjmp_buffer)) {
		/* If we get here, the JPEG code has signaled an error.
		 * We need to clean up the JPEG object, close the input file, and return.
		 */
		jpeg_destroy_decompress(&cinfo);
		fclose(infile);
		return 0;
	}
#endif
	/* Now we can initialize the JPEG decompression object. */
	jpeg_create_decompress(&cinfo);
	
	jpeg_stdio_src(&cinfo, infile);
	jpeg_read_header(&cinfo, TRUE);
	
	/************/
	
	color_space outColorSpace = B_RGB32;
	int outColorComponents = 4;
	
	// Function pointer to convert function
	// It will point to proper function if needed
	void (*converter)(uchar* inScanLine, uchar* outScanLine, int32 inRowBytes, int32 xStep)
		= convert_from_24_to_32;
	
	if (cinfo.out_color_space != JCS_RGB) {
		Genode::error("jpeg: format N/I !");
		
		return 0;
	}
	
	jpeg_start_decompress(&cinfo);
	
	int32 orientation = 0;
	
	/***/
	
	int32 outputWidth = cinfo.output_width;
	int32 outputHeight = cinfo.output_height;
	
//	int row_stride = cinfo.output_width * cinfo.output_components;  // physical row width in output buffer
	/* Make a one-row-high sample array that will go away when done with image */
//	buffer = (*cinfo.mem->alloc_sarray)
//		((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
	;
	int32 destOffset = dest_index(outputWidth, outputHeight,
		0, 0, orientation) * outColorComponents;
	int32 xStep = dest_index(outputWidth, outputHeight,
		1, 0, orientation) * outColorComponents - destOffset;
	int32 yStep = dest_index(outputWidth, outputHeight,
		0, 1, orientation) * outColorComponents - destOffset;
	bool needAll = orientation != 1;
	
	BRect bounds(0, 0, outputWidth - 1, outputHeight - 1);
	;
	// Bytes count in one line of image (scanline)
	int32 inRowBytes = cinfo.output_width * cinfo.output_components;
	int32 rowBytes = (bounds.IntegerWidth() + 1) * outColorComponents;
	int32 dataSize = cinfo.output_width * cinfo.output_height
		* outColorComponents;
	;
	TranslatorBitmap header;
	if( debug )
	{
		Genode::log("width: ", bounds.Width(), " for native LE rect:" );
		bounds.PrintToStream();
	}
	header.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
	header.bounds.left = B_HOST_TO_BENDIAN_FLOAT(bounds.left);
	header.bounds.top = B_HOST_TO_BENDIAN_FLOAT(bounds.top);
	header.bounds.right = B_HOST_TO_BENDIAN_FLOAT(bounds.right);
	header.bounds.bottom = B_HOST_TO_BENDIAN_FLOAT(bounds.bottom);
	if( debug )
	{
		Genode::log("width: ", header.bounds.Width(), " for BEndian rect:");
		header.bounds.PrintToStream();
	}
	header.colors = (color_space)B_HOST_TO_BENDIAN_INT32(outColorSpace);
	header.rowBytes = B_HOST_TO_BENDIAN_INT32(rowBytes);
	header.dataSize = B_HOST_TO_BENDIAN_INT32(dataSize);
	
	// Write out the header
	status_t err = outstream->Write(&header, sizeof(TranslatorBitmap));
	if (err < B_OK)
		return Error((j_common_ptr)&cinfo, err);
	else if (err < (int)sizeof(TranslatorBitmap))
		return Error((j_common_ptr)&cinfo, B_ERROR);
	
	uint8 * dest = NULL;
	uint8 * destLine = NULL;
	// Allocate scanline
	// Use libjpeg memory allocation functions, so in case of error it will free them itself
	JSAMPROW inScanLine = (unsigned char *)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo,
		JPOOL_PERMANENT, inRowBytes);
	
	// We need 2nd scanline storage only for conversion
	if (converter != NULL) {
		// There will be conversion, allocate second scanline...
		// Use libjpeg memory allocation functions, so in case of error it will
		// free them itself
	    dest = (uint8*)(cinfo.mem->alloc_large)((j_common_ptr)&cinfo,
	    	JPOOL_PERMANENT, needAll ? dataSize : rowBytes);
	    destLine = dest + destOffset;
	} else
		destLine = inScanLine;
	
	while (cinfo.output_scanline < cinfo.output_height) {
		// Read one scanline
		jpeg_read_scanlines( &cinfo, &inScanLine, 1 );
		
		// Convert if needed
		if (converter != NULL)
			converter(inScanLine, destLine, inRowBytes, xStep);
		
		if (!needAll) {
	  		// Write the scanline buffer to the output stream
			ssize_t bytesWritten = outstream->Write(destLine, rowBytes);
			if (bytesWritten < rowBytes) {
				return bytesWritten < B_OK
					? Error((j_common_ptr)&cinfo, bytesWritten)
					: Error((j_common_ptr)&cinfo, B_ERROR);
			}
		} else
			destLine += yStep;
	}
	
	if (needAll) {
		ssize_t bytesWritten = outstream->Write(dest, dataSize);
		if (bytesWritten < dataSize) {
			return bytesWritten < B_OK
				? Error((j_common_ptr)&cinfo, bytesWritten)
				: Error((j_common_ptr)&cinfo, B_ERROR);
		}
	}
	
	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);
	
	return 1;
}

