/* This file is part of the KDE project
   Copyright (C) 2003,2009 Carsten Pfeiffer <pfeiffer@kde.org>

   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, version 2.

   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; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "kuickimage.h"

#include <QImage>
#include <QUrl>

#include "imagemods.h"


KuickImage::KuickImage( const KuickFile * file, ImlibImage *im, ImlibData *id)
    : QObject( 0L )
{
    myFile     = file;
    myOrigIm   = 0L;
    myIm       = im;
    myId       = id;
    myPixmap   = 0L;
    myWidth    = im->rgb_width;
    myHeight   = im->rgb_height;
    setDirty(true);

    myOrigWidth  = myWidth;
    myOrigHeight = myHeight;
    myRotation   = ROT_0;
    myFlipMode   = FlipNone;
}

KuickImage::~KuickImage()
{
	if (isModified())
	{
		ImageMods::rememberFor( this );
	}
    if ( myPixmap )
        Imlib_free_pixmap( myId, myPixmap );

    if ( myOrigIm )
    {
    	Imlib_destroy_image( myId, myOrigIm );
	    Imlib_kill_image( myId, myIm ); // kill scaled image (### really? analyze!)
    }
    else
	    Imlib_destroy_image( myId, myIm );
}

bool KuickImage::isModified() const
{
	bool modified = myWidth != myOrigWidth || myHeight != myOrigHeight;
	modified |= (myFlipMode != FlipNone);
	modified |= (myRotation != ROT_0);
	return modified;
}


Pixmap& KuickImage::pixmap()
{
    if ( myIsDirty )
	renderPixmap();

    return myPixmap;
}


void KuickImage::renderPixmap()
{
    if ( !myIsDirty )
	return;

//     qDebug("### rendering: %s", myFilename.latin1());

    if ( myPixmap )
	Imlib_free_pixmap( myId, myPixmap );

    emit startRendering();

// #ifndef NDEBUG
//     struct timeval tms1, tms2;
//     gettimeofday( &tms1, NULL );
// #endif

    Imlib_render( myId, myIm, myWidth, myHeight );
	myPixmap = Imlib_move_image( myId, myIm );

// #ifndef NDEBUG
//     gettimeofday( &tms2, NULL );
//     qDebug("*** rendering image: %s, took %ld ms", myFilename.latin1(),
//            (tms2.tv_usec - tms1.tv_usec)/1000);
// #endif

    emit stoppedRendering();

    myIsDirty = false;
}


void KuickImage::rotate( Rotation rot )
{
    if ( rot == ROT_180 ) { 		// rotate 180 degrees
	Imlib_flip_image_horizontal( myId, myIm );
	Imlib_flip_image_vertical( myId, myIm );
    }

    else if ( rot == ROT_90 || rot == ROT_270 ) {
	qSwap( myWidth, myHeight );
	Imlib_rotate_image( myId, myIm, -1 );

	if ( rot == ROT_90 ) 		// rotate 90 degrees
	    Imlib_flip_image_horizontal( myId, myIm );
	else if ( rot == ROT_270 ) 		// rotate 270 degrees
	    Imlib_flip_image_vertical( myId, myIm );
    }

    myRotation = (Rotation) ((myRotation + rot) % 4);
    setDirty(true);
}


bool KuickImage::rotateAbs( Rotation rot )
{
    if ( myRotation == rot )
	return false;

    int diff = rot - myRotation;
    bool clockWise = (diff > 0);

    switch( abs(diff) ) {
    case ROT_90:
        rotate( clockWise ? ROT_90 : ROT_270 );
	break;
    case ROT_180:
	rotate( ROT_180 );
	break;
    case ROT_270:
        rotate( clockWise ? ROT_270 : ROT_90 );
	break;
    }

    return true;
}

void KuickImage::flip( FlipMode flipMode )
{
    if ( flipMode & FlipHorizontal )
	Imlib_flip_image_horizontal( myId, myIm );
    if ( flipMode & FlipVertical )
	Imlib_flip_image_vertical( myId, myIm );

    myFlipMode = (FlipMode) (myFlipMode ^ flipMode);
    setDirty(true);
}

bool KuickImage::flipAbs( int mode )
{
    if ( myFlipMode == mode )
	return false;

    bool changed = false;

    if ( ((myFlipMode & FlipHorizontal) && !(mode & FlipHorizontal)) ||
	 (!(myFlipMode & FlipHorizontal) && (mode & FlipHorizontal)) ) {
	Imlib_flip_image_horizontal( myId, myIm );
	changed = true;
    }

    if ( ((myFlipMode & FlipVertical) && !(mode & FlipVertical)) ||
	 (!(myFlipMode & FlipVertical) && (mode & FlipVertical)) ) {
	Imlib_flip_image_vertical( myId, myIm );
	changed = true;
    }

    if ( changed ) {
        myFlipMode = (FlipMode) mode;
        setDirty(true);
        return true;
    }

    return false;
}


void KuickImage::restoreOriginalSize()
{
	if (myWidth == myOrigWidth && myHeight == myOrigHeight)
		return;

//	qDebug("-- restoreOriginalSize");

	if ( myOrigIm != 0L )
	{
		Imlib_destroy_image( myId, myIm );
		myIm = myOrigIm;
		myOrigIm = 0L;
	}

    myWidth   = myOrigWidth;
    myHeight  = myOrigHeight;
    setDirty(true);

    if ( myRotation == ROT_90 || myRotation == ROT_270 )
        qSwap( myWidth, myHeight );
}

void KuickImage::resize( int width, int height, KuickImage::ResizeMode mode )
{
	if ( myWidth == width && myHeight == height )
		return;

	if ( mode == KuickImage::SMOOTH )
	{
		if ( !smoothResize( width, height ) )
			fastResize( width, height );
	}
	else
	{
		fastResize( width, height );
	}
}


void KuickImage::fastResize( int width, int height )
{
//	qDebug("-- fastResize: %i x %i", width, height );

	// lazy resizing (only done when rendering pixmap)
    myWidth   = width;
    myHeight  = height;
    setDirty(true);
}

bool KuickImage::smoothResize( int newWidth, int newHeight )
{
//	qDebug("-- smoothResize: %i x %i", newWidth, newHeight);

	QImage *image = newQImage();
	// Note: QImage::ScaleMin seems to have a bug (off-by-one, sometimes results in width being 1 pixel too small)
	QImage scaledImage = image->scaled(newWidth, newHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);

	delete image;


	ImlibImage *newIm = toImage( myId, scaledImage );
	if ( newIm )
	{
		if ( myOrigIm == 0 )
			myOrigIm = myIm;

		myIm = newIm;
		myWidth = newWidth;
		myHeight = newHeight;
        setDirty(true);
		return true;
	}

	return false;
}

QImage * KuickImage::newQImage() const
{
	ImlibImage *im;

//	qDebug("-- newQImage");

	if ( myOrigIm != 0L && myRotation == ROT_0 && myFlipMode == FlipNone )
	{
		// use original image if no other modifications have been applied
		// ### use orig image always and reapply mods?
		im = myOrigIm;
	}
	else
	{
		im = myIm;
	}

	int w = im->rgb_width;
	int h = im->rgb_height;

	QImage *image = new QImage( w, h, QImage::Format_RGB32 );
	uchar *rgb = im->rgb_data;
//	QRgb **destImageData = reinterpret_cast<QRgb**>( image->jumpTable() );


	int byteIndex = 0;
	int destLineIndex = 0;
	int destByteIndex = 0;
	for ( int pixel = 0; pixel < (w * h); pixel++ )
	{
		if ( pixel != 0 && (pixel % w) == 0 )
		{
			destLineIndex++;
			destByteIndex = 0;
		}

		uchar r = rgb[byteIndex++];
		uchar g = rgb[byteIndex++];
		uchar b = rgb[byteIndex++];

		QRgb rgbPixel = qRgb( r, g, b );
		QRgb *destImageData = reinterpret_cast<QRgb*>( image->scanLine( destLineIndex ));
		destImageData[destByteIndex++] = rgbPixel;
//		destImageData[destLineIndex][destByteIndex++] = rgbPixel;
	}

	return image;
}

ImlibImage * KuickImage::toImage( ImlibData *id, QImage& image )
{
	if ( image.isNull() )
		return 0L;

    if ( image.depth() != 32 )
    {
	image = image.convertToFormat(QImage::Format_RGB32);

    	if ( image.isNull() )
			return 0L;
    }

    // convert to 24 bpp (discard alpha)
    int numPixels = image.width() * image.height();
    const int NUM_BYTES_NEW  = 3; // 24 bpp
    uchar *newImageData = new uchar[numPixels * NUM_BYTES_NEW];
    uchar *newData = newImageData;

    int w = image.width();
    int h = image.height();

	for (int y = 0; y < h; y++) {
		QRgb *scanLine = reinterpret_cast<QRgb *>( image.scanLine(y) );
		for (int x = 0; x < w; x++) {
		    const QRgb& pixel = scanLine[x];
		    *(newData++) = qRed(pixel);
		    *(newData++) = qGreen(pixel);
		    *(newData++) = qBlue(pixel);
		}
	}

	ImlibImage *im = Imlib_create_image_from_data( id, newImageData, NULL,
                                                   image.width(), image.height() );

    delete [] newImageData;

    return im;
}


#if 0
bool KuickImage::smoothResize( int newWidth, int newHeight )
{
	int numPixels = newWidth * newHeight;
	const int NUM_BYTES_NEW  = 3; // 24 bpp
	uchar *newImageData = new uchar[numPixels * NUM_BYTES_NEW];

	// ### endianness
	// myIm : old image, old size


	/////////////////////////////////////////////////
//	int w = myOrigWidth; //myViewport.width();
	//int h = myOrigHeight; //myViewport.height();

	//QImage dst(w, h, myIm->depth(), myIm->numColors(), myIm->bitOrder());

	//QRgb *scanline;

	int basis_ox, basis_oy, basis_xx, basis_yy;

	// ### we only scale with a fixed factor for x and y anyway
	double scalex = newWidth / (double) myOrigWidth;
	double scaley = newHeight / (double) myOrigHeight;

//	basis_ox=(int) (myViewport.left() * 4096.0 / scalex);
//	basis_oy=(int) (myViewport.top() * 4096.0 / scaley);
	basis_ox = 0;
	basis_oy = 0;
	basis_xx = (int) (4096.0 / scalex);
	basis_yy = (int) (4096.0 / scaley);

	//qDebug("Basis: (%d, %d), (%d, 0), (0, %d)", basis_ox, basis_oy, basis_xx, basis_yy);

	int x2, y2;

	int max_x2 = (myOrigWidth << 12);
	int max_y2 = (myOrigHeight << 12);

//	QRgb background = idata->backgroundColor.rgb();

//	QRgb **imdata = (QRgb **) myIm->jumpTable();
//	QRgb *imdata = reinterpret_cast<QRgb*>( myIm->rgb_data );
	uchar *imdata = myIm->rgb_data;


	int y = 0;


//	for (;;)	//fill the top of the target pixmap with the background color
//	{
//		y2 = basis_oy + y * basis_yy;
//
//		if ((y2 >= 0  && (y2 >> 12) < myIm->height()) || y >= h)
//			break;
//
//		scanline = (QRgb*) dst.scanLine(y);
//		for (int i = 0; i < w; i++)
//			*(scanline++) = background; //qRgb(0,255,0);
//		y++;
//	}

	for (; y < newHeight; y++)
	{
//		scanline = (QRgb*) dst.scanLine(y);

		x2 = basis_ox;
		y2 = basis_oy + y * basis_yy;

		if (y2 >= max_y2)
			break;

		int x = 0;

//		while  ((x2 < 0 || (x2 >> 12) >= myIm->width()) && x < w)	//fill the left of the target pixmap with the background color
//		{
//			*(scanline++) = background; //qRgb(0,0,255);
//			x2 += basis_xx;
//			x++;
//		}

		int top = y2 >> 12;
		int bottom = top + 1;
		if (bottom >= myOrigHeight)
			bottom--;

//		for (; x < w; x++)
		for (; x < newWidth; x++) // ### myOrigWidth orig
		{
			int left = x2 >> 12;
			int right = left + 1;

			if (right >= myOrigWidth)
				right = myOrigWidth - 1;

			unsigned int wx = x2  & 0xfff; //12 bits of precision for reasons which will become clear
			unsigned int wy = y2 & 0xfff; //12 bits of precision

			unsigned int iwx = 0xfff - wx;
			unsigned int iwy = 0xfff - wy;

			QRgb tl = 0, tr = 0, bl = 0, br = 0;
			int ind = 0;
			ind = (left + top * myOrigWidth) * 3;
			tl  = (imdata[ind] << 16);
			tl |= (imdata[ind + 1] << 8);
			tl |= (imdata[ind + 2] << 0);
			int bar = imdata[ind + 2] << 8;
			bar = qBlue(bar);

			ind = (right + top * myOrigWidth) * 3;
			tr  = (imdata[ind] << 16);
			tr |= (imdata[ind + 1] << 8);
			tr |= (imdata[ind + 2] << 0);
			bar = imdata[ind + 2] << 8;

			ind = (left + bottom * myOrigWidth) * 3;
			bl  = (imdata[ind] << 16);
			bl |= (imdata[ind + 1] << 8);
			bl |= (imdata[ind + 2] << 0);
			bar = imdata[ind + 2] << 8;

			ind = (right + bottom * myOrigWidth) * 3;
			br  = (imdata[ind] << 16);
			br |= (imdata[ind + 1] << 8);
			br |= (imdata[ind + 2] << 0);
//			tl=imdata[top][left];
//			tr=imdata[top][right];
//			bl=imdata[bottom][left];
//			br=imdata[bottom][right];

			/*
			tl=getValidPixel(myIm, left, top, x, y);		//these calls are expensive
			tr=getValidPixel(myIm, right, top, x, y);		//use them to debug segfaults in this function
			bl=getValidPixel(myIm, left, bottom, x, y);
			br=getValidPixel(myIm, right, bottom, x, y);
			*/

			unsigned int r = (unsigned int) (qRed(tl) * iwx * iwy + qRed(tr) * wx* iwy + qRed(bl) * iwx * wy + qRed(br) * wx * wy); // NB 12+12+8 == 32
			unsigned int g = (unsigned int) (qGreen(tl) * iwx * iwy + qGreen(tr) * wx * iwy + qGreen(bl) * iwx * wy + qGreen(br) * wx * wy);
			unsigned int b = (unsigned int) (qBlue(tl) * iwx * iwy + qBlue(tr) * wx * iwy + qBlue(bl) * iwx * wy + qBlue(br) * wx * wy);

			// ### endianness
			//we're actually off by one in 255 here! (254 instead of 255)
			int foo = r >> 24;
			foo = g >> 24;
			foo = b >> 24;
			newImageData[(y * newWidth * 3) + (x * 3) + 0] = (r >> 24);
			newImageData[(y * newWidth * 3) + (x * 3) + 1] = (g >> 24);
			newImageData[(y * newWidth * 3) + (x * 3) + 2] = (b >> 24);
//			*(scanline++) = qRgb(r >> 24, g >> 24, b >> 24); //we're actually off by one in 255 here

			x2 += basis_xx;

			if (x2 > max_x2)
			{
				x++;
				break;
			}

		}

//		while  (x < w)	//fill the right of each scanline with the background colour
//		{
//			*(scanline++) = background; //qRgb(255,0,0);
//			x++;
//		}
	}

//	for (;;)	//fill the bottom of the target pixmap with the background color
//	{
//		y2 = basis_oy + y * basis_yy;
//
//		if (y >= h)
//			break;
//
//		scanline = (QRgb*) dst.scanLine(y);
//		for (int i = 0; i < w; i++)
//			*(scanline++) = background; //qRgb(255,255,0);
//		y++;
//	}

	// ### keep orig image somewhere but delete all scaled images!
	ImlibImage *newIm = Imlib_create_image_from_data( myId, newImageData, NULL,
                                                      newWidth, newHeight );
    delete[] newImageData;

    if ( newIm )
    {
    	myScaledIm = newIm;
    	myIsDirty = true;
    	myWidth = newWidth;
    	myHeight = newHeight;
    }

    return myIm != 0L;
//	return dst.copy();
}
#endif
