// vim: set tabstop=4 shiftwidth=4 noexpandtab:
/*
Gwenview - A simple image viewer for KDE
Copyright 2000-2004 Aur�lien G�teau
 
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
*/
// System
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern "C" {
#include <jpeglib.h>
#include "transupp.h"
}

// TQt
#include <tqbuffer.h>
#include <tqfile.h>
#include <tqimage.h>
#include <tqmap.h>
#include <tqwmatrix.h>

// KDE
#include <kdebug.h>

// Exiv2
#include <exiv2/exif.hpp>
#include <exiv2/image.hpp>

// Local
#include "imageutils/imageutils.h"
#include "imageutils/jpegcontent.h"
#include "imageutils/jpegerrormanager.h"

// Make sure an EXIV2_TEST_VERSION macro exists:

#ifdef EXIV2_VERSION
#    ifndef EXIV2_TEST_VERSION
#        define EXIV2_TEST_VERSION(major,minor,patch) \
         ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) )
#    endif
#else
#    define EXIV2_TEST_VERSION(major,minor,patch) (false)
#endif

namespace ImageUtils {

const int INMEM_DST_DELTA=4096;


//------------------------------------------
//
// In-memory data source manager for libjpeg
//
//------------------------------------------
struct inmem_src_mgr : public jpeg_source_mgr {
	TQByteArray* mInput;
};

void inmem_init_source(j_decompress_ptr cinfo) {
	inmem_src_mgr* src=(inmem_src_mgr*)(cinfo->src);
	src->next_input_byte=(const JOCTET*)( src->mInput->data() );
	src->bytes_in_buffer=src->mInput->size();
}

/**
 * If this function is called, it means the JPEG file is broken. We feed the
 * decoder with fake EOI has specified in the libjpeg documentation.
 */
int inmem_fill_input_buffer(j_decompress_ptr cinfo) {
	static JOCTET fakeEOI[2]={ JOCTET(0xFF), JOCTET(JPEG_EOI)};
	kdWarning() << k_funcinfo << " Image is incomplete" << endl;
	cinfo->src->next_input_byte=fakeEOI;
	cinfo->src->bytes_in_buffer=2;
	return true;
}

void inmem_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
	if (num_bytes<=0) return;
	Q_ASSERT(num_bytes>=long(cinfo->src->bytes_in_buffer));
	cinfo->src->next_input_byte+=num_bytes;
	cinfo->src->bytes_in_buffer-=num_bytes;
}

void inmem_term_source(j_decompress_ptr /*cinfo*/) {
}


//-----------------------------------------------
//
// In-memory data destination manager for libjpeg
//
//-----------------------------------------------
struct inmem_dest_mgr : public jpeg_destination_mgr {
	TQByteArray* mOutput;

	void dump() {
		kdDebug() << "dest_mgr:\n";
		kdDebug() << "- next_output_byte: " << next_output_byte << endl;
		kdDebug() << "- free_in_buffer: " << free_in_buffer << endl;
		kdDebug() << "- output size: " << mOutput->size() << endl;
	}
};

void inmem_init_destination(j_compress_ptr cinfo) {
	inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest);
	if (dest->mOutput->size()==0) {
		bool result=dest->mOutput->resize(INMEM_DST_DELTA);
		Q_ASSERT(result);
	}
	dest->free_in_buffer=dest->mOutput->size();
	dest->next_output_byte=(JOCTET*)(dest->mOutput->data() );
}

int inmem_empty_output_buffer(j_compress_ptr cinfo) {
	inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest);
	bool result=dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA);
	Q_ASSERT(result);
	dest->next_output_byte=(JOCTET*)( dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA );
	dest->free_in_buffer=INMEM_DST_DELTA;

	return true;
}

void inmem_term_destination(j_compress_ptr cinfo) {
	inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest);
	int finalSize=dest->next_output_byte - (JOCTET*)(dest->mOutput->data());
	Q_ASSERT(finalSize>=0);
	dest->mOutput->resize(finalSize);
}


//---------------------
//
// JPEGContent::Private
//
//---------------------
struct JPEGContent::Private {
	TQByteArray mRawData;
	TQSize mSize;
	TQString mComment;
	TQString mAperture;
	TQString mExposureTime;
	TQString mFocalLength;
	TQString mIso;

	bool mPendingTransformation;
	TQWMatrix mTransformMatrix;
	Exiv2::ExifData mExifData;

	Private() {
		mPendingTransformation = false;
	}

	void setupInmemSource(j_decompress_ptr cinfo) {
		Q_ASSERT(!cinfo->src);
		inmem_src_mgr* src = (inmem_src_mgr*)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
										sizeof(inmem_src_mgr));
		cinfo->src=(struct jpeg_source_mgr*)(src);

		src->init_source=inmem_init_source;
		src->fill_input_buffer=inmem_fill_input_buffer;
		src->skip_input_data=inmem_skip_input_data;
		src->resync_to_restart=jpeg_resync_to_restart;
		src->term_source=inmem_term_source;

		src->mInput=&mRawData;
	}

	
	void setupInmemDestination(j_compress_ptr cinfo, TQByteArray* outputData) {
		Q_ASSERT(!cinfo->dest);
		inmem_dest_mgr* dest = (inmem_dest_mgr*)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
										sizeof(inmem_dest_mgr));
		cinfo->dest=(struct jpeg_destination_mgr*)(dest);

		dest->init_destination=inmem_init_destination;
		dest->empty_output_buffer=inmem_empty_output_buffer;
		dest->term_destination=inmem_term_destination;

		dest->mOutput=outputData;
	}
	bool readSize() {
		struct jpeg_decompress_struct srcinfo;
		jpeg_saved_marker_ptr mark;
		
		// Init JPEG structs 
		JPEGErrorManager errorManager;

		// Initialize the JPEG decompression object
		srcinfo.err = &errorManager;
		jpeg_create_decompress(&srcinfo);
		if (setjmp(errorManager.jmp_buffer)) {
			kdError() << k_funcinfo << "libjpeg fatal error\n";
			return false;
		}

		// Specify data source for decompression
		setupInmemSource(&srcinfo);

		// Read the header
		jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL);
		int result=jpeg_read_header(&srcinfo, true);
		if (result!=JPEG_HEADER_OK) {
			kdError() << "Could not read jpeg header\n";
			jpeg_destroy_decompress(&srcinfo);
			return false;
		}
		mSize=TQSize(srcinfo.image_width, srcinfo.image_height);
		
		jpeg_destroy_decompress(&srcinfo);
		return true;
	}
};


//------------
//
// JPEGContent
//
//------------
JPEGContent::JPEGContent() {
	d=new JPEGContent::Private();
}


JPEGContent::~JPEGContent() {
	delete d;
}


bool JPEGContent::load(const TQString& path) {
	TQFile file(path);
	if (!file.open(IO_ReadOnly)) {
		kdError() << "Could not open '" << path << "' for reading\n";
		return false;
	}
	return loadFromData(file.readAll());
}


bool JPEGContent::loadFromData(const TQByteArray& data) {
	d->mPendingTransformation = false;
	d->mTransformMatrix.reset();

	d->mRawData = data;
	if (d->mRawData.size()==0) {
		kdError() << "No data\n";
		return false;
	}

	if (!d->readSize()) return false;

	Exiv2::Image::AutoPtr image;
	try {
		image = Exiv2::ImageFactory::open((unsigned char*)data.data(), data.size());
		image->readMetadata();
	} catch (Exiv2::Error&) {
		kdError() << "Could not load image with Exiv2\n";
		return false;
	}

	d->mExifData = image->exifData();
	d->mComment = TQString::fromUtf8( image->comment().c_str() );

	d->mAperture=aperture();
	d->mExposureTime=exposureTime();
	d->mIso=iso();
	d->mFocalLength=iso();

	// Adjust the size according to the orientation
	switch (orientation()) {
		case TRANSPOSE:
		case ROT_90:
		case TRANSVERSE:
		case ROT_270:
			d->mSize.transpose();
			break;
		default:
			break;
	}

	return true;
}


Orientation JPEGContent::orientation() const {
	Exiv2::ExifKey key("Exif.Image.Orientation");
	Exiv2::ExifData::iterator it = d->mExifData.findKey(key);
	if (it == d->mExifData.end()) {
		return NOT_AVAILABLE;
	}
	return Orientation( it->toLong() );
}


int JPEGContent::dotsPerMeterX() const {
	return dotsPerMeter("XResolution");
}


int JPEGContent::dotsPerMeterY() const {
	return dotsPerMeter("YResolution");
}


int JPEGContent::dotsPerMeter(const TQString& keyName) const {
	Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit");
	Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit);
	if (it == d->mExifData.end()) {
		return 0;
	}
	int res = it->toLong();
	TQString keyVal = "Exif.Image." + keyName;
	Exiv2::ExifKey keyResolution(keyVal.ascii());
	it = d->mExifData.findKey(keyResolution);
	if (it == d->mExifData.end()) {
		return 0;
	}
	// The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution.
	//     If the image resolution in unknown, 2 (inches) is designated.
	//         Default = 2
	//         2 = inches
	//         3 = centimeters
	//         Other = reserved
	const float INCHESPERMETER = (100. / 2.54); 
	Exiv2::Rational r = it->toRational();
	if (r.second == 0) {
		// a rational with 0 as second will make hang toLong() conversion
		r.second = 1;
	}
	switch (res) {
	case 3:  // dots per cm 
		return int(float(r.first) * 100 / float(r.second)); 
	default:  // dots per inch 
		return int(float(r.first) * INCHESPERMETER / float(r.second)); 
	}

	return 0;
}


void JPEGContent::resetOrientation() {
	Exiv2::ExifKey key("Exif.Image.Orientation");
	Exiv2::ExifData::iterator it = d->mExifData.findKey(key);
	if (it == d->mExifData.end()) {
		return;
	}

	*it = uint16_t(ImageUtils::NORMAL);
}


TQSize JPEGContent::size() const {
	return d->mSize;
}


TQString JPEGContent::comment() const {
	return d->mComment;
}

TQString JPEGContent::getExifInformation(const TQString exifkey) const {
	TQString ret;
	
	Exiv2::ExifKey key(exifkey.latin1());
	Exiv2::ExifData::iterator it = d->mExifData.findKey(key);

	if (it != d->mExifData.end()) {
               std::ostringstream outputString;
               outputString << *it;
               ret=TQString(outputString.str().c_str());
	}
	else {
		ret="n/a";
	}
	return ret;
}

TQString JPEGContent::aperture() const {
	d->mAperture=getExifInformation("Exif.Photo.FNumber");
	return d->mAperture;
}

TQString JPEGContent::exposureTime() const {
	d->mExposureTime=getExifInformation("Exif.Photo.ExposureTime");
	return d->mExposureTime;
}

TQString JPEGContent::iso() const {
	d->mIso=getExifInformation("Exif.Photo.ISOSpeedRatings");
	return d->mIso;
}

TQString JPEGContent::focalLength() const {
	d->mFocalLength=getExifInformation("Exif.Photo.FocalLength");
	return d->mFocalLength;
}

void JPEGContent::setComment(const TQString& comment) {
	d->mComment = comment;
}

static TQWMatrix createRotMatrix(int angle) {
	TQWMatrix matrix;
	matrix.rotate(angle);
	return matrix;
}


static TQWMatrix createScaleMatrix(int dx, int dy) {
	TQWMatrix matrix;
	matrix.scale(dx, dy);
	return matrix;
}



struct OrientationInfo {
	OrientationInfo() {}
	OrientationInfo(Orientation o, TQWMatrix m, JXFORM_CODE j)
	: orientation(o), matrix(m), jxform(j) {}

	Orientation orientation;
	TQWMatrix matrix;
	JXFORM_CODE jxform;
};
typedef TQValueList<OrientationInfo> OrientationInfoList;

static const OrientationInfoList& orientationInfoList() {
	static OrientationInfoList list;
	if (list.size() == 0) {
		TQWMatrix rot90 = createRotMatrix(90);
		TQWMatrix hflip = createScaleMatrix(-1, 1);
		TQWMatrix vflip = createScaleMatrix(1, -1);

		list
			<< OrientationInfo(NOT_AVAILABLE, TQWMatrix(), JXFORM_NONE)
			<< OrientationInfo(NORMAL, TQWMatrix(), JXFORM_NONE)
			<< OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H)
			<< OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180)
			<< OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V)
			<< OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE)
			<< OrientationInfo(ROT_90, rot90, JXFORM_ROT_90)
			<< OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE)
			<< OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270)
			;
	}
	return list;
}


void JPEGContent::transform(Orientation orientation) {
	if (orientation != NOT_AVAILABLE && orientation != NORMAL) {
		d->mPendingTransformation = true;
		OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end());
		for (; it!=end; ++it) {
			if ( (*it).orientation == orientation ) {
				d->mTransformMatrix = (*it).matrix * d->mTransformMatrix;
				break;
			}
		}
		if (it == end) {
			kdWarning() << k_funcinfo << "Could not find matrix for orientation\n";
		}
	}
}


#if 0
static void dumpMatrix(const TQWMatrix& matrix) {
	kdDebug() << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n";
	kdDebug() << "       | " << matrix.m21() << ", " << matrix.m22() << " |\n";
	kdDebug() << "       ( " << matrix.dx()  << ", " << matrix.dy()  << " )\n";
}
#endif


static bool matricesAreSame(const TQWMatrix& m1, const TQWMatrix& m2, double tolerance) {
	return fabs( m1.m11() - m2.m11() ) < tolerance
		&& fabs( m1.m12() - m2.m12() ) < tolerance
		&& fabs( m1.m21() - m2.m21() ) < tolerance
		&& fabs( m1.m22() - m2.m22() ) < tolerance
		&& fabs( m1.dx()  - m2.dx()  ) < tolerance
		&& fabs( m1.dy()  - m2.dy()  ) < tolerance;
}


static JXFORM_CODE findJxform(const TQWMatrix& matrix) {
	OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end());
	for (; it!=end; ++it) {
		if ( matricesAreSame( (*it).matrix, matrix, 0.001) ) {
			return (*it).jxform;
		}
	}
	kdWarning() << "findJxform: failed\n";
	return JXFORM_NONE;
}


void JPEGContent::applyPendingTransformation() {
	if (d->mRawData.size()==0) {
		kdError() << "No data loaded\n";
		return;
	}

	// The following code is inspired by jpegtran.c from the libjpeg

	// Init JPEG structs 
	struct jpeg_decompress_struct srcinfo;
	struct jpeg_compress_struct dstinfo;
	jvirt_barray_ptr * src_coef_arrays;
	jvirt_barray_ptr * dst_coef_arrays;

	// Initialize the JPEG decompression object
	JPEGErrorManager srcErrorManager;
	srcinfo.err = &srcErrorManager;
	jpeg_create_decompress(&srcinfo);
	if (setjmp(srcErrorManager.jmp_buffer)) {
		kdError() << k_funcinfo << "libjpeg error in src\n";
		return;
	}

	// Initialize the JPEG compression object
	JPEGErrorManager dstErrorManager;
	dstinfo.err = &dstErrorManager;
	jpeg_create_compress(&dstinfo);
	if (setjmp(dstErrorManager.jmp_buffer)) {
		kdError() << k_funcinfo << "libjpeg error in dst\n";
		return;
	}

	// Specify data source for decompression
	d->setupInmemSource(&srcinfo);

	// Enable saving of extra markers that we want to copy
	jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL);

	(void) jpeg_read_header(&srcinfo, TRUE);

	// Init transformation
	jpeg_transform_info transformoption;
	memset(&transformoption, 0, sizeof(jpeg_transform_info));
	transformoption.transform = findJxform(d->mTransformMatrix);
	transformoption.force_grayscale = false;
	transformoption.trim = false;
	jtransform_request_workspace(&srcinfo, &transformoption);

	/* Read source file as DCT coefficients */
	src_coef_arrays = jpeg_read_coefficients(&srcinfo);

	/* Initialize destination compression parameters from source values */
	jpeg_copy_critical_parameters(&srcinfo, &dstinfo);

	/* Adjust destination parameters if required by transform options;
	* also find out which set of coefficient arrays will hold the output.
	*/
	dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo,
		src_coef_arrays,
		&transformoption);

	/* Specify data destination for compression */
	TQByteArray output;
	output.resize(d->mRawData.size());
	d->setupInmemDestination(&dstinfo, &output);

	/* Start compressor (note no image data is actually written here) */
	jpeg_write_coefficients(&dstinfo, dst_coef_arrays);
	
	/* Copy to the output file any extra markers that we want to preserve */
	jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL);

	/* Execute image transformation, if any */
	jtransform_execute_transformation(&srcinfo, &dstinfo,
		src_coef_arrays,
		&transformoption);

	/* Finish compression and release memory */
	jpeg_finish_compress(&dstinfo);
	jpeg_destroy_compress(&dstinfo);
	(void) jpeg_finish_decompress(&srcinfo);
	jpeg_destroy_decompress(&srcinfo);

	// Set rawData to our new JPEG
	d->mRawData = output;
}


TQImage JPEGContent::thumbnail() const {
	TQImage image;
	if (!d->mExifData.empty()) {
#if (EXIV2_TEST_VERSION(0,17,91))
		Exiv2::ExifThumbC thumb(d->mExifData);
		Exiv2::DataBuf const thumbnail = thumb.copy();
#else
		Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail();
#endif
		image.loadFromData(thumbnail.pData_, thumbnail.size_);
	}
	return image;
}


void JPEGContent::setThumbnail(const TQImage& thumbnail) {
	if (d->mExifData.empty()) {
		return;
	}
	
	TQByteArray array;
	TQBuffer buffer(array);
	buffer.open(IO_WriteOnly);
	TQImageIO iio(&buffer, "JPEG");
	iio.setImage(thumbnail);
	if (!iio.write()) {
		kdError() << "Could not write thumbnail\n";
		return;
	}
	
#if (EXIV2_TEST_VERSION(0,17,91))
	Exiv2::ExifThumb thumb(d->mExifData);
	thumb.setJpegThumbnail((unsigned char*)array.data(), array.size());
#else
	d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size());
#endif
}


bool JPEGContent::save(const TQString& path) {
	TQFile file(path);
	if (!file.open(IO_WriteOnly)) {
		kdError() << "Could not open '" << path << "' for writing\n";
		return false;
	}

	return save(&file);
}


bool JPEGContent::save(TQFile* file) {
	if (d->mRawData.size()==0) {
		kdError() << "No data to store in '" << file->name() << "'\n";
		return false;
	}

	if (d->mPendingTransformation) {
		applyPendingTransformation();
		d->mPendingTransformation = false;
	}

	Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size());

	// Store Exif info
	image->setExifData(d->mExifData);
	image->setComment(d->mComment.utf8().data());
	image->writeMetadata();
	
	// Update mRawData
	Exiv2::BasicIo& io = image->io();
	d->mRawData.resize(io.size());
	io.read((unsigned char*)d->mRawData.data(), io.size());
	
	TQDataStream stream(file);
	stream.writeRawBytes(d->mRawData.data(), d->mRawData.size());

	// Make sure we are up to date
	loadFromData(d->mRawData);
	return true;
}


} // namespace
