// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CImageWriterJPG.h"

#ifdef _IRR_COMPILE_WITH_JPG_WRITER_

#include "CColorConverter.h"
#include "IWriteFile.h"
#include "CImage.h"
#include "CColorConverter.h"
#include "irrString.h"

#ifdef _IRR_COMPILE_WITH_LIBJPEG_
#include <stdio.h> // required for jpeglib.h
extern "C"
{
#ifndef _IRR_USE_NON_SYSTEM_JPEG_LIB_
	#include <jpeglib.h>
	#include <jerror.h>
#else
	#include "jpeglib/jpeglib.h"
	#include "jpeglib/jerror.h"
#endif
#include <setjmp.h>
}


namespace irr
{
namespace video
{


typedef struct
{
  struct jpeg_destination_mgr pub; /* public fields */

  JOCTET * buffer;              /* image buffer */
  u32 buffer_size;				/* image buffer size */
} mem_destination_mgr;


typedef mem_destination_mgr * mem_dest_ptr;

void init_destination (j_compress_ptr cinfo)
{
	mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;

	/* image buffer must be allocated before mem_dest routines are called.  */
	if(dest->buffer == NULL) {
		//fprintf(stderr, "jmem_dest: init_destination: buffer not allocated\n");
	}

	dest->pub.next_output_byte = dest->buffer;
	dest->pub.free_in_buffer = dest->buffer_size;
}


boolean empty_output_buffer (j_compress_ptr cinfo)
{
	/*
	mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
	*/
	// empty_output_buffer: buffer should not ever be full\n");
	return FALSE;
}


void term_destination (j_compress_ptr cinfo)
{
	mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
	size_t datacount = dest->buffer_size - dest->pub.free_in_buffer;
}

void jpeg_memory_dest (j_compress_ptr cinfo, u8 *jfif_buffer,
                  s32 buf_size)
{
  mem_dest_ptr dest;

  if(jfif_buffer == NULL) {
    //fprintf(stderr, "jpeg_memory_dest: memory buffer needs to be allocated\n");
    //ERREXIT(cinfo, JERR_BUFFER_SIZE);
	  return;
  }

  if (cinfo->dest == NULL) {    /* first time for this JPEG object? */
    cinfo->dest = (struct jpeg_destination_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                                  (size_t) sizeof(mem_destination_mgr));
  }

  dest = (mem_dest_ptr) cinfo->dest;  /* for casting */

  /* Initialize method pointers */
  dest->pub.init_destination = init_destination;
  dest->pub.empty_output_buffer = empty_output_buffer;
  dest->pub.term_destination = term_destination;

  /* Initialize private member */
  dest->buffer = (JOCTET*)jfif_buffer;
  dest->buffer_size = buf_size;
}

/* write_JPEG_memory: store JPEG compressed image into memory.
*/
void write_JPEG_memory (void *img_buf, s32 width, s32 height, u32 bpp, u32 pitch,
					u8 *jpeg_buffer, u32 jpeg_buffer_size,
					s32 quality, u32 *jpeg_comp_size)
{
	struct jpeg_compress_struct cinfo;
	struct jpeg_error_mgr jerr;

	/* More stuff */
	JSAMPROW row_pointer[1];      /* pointer to JSAMPLE row[s] */

	cinfo.err = jpeg_std_error(&jerr);
	jpeg_create_compress(&cinfo);
	jpeg_memory_dest(&cinfo, jpeg_buffer, jpeg_buffer_size);
	cinfo.image_width = width;
	cinfo.image_height = height;
	cinfo.input_components = bpp;
	cinfo.in_color_space = JCS_RGB;

	jpeg_set_defaults(&cinfo);
	jpeg_set_quality(&cinfo, quality, TRUE);
	jpeg_start_compress(&cinfo, TRUE);

	while (cinfo.next_scanline < cinfo.image_height)
	{
		row_pointer[0] = (u8*) img_buf + (cinfo.next_scanline * pitch );
		jpeg_write_scanlines(&cinfo, row_pointer, 1);
	}

	/* Step 6: Finish compression */
	jpeg_finish_compress(&cinfo);

	{
		mem_dest_ptr dest = (mem_dest_ptr) cinfo.dest;
		*jpeg_comp_size = dest->buffer_size - dest->pub.free_in_buffer;
	}

	jpeg_destroy_compress(&cinfo);
}

} // namespace video
} // namespace irr

#endif // _IRR_COMPILE_WITH_LIBJPEG_

namespace irr
{
namespace video
{

IImageWriter* createImageWriterJPG()
{
	return new CImageWriterJPG;
}

CImageWriterJPG::CImageWriterJPG()
{
#ifdef _DEBUG
	setDebugName("CImageWriterJPG");
#endif
}


bool CImageWriterJPG::isAWriteableFileExtension(const c8* fileName) const
{
	return strstr(fileName, ".jpg") != 0 || strstr(fileName, ".jpeg") != 0;
}


bool CImageWriterJPG::writeImage(io::IWriteFile *file, IImage *input,u32 quality) const
{
#ifndef _IRR_COMPILE_WITH_LIBJPEG_
	return false;
#else

	core::dimension2di dim = input->getDimension();
	IImage * image = new CImage(ECF_R8G8B8, dim );

	void (*format)(const void*, s32, void*) = 0;
	switch( input->getColorFormat () )
	{
		case ECF_R8G8B8:	format = CColorConverter::convert_R8G8B8toR8G8B8; break;
		case ECF_A8R8G8B8:	format = CColorConverter::convert_A8R8G8B8toR8G8B8; break;
		case ECF_A1R5G5B5:	format = CColorConverter::convert_A1R5G5B5toB8G8R8; break;
		case ECF_R5G6B5:	format = CColorConverter::convert_R5G6B5toR8G8B8; break;
	}

	// couldn't find a color converter
	if ( 0 == format )
		return false;

	s32 y;
	void *src = input->lock();
	void *dst = image->lock();
	for ( y = 0; y!= dim.Height; ++y )
	{
		format( src, dim.Width, dst );
		src = (void*) ( (u8*) src + input->getPitch () );
		dst = (void*) ( (u8*) dst + image->getPitch () );
	}
	input->unlock ();
	image->unlock ();

	// temp buffer
	u32 destSize = image->getImageDataSizeInBytes ();
	u8 * dest = new u8 [ destSize ];
	
	if ( 0 == quality )
		quality = 75;

	write_JPEG_memory ( image->lock (), dim.Width, dim.Height,
				image->getBytesPerPixel(), image->getPitch(),
				dest, destSize,
				quality,
				&destSize);

	file->write ( dest, destSize );

	image->drop ();
	delete [] dest;

	return true;
#endif
}

} // namespace video
} // namespace irr

#endif

