/*
Copyright 2004 Jonathan Riddell <jr@jriddell.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; 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 Steet, Fifth Floor, Boston, MA  02110-1301, USA.

*/
#include "gvimagepart.moc"

#include <tqapplication.h>
#include <tqcursor.h>
#include <tqfile.h>
#include <tqpoint.h>

#include <tdeaction.h>
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <kdirlister.h>
#include <tdefiledialog.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kmimetype.h>
#include <tdeparts/genericfactory.h>

#include <gvcore/cache.h>
#include <gvcore/document.h>
#include <gvcore/fileoperation.h>
#include <gvcore/printdialog.h>
#include <gvcore/imageview.h>
#include <gvcore/imageloader.h>
#include <gvcore/mimetypeutils.h>

#include "config.h"

namespace Gwenview {
// For now let's duplicate
const char CONFIG_CACHE_GROUP[]="cache";

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


static bool storeData(TQWidget* parent, TQFile* file, const TQByteArray& data) {
	uint sizeWritten = file->writeBlock(data);
	if (sizeWritten != data.size()) {
		KMessageBox::error(
			parent,
			i18n("Could not save image to a temporary file"));
		return false;
	}
	return true;
}


//Factory Code
typedef KParts::GenericFactory<GVImagePart> GVImageFactory;
K_EXPORT_COMPONENT_FACTORY( libgvimagepart /*library name*/, GVImageFactory )

GVImagePart::GVImagePart(TQWidget* parentWidget, const char* /*widgetName*/, TQObject* parent,
			 const char* name, const TQStringList &)
	: KParts::ReadOnlyPart( parent, name )
	, mPrefetch( NULL )
	, mLastDirection( DirectionUnknown )  {
	GVImageFactory::instance()->iconLoader()->addAppDir( "gwenview");
	setInstance( GVImageFactory::instance() );
	TDEGlobal::locale()->insertCatalogue("gwenview");
	TDEGlobal::locale()->setActiveCatalogue("gwenview");

	mBrowserExtension = new GVImagePartBrowserExtension(this);

	// Create the widgets
	mDocument = new Document(this);
	connect( mDocument, TQ_SIGNAL( loading()), TQ_SLOT( slotLoading()));
	connect( mDocument, TQ_SIGNAL( loaded(const KURL&)), TQ_SLOT( slotLoaded(const KURL&)));
	mImageView = new ImageView(parentWidget, mDocument, actionCollection());
	connect( mImageView, TQ_SIGNAL(requestContextMenu(const TQPoint&)),
		this, TQ_SLOT(openContextMenu(const TQPoint&)) );
	setWidget(mImageView);

	mDirLister = new KDirLister;
	mDirLister->setAutoErrorHandlingEnabled( false, 0 );
	mDirLister->setMainWindow(TDEApplication::kApplication()->mainWidget());
	connect( mDirLister, TQ_SIGNAL( clear()), TQ_SLOT( dirListerClear()));
	connect( mDirLister, TQ_SIGNAL( newItems( const KFileItemList& )),
		TQ_SLOT( dirListerNewItems( const KFileItemList& )));
	connect(mDirLister,TQ_SIGNAL(deleteItem(KFileItem*)),
		TQ_SLOT(dirListerDeleteItem(KFileItem*)) );
	
	TQStringList mimeTypes=MimeTypeUtils::rasterImageMimeTypes();
	mDirLister->setMimeFilter(mimeTypes);
	mPreviousImage=new TDEAction(i18n("&Previous Image"),
		TQApplication::reverseLayout() ? "1rightarrow":"1leftarrow", Key_BackSpace,
		this,TQ_SLOT(slotSelectPrevious()), actionCollection(), "previous");
	mNextImage=new TDEAction(i18n("&Next Image"),
		TQApplication::reverseLayout() ? "1leftarrow":"1rightarrow", Key_Space,
		this,TQ_SLOT(slotSelectNext()), actionCollection(), "next");
	updateNextPrevious();

	KStdAction::saveAs( this, TQ_SLOT(saveAs()), actionCollection(), "saveAs" );
	new TDEAction(i18n("Rotate &Left"), "object-rotate-left", CTRL + Key_L, this, TQ_SLOT(rotateLeft()), actionCollection(), "rotate_left");
	new TDEAction(i18n("Rotate &Right"), "object-rotate-right", CTRL + Key_R, this, TQ_SLOT(rotateRight()), actionCollection(), "rotate_right");

	setXMLFile( "gvimagepart/gvimagepart.rc" );
}

GVImagePart::~GVImagePart() {
	delete mDirLister;
}


void GVImagePart::partActivateEvent(KParts::PartActivateEvent* event) {
	if (event->activated()) {
		TDEConfig* config=new TDEConfig("gwenviewrc");
		Cache::instance()->readConfig(config,CONFIG_CACHE_GROUP);
		delete config;
	}
	KParts::ReadOnlyPart::partActivateEvent( event );
}


void GVImagePart::guiActivateEvent( KParts::GUIActivateEvent* event) {
	// Stolen from TDEHTMLImage
	//
	// prevent the base implementation from emitting setWindowCaption with
	// our url. It destroys our pretty, previously caption. Konq saves/restores
	// the caption for us anyway.
	if (event->activated()) {
		return;
	}
	KParts::ReadOnlyPart::guiActivateEvent(event);
}


TDEAboutData* GVImagePart::createAboutData() {
	TDEAboutData* aboutData = new TDEAboutData( "gvimagepart", I18N_NOOP("GVImagePart"),
						"0.1", I18N_NOOP("Image Viewer"),
						TDEAboutData::License_GPL,
						"(c) 2004, Jonathan Riddell <jr@jriddell.org>");
	return aboutData;
}

bool GVImagePart::openURL(const KURL& url) {
	if (!url.isValid())  {
		return false;
	}
	KURL oldURLDir = m_url;
	oldURLDir.setFileName( TQString() );
	KURL newURLDir = url;
	newURLDir.setFileName( TQString() );
	bool sameDir = oldURLDir == newURLDir;
	m_url = url;
	emit started( 0 );
	if( mDocument->url() == url ) { // reload button in Konqy - setURL would return immediately
		mDocument->reload();
	} else {
		mDocument->setURL(url);
	}
	if( !sameDir ) {
		mDirLister->openURL(mDocument->dirURL());
		mLastDirection = DirectionUnknown;
	}
	return true;
}

TQString GVImagePart::filePath() {
	return m_file;
}

void GVImagePart::slotLoading() {
	emit setWindowCaption(mDocument->url().filename() + " - " + i18n("Loading..."));
	// Set the location bar URL because we can arrive here if the user click on
	// previous/next, which do not use openURLRequest
	emit mBrowserExtension->setLocationBarURL(mDocument->url().pathOrURL());
	updateNextPrevious();
}

void GVImagePart::slotLoaded(const KURL& url) {
	TQString caption = url.filename() + TQString(" - %1x%2").arg(mDocument->width()).arg(mDocument->height());
	emit setWindowCaption(caption);
	emit completed();
	emit setStatusBarText(i18n("Done."));
	prefetchDone();
	mPrefetch = ImageLoader::loader( mLastDirection == DirectionPrevious ? previousURL() : nextURL(),
		this, BUSY_PRELOADING );
	connect( mPrefetch, TQ_SIGNAL( imageLoaded( bool )), TQ_SLOT( prefetchDone()));
}

void GVImagePart::prefetchDone() {
	if( mPrefetch != NULL ) { 
		mPrefetch->release( this );
	}
	mPrefetch = NULL;
}

void GVImagePart::print() {
	KPrinter printer;

	printer.setDocName( m_url.filename() );
	KPrinter::addDialogPage( new PrintDialogPage( mDocument, mImageView, "GV page"));

	if (printer.setup(mImageView, TQString(), true)) {
		mDocument->print(&printer);
	}
}

void GVImagePart::rotateLeft() {
	mDocument->transform(ImageUtils::ROT_270);
}

void GVImagePart::rotateRight() {
	mDocument->transform(ImageUtils::ROT_90);
}

void GVImagePart::dirListerClear() {
	mImagesInDirectory.clear();
	updateNextPrevious();
}

void GVImagePart::dirListerNewItems( const KFileItemList& list ) {
	TQPtrListIterator<KFileItem> it(list);
	for( ; it.current(); ++it ) {
		mImagesInDirectory.append( (*it)->name());
	}
	qHeapSort( mImagesInDirectory );
	updateNextPrevious();
}

void GVImagePart::dirListerDeleteItem( KFileItem* item ) {
	mImagesInDirectory.remove( item->name());
	updateNextPrevious();
}

void GVImagePart::updateNextPrevious() {
	TQStringList::ConstIterator current = mImagesInDirectory.find( mDocument->filename());
	if( current == mImagesInDirectory.end()) {
		mNextImage->setEnabled( false );
		mPreviousImage->setEnabled( false );
		return;
	}
	mPreviousImage->setEnabled( current != mImagesInDirectory.begin());
	++current;
	mNextImage->setEnabled( current != mImagesInDirectory.end());
}

KURL GVImagePart::nextURL() const {
	TQStringList::ConstIterator current = mImagesInDirectory.find( mDocument->filename());
	if( current == mImagesInDirectory.end()) {
		return KURL();
	}
	++current;
	if( current == mImagesInDirectory.end()) {
		return KURL();
	}
	KURL newURL = mDocument->dirURL();
	newURL.setFileName( *current );
	return newURL;
}

void GVImagePart::slotSelectNext() {
	KURL newURL = nextURL();
	if( newURL.isEmpty()) return;
	mLastDirection = DirectionNext;
	// Do not use mBrowserExtension->openURLRequest to avoid switching to
	// another KPart
	openURL(newURL);
	emit mBrowserExtension->openURLNotify();
}

KURL GVImagePart::previousURL() const {
	TQStringList::ConstIterator current = mImagesInDirectory.find( mDocument->filename());
	if( current == mImagesInDirectory.end() || current == mImagesInDirectory.begin()) {
		return KURL();
	}
	--current;
	KURL newURL = mDocument->dirURL();
	newURL.setFileName( *current );
	return newURL;
}

void GVImagePart::slotSelectPrevious() {
	KURL newURL = previousURL();
	if( newURL.isEmpty()) return;
	mLastDirection = DirectionPrevious;
	openURL(newURL);
	emit mBrowserExtension->openURLNotify();
}


void GVImagePart::saveAs() {
	if (!mDocument->isModified()) {
		saveOriginalAs();
		return;
	}

	if (mDocument->canBeSaved()) {
		mDocument->saveAs();
		return;
	}

	KGuiItem saveItem(i18n("&Save Original"), "document-save-as");
	int result = KMessageBox::warningContinueCancel(
		widget(), 
		i18n("Gwenview KPart can't save the modifications you made. Do you want to save the original image?"),
		i18n("Warning"),
		saveItem);

	if (result == KMessageBox::Cancel) return;

	saveOriginalAs();
}


void GVImagePart::showJobError(TDEIO::Job* job) {
	if (job->error() != 0) { 
		job->showErrorDialog(widget());
	}
}


void GVImagePart::saveOriginalAs() {
	KURL srcURL = mDocument->url();
	KURL dstURL = KFileDialog::getSaveURL(
		srcURL.fileName(),
		TQString(),
		widget());
	if (!dstURL.isValid()) return;

	// Try to get data from the cache to avoid downloading the image again.
	TQByteArray data = Cache::instance()->file(srcURL);

	if (data.size() == 0) {
		// We need to read the image again. Let TDEIO::copy do the work.
		TDEIO::Job* job = TDEIO::copy(srcURL, dstURL);
		job->setWindow(widget());
		connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
			this, TQ_SLOT(showJobError(TDEIO::Job*)) );
		return;
	}

	if (dstURL.isLocalFile()) {
		// Destination is a local file, store it ourself
		TQString path = dstURL.path();
		TQFile file(path);
		if (!file.open(IO_WriteOnly)) {
			KMessageBox::error(
				widget(),
				i18n("Could not open '%1' for writing.").arg(path));
			return;
		}
		storeData(widget(), &file, data);
		return;
	}

	// We need to send the data to a remote location
	new DataUploader(widget(), data, dstURL);
}


DataUploader::DataUploader(TQWidget* dialogParent, const TQByteArray& data, const KURL& dstURL)
: mDialogParent(dialogParent)
{
	mTempFile.setAutoDelete(true);

	// Store it in a temp file
	if (! storeData(dialogParent, mTempFile.file(), data) ) return;

	// Now upload it
	KURL tmpURL;
	tmpURL.setPath(mTempFile.name());
	TDEIO::Job* job = TDEIO::copy(tmpURL, dstURL);
	job->setWindow(dialogParent);
	connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
		this, TQ_SLOT(slotJobFinished(TDEIO::Job*)) );
}


void DataUploader::slotJobFinished(TDEIO::Job* job) {
	if (job->error() != 0) { 
		job->showErrorDialog(mDialogParent);
	}

	delete this;
}


/**
 * Overload KXMLGUIClient so that we can call setXML
 */
class PopupGUIClient : public KXMLGUIClient {
public:
	PopupGUIClient( TDEInstance *inst, const TQString &doc ) {
		setInstance( inst );
		setXML( doc );
	}
};


void GVImagePart::openContextMenu(const TQPoint& pos) {
	TQString doc = KXMLGUIFactory::readConfigFile( "gvimagepartpopup.rc", true, instance() );
	PopupGUIClient guiClient(instance(), doc);
	
	KStdAction::saveAs( this, TQ_SLOT(saveAs()), guiClient.actionCollection(), "saveAs" );
	
	KParts::URLArgs urlArgs;
	urlArgs.serviceType = mDocument->mimeType();

	KParts::BrowserExtension::PopupFlags flags = 
		KParts::BrowserExtension::ShowNavigationItems 
		| KParts::BrowserExtension::ShowUp
		| KParts::BrowserExtension::ShowReload;
	
	emit mBrowserExtension->popupMenu(&guiClient, pos, m_url, urlArgs, flags, S_IFREG);
}


/***** GVImagePartBrowserExtension *****/

GVImagePartBrowserExtension::GVImagePartBrowserExtension(GVImagePart* viewPart, const char* name)
	:KParts::BrowserExtension(viewPart, name) {
	mGVImagePart = viewPart;
	emit enableAction("print", true );
}

GVImagePartBrowserExtension::~GVImagePartBrowserExtension() {
}

void GVImagePartBrowserExtension::print() {
	mGVImagePart->print();
}

} // namespace
