/*
Gwenview - A simple image viewer for TDE
Copyright 2000-2006 Aurelien Gateau

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.

*/

#include <sys/stat.h> // For S_ISDIR

// TQt
#include <tqfileinfo.h>
#include <tqguardedptr.h>
#include <tqpaintdevicemetrics.h>
#include <tqpainter.h>
#include <tqwmatrix.h>

// KDE
#include <tdeapplication.h>
#include <kdebug.h>
#include <tdefilemetainfo.h>
#include <tdeglobalsettings.h>
#include <kimageio.h>
#include <tdeio/job.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kprinter.h>
#include <kstringhandler.h>

// Local
#include "archive.h"
#include "busylevelmanager.h"
#include "cache.h"
#include "documentloadingimpl.h"
#include "documentimpl.h"
#include "imagesavedialog.h"
#include "imageutils/imageutils.h"
#include "jpegformattype.h"
#include "pngformattype.h"
#include "mngformattype.h"
#include "printdialog.h"
#include "qxcfi.h"
#include "xpm.h"
#include "xcursor.h"

#include "document.moc"
namespace Gwenview {


#undef ENABLE_LOG
#undef LOG
//#define ENABLE_LOG
#ifdef ENABLE_LOG
#define LOG(x) kdDebug() << k_funcinfo << x << endl
#else
#define LOG(x) ;
#endif

const char* CONFIG_SAVE_AUTOMATICALLY="save automatically";


/**
 * Returns a widget suitable to use as a dialog parent
 */
static TQWidget* dialogParentWidget() {
	return TDEApplication::kApplication()->mainWidget();
}

//-------------------------------------------------------------------
//
// DocumentPrivate
//
//-------------------------------------------------------------------
class DocumentPrivate {
public:
	KURL mURL;
	bool mModified;
	TQImage mImage;
	TQString mMimeType;
	TQCString mImageFormat;
	DocumentImpl* mImpl;
	TQGuardedPtr<TDEIO::StatJob> mStatJob;
	int mFileSize;
};


//-------------------------------------------------------------------
//
// Document
//
//-------------------------------------------------------------------
Document::Document(TQObject* parent)
: TQObject(parent) {
	d=new DocumentPrivate;
	d->mModified=false;
	d->mImpl=new DocumentEmptyImpl(this);
	d->mStatJob=0L;
	d->mFileSize=-1;

	// Register formats here to make sure they are always enabled
	KImageIO::registerFormats();
	XCFImageFormat::registerFormat();

	// First load TQt's plugins, so that Gwenview's decoders that
	// override some of them are installed later and thus come first.
	TQImageIO::inputFormats();
	{
		static Gwenview::JPEGFormatType sJPEGFormatType;
		static Gwenview::PNGFormatType sPNGFormatType;
		static Gwenview::XPM sXPM;
		static Gwenview::MNG sMNG;
		static Gwenview::XCursorFormatType sXCursorFormatType;
	}

	connect( this, TQ_SIGNAL( loading()),
		this, TQ_SLOT( slotLoading()));
	connect( this, TQ_SIGNAL( loaded(const KURL&)),
		this, TQ_SLOT( slotLoaded()));
}


Document::~Document() {
	delete d->mImpl;
	delete d;
}


//---------------------------------------------------------------------
//
// Properties
//
//---------------------------------------------------------------------
TQString Document::mimeType() const {
	return d->mMimeType;
}

void Document::setMimeType(const TQString& mimeType) {
	d->mMimeType = mimeType;
}

MimeTypeUtils::Kind Document::urlKind() const {
	return d->mImpl->urlKind();
}


KURL Document::url() const {
	return d->mURL;
}


void Document::setURL(const KURL& paramURL) {
	if (paramURL==url()) return;
	// Make a copy, we might have to fix the protocol
	KURL localURL(paramURL);
	LOG("url: " << paramURL.prettyURL());

	// Be sure we are not waiting for another stat result
	if (!d->mStatJob.isNull()) {
		d->mStatJob->kill();
	}
	BusyLevelManager::instance()->setBusyLevel(this, BUSY_NONE);

	// Ask to save if necessary.
	saveBeforeClosing();

	if (localURL.isEmpty()) {
		reset();
		return;
	}

	// Set high busy level, so that operations like smoothing are suspended.
	// Otherwise the stat() below done using TDEIO can take quite long.
	BusyLevelManager::instance()->setBusyLevel( this, BUSY_CHECKING_NEW_IMAGE );


	// Fix wrong protocol
	if (Archive::protocolIsArchive(localURL.protocol())) {
		TQFileInfo info(localURL.path());
		if (info.exists()) {
			localURL.setProtocol("file");
		}
	}

	d->mURL = localURL; // this may be fixed after stat() is complete, but set at least something
	d->mStatJob = TDEIO::stat( localURL, !localURL.isLocalFile() );
	d->mStatJob->setWindow(TDEApplication::kApplication()->mainWidget());
	connect( d->mStatJob, TQ_SIGNAL( result (TDEIO::Job *) ),
	   this, TQ_SLOT( slotStatResult (TDEIO::Job *) ) );
}


void Document::slotStatResult(TDEIO::Job* job) {
	LOG("");
	Q_ASSERT(d->mStatJob==job);
	if (d->mStatJob!=job) {
		kdWarning() << k_funcinfo << "We did not get the right job!\n";
		return;
	}
	BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE );
	if (d->mStatJob->error()) return;

	bool isDir=false;
	TDEIO::UDSEntry entry = d->mStatJob->statResult();
	d->mURL=d->mStatJob->url();

	TDEIO::UDSEntry::ConstIterator it;
	for(it=entry.begin();it!=entry.end();++it) {
		if ((*it).m_uds==TDEIO::UDS_FILE_TYPE) {
			isDir=S_ISDIR( (*it).m_long );
			break;
		}
	}

	if (isDir) {
		d->mURL.adjustPath( +1 ); // add trailing /
		reset();
		return;
	}

	load();
}


void Document::setDirURL(const KURL& paramURL) {
	saveBeforeClosing();
	d->mURL=paramURL;
	d->mURL.adjustPath( +1 ); // add trailing /
	reset();
}


const TQImage& Document::image() const {
	return d->mImage;
}

void Document::setImage(TQImage img) {
	bool sizechange = d->mImage.size() != img.size();
	d->mImage = img;
	if( sizechange ) emit sizeUpdated();
}


KURL Document::dirURL() const {
	if (filename().isEmpty()) {
		return d->mURL;
	} else {
		KURL url=d->mURL.upURL();
		url.adjustPath(1);
		return url;
	}
}

TQString Document::filename() const {
	return d->mURL.filename(false);
}

const TQCString& Document::imageFormat() const {
	return d->mImageFormat;
}

void Document::setImageFormat(const TQCString& format) {
	d->mImageFormat=format;
}

void Document::setFileSize(int size) {
	d->mFileSize=size;
}

TQString Document::comment() const {
	return d->mImpl->comment();
}

TQString Document::aperture() const {
	return d->mImpl->aperture();
}

TQString Document::exposureTime() const {
       return d->mImpl->exposureTime();
}

TQString Document::iso() const {
	return d->mImpl->iso();
}

TQString Document::focalLength() const {
	return d->mImpl->focalLength();
}

void Document::setComment(const TQString& comment) {
	d->mImpl->setComment(comment);
	d->mModified=true;
	emit modified();
}

Document::CommentState Document::commentState() const {
	return d->mImpl->commentState();
}

/**
 * Returns the duration of the document in seconds, or 0 if there is no
 * duration
 */
int Document::duration() const {
	return d->mImpl->duration();
}

int Document::fileSize() const {
	return d->mFileSize;
}

bool Document::canBeSaved() const {
	return d->mImpl->canBeSaved();
}

bool Document::isModified() const {
	return d->mModified;
}

void Document::slotLoading() {
	BusyLevelManager::instance()->setBusyLevel( this, BUSY_LOADING );
}

void Document::slotLoaded() {
	BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE );
}

//---------------------------------------------------------------------
//
// Operations
//
//---------------------------------------------------------------------
void Document::reload() {
	Cache::instance()->invalidate( url());
	load();
	emit reloaded(url());
}


void Document::print(KPrinter *pPrinter) {
	TQPainter printPainter;
	printPainter.begin(pPrinter);
	doPaint(pPrinter, &printPainter);
	printPainter.end();
}


void Document::doPaint(KPrinter *printer, TQPainter *painter) {
	// will contain the final image to print
	TQImage image = d->mImage;
	image.detach();

	// We use a TQPaintDeviceMetrics to know the actual page size in pixel,
	// this gives the real painting area
	TQPaintDeviceMetrics pdMetrics(painter->device());
	const int margin = pdMetrics.logicalDpiY() / 2; // half-inch margin

	painter->setFont( TDEGlobalSettings::generalFont() );
	TQFontMetrics fMetrics = painter->fontMetrics();

	int x = 0;
	int y = 0;
	int pdWidth = pdMetrics.width();
	int pdHeight = pdMetrics.height();

	TQString t = "true";
	TQString f = "false";

	int alignment = (printer->option("app-gwenview-position").isEmpty() ?
		TQt::AlignCenter : printer->option("app-gwenview-position").toInt());

	// Compute filename offset
	int filenameOffset = 0;
	bool printFilename = printer->option( "app-gwenview-printFilename" ) != f;
	if ( printFilename ) {
		filenameOffset = fMetrics.lineSpacing() + 14;
		pdHeight -= filenameOffset; // filename goes into one line!
	}

	// Compute comment offset
	int commentOffset = 0;
	bool printComment = printer->option( "app-gwenview-printComment" ) != f;
	if ( commentOffset ) {
		commentOffset = fMetrics.lineSpacing() + 14;// #### TODO check if it's correct
		pdHeight -= commentOffset; // #### TODO check if it's correct
	}
	if (commentOffset || printFilename) {
		pdHeight -= margin;
	}

	// Apply scaling
	int scaling = printer->option( "app-gwenview-scale" ).toInt();

	TQSize size = image.size();
	if (scaling==GV_FITTOPAGE /* Fit to page */) {
		bool enlargeToFit = printer->option( "app-gwenview-enlargeToFit" ) != f;
		if ((image.width() > pdWidth || image.height() > pdHeight) || enlargeToFit) {
			size.scale( pdWidth, pdHeight, TQSize::ScaleMin );
		}
	} else {
		if (scaling==GV_SCALE /* Scale To */) {
			int unit = (printer->option("app-gwenview-scaleUnit").isEmpty() ?
				GV_INCHES : printer->option("app-gwenview-scaleUnit").toInt());
			double inches = 1;
			if (unit == GV_MILLIMETERS) {
				inches = 1/25.4;
			} else if (unit == GV_CENTIMETERS) {
				inches = 1/2.54;
			}
			double wImg = (printer->option("app-gwenview-scaleWidth").isEmpty() ?
				1 : printer->option("app-gwenview-scaleWidth").toDouble()) * inches;
			double hImg = (printer->option("app-gwenview-scaleHeight").isEmpty() ?
				1 : printer->option("app-gwenview-scaleHeight").toDouble()) * inches;
			size.setWidth( int(wImg * printer->resolution()) );
			size.setHeight( int(hImg * printer->resolution()) );
		} else {
			/* GV_NOSCALE: no scaling */
			// try to get the density info so that we can print using original size
			// known if it is am image from scanner for instance
			const float INCHESPERMETER = (100. / 2.54);
			if (image.dotsPerMeterX())
			{
				double wImg = double(size.width()) / double(image.dotsPerMeterX()) * INCHESPERMETER;
				size.setWidth( int(wImg *printer->resolution()) );
			}
			if (image.dotsPerMeterY())
			{
				double hImg = double(size.height()) / double(image.dotsPerMeterY()) * INCHESPERMETER;
				size.setHeight( int(hImg *printer->resolution()) );
			}
		}
    
		if (size.width() > pdWidth || size.height() > pdHeight) {
			int resp = KMessageBox::warningYesNoCancel(dialogParentWidget(),
				i18n("The image will not fit on the page, what do you want to do?"),
				TQString(),KStdGuiItem::cont(),
				i18n("Shrink") );

			if (resp==KMessageBox::Cancel) {
				printer->abort();
				return;
			} else if (resp == KMessageBox::No) { // Shrink
				size.scale(pdWidth, pdHeight, TQSize::ScaleMin);
			}
		}
	}

	// Compute x and y
	if ( alignment & TQt::AlignHCenter )
		x = (pdWidth - size.width())/2;
	else if ( alignment & TQt::AlignLeft )
		x = 0;
	else if ( alignment & TQt::AlignRight )
		x = pdWidth - size.width();

	if ( alignment & TQt::AlignVCenter )
		y = (pdHeight - size.height())/2;
	else if ( alignment & TQt::AlignTop )
		y = 0;
	else if ( alignment & TQt::AlignBottom )
		y = pdHeight - size.height();

	// Draw, the image will be scaled to fit the given area if necessary
	painter->drawImage( TQRect( x, y, size.width(), size.height()), image );

	if ( printFilename ) {
		TQString fname = KStringHandler::cPixelSqueeze( filename(), fMetrics, pdWidth );
		if ( !fname.isEmpty() ) {
			int fw = fMetrics.width( fname );
			int x = (pdWidth - fw)/2;
			int y = pdMetrics.height() - filenameOffset/2 -commentOffset/2 - margin;
			painter->drawText( x, y, fname );
		}
	}
	if ( printComment ) {
		TQString comm = comment();
		if ( !comm.isEmpty() ) {
			int fw = fMetrics.width( comm );
			int x = (pdWidth - fw)/2;
			int y = pdMetrics.height() - commentOffset/2 - margin;
			painter->drawText( x, y, comm );
		}
	}
}


void Document::transform(ImageUtils::Orientation orientation) {
	d->mImpl->transform(orientation);
	d->mModified=true;
	emit modified();
}


void Document::save() {
	TQString msg=saveInternal(url(), d->mImageFormat);
	if (!msg.isNull()) {
		KMessageBox::error(dialogParentWidget(), msg);
		// If it can't be saved we leave it as modified, because user
		// could choose to save it to another path with saveAs
	}
}


void Document::saveAs() {
	KURL saveURL;

	ImageSaveDialog dialog(saveURL, d->mImageFormat, dialogParentWidget());
	dialog.setSelection(url().fileName());
	if (!dialog.exec()) return;

	TQString msg=saveInternal(saveURL, dialog.imageFormat() );
	if (!msg.isNull()) {
		// If it can't be saved we leave it as modified, because user
		// could choose a wrong readonly path from dialog and retry to
		KMessageBox::error(dialogParentWidget(), msg);
	}
}

void Document::saveBeforeClosing() {
	if (!d->mModified) return;

	TQString msg=i18n("<qt>The image <b>%1</b> has been modified, do you want to save the changes?</qt>")
		.arg(url().prettyURL());

	int result=KMessageBox::questionYesNo(dialogParentWidget(), msg, TQString(),
		KStdGuiItem::save(), KStdGuiItem::discard(), CONFIG_SAVE_AUTOMATICALLY);		

	if (result == KMessageBox::Yes) {
		saveInternal(url(), d->mImageFormat);	
		// If it can't be saved it's useless to leave it as modified
		// since user is closing this image and changing to another one
		d->mModified=false;
		//FIXME it should be nice to tell the user it failed
	} else {
		d->mModified=false;
	}
}


//---------------------------------------------------------------------
//
// Private stuff
//
//---------------------------------------------------------------------
void Document::switchToImpl(DocumentImpl* impl) {
	// There should always be an implementation defined
	Q_ASSERT(d->mImpl);
	Q_ASSERT(impl);
	delete d->mImpl;
	d->mImpl=impl;

	connect(d->mImpl, TQ_SIGNAL(finished(bool)),
		this, TQ_SLOT(slotFinished(bool)) );
	connect(d->mImpl, TQ_SIGNAL(sizeUpdated()),
		this, TQ_SIGNAL(sizeUpdated()) );
	connect(d->mImpl, TQ_SIGNAL(rectUpdated(const TQRect&)),
		this, TQ_SIGNAL(rectUpdated(const TQRect&)) );
	d->mImpl->init();
}


void Document::load() {
	KURL pixURL=url();
	Q_ASSERT(!pixURL.isEmpty());
	LOG("url: " << pixURL.prettyURL());

	// DocumentLoadingImpl might emit "finished()" in its "init()" method, so
	// make sure we emit "loading()" before switching
	emit loading();
	switchToImpl(new DocumentLoadingImpl(this));
}


void Document::slotFinished(bool success) {
	LOG("");
	if (success) {
		emit loaded(d->mURL);
	} else {
		// FIXME: Emit a failed signal instead
		emit loaded(d->mURL);
	}
}


TQString Document::saveInternal(const KURL& url, const TQCString& format) {
	TQString msg=d->mImpl->save(url, format);

	if (msg.isNull()) {
		emit saved(url);
		d->mModified=false;
		return TQString();
	}
	
	LOG("Save failed: " << msg);
	return TQString("<qt><b>%1</b><br/>")
		.arg(i18n("Could not save the image to %1.").arg(url.prettyURL()))
		+ msg + "</qt>";
}


void Document::reset() {
	switchToImpl(new DocumentEmptyImpl(this));
	emit loaded(d->mURL);
}

} // namespace
