/*********************************************************************************
* C++ Implementation: kbearfilediffplugin..cpp
* Description:
*
* Begin : ons jun 18 2003
* Author : Björn Sahlström <kbjorn@users.sourceforge.net> (C) 2003
* Copyright : See COPYING file that comes with this distribution
**********************************************************************************/

//////////////////////////////////////////////////////////////////////
// Qt specific include files
#include <qframe.h>
#include <qlabel.h>
#include <qfile.h>
#include <qregexp.h>
#include <qptrlist.h>
#include <qlayout.h>
#include <qpopupmenu.h>
#include <qapplication.h>
#include <qcursor.h>
#include <qwidget.h>
//////////////////////////////////////////////////////////////////////
// KDE specific include files
//////////////////////////////////////////////////////////////////////
// System specific include files
#include <klocale.h>
#include <kglobal.h>
#include <kconfig.h>
#include <kinstance.h>
#include <ktempfile.h>
#include <kservice.h>
#include <kfiledialog.h>
#include <kmessagebox.h>
#include <kprocess.h>
#include <ktrader.h>
#include <kurldrag.h>
#include <kstandarddirs.h>
#include <kgenericfactory.h>
#include <kparts/componentfactory.h>
#include <kdebug.h>
//////////////////////////////////////////////////////////////////////
// Application specific include files
#include "kbearfilediffplugin.h"
#include "kbeardifftextedit.h"
#include "misc.h"
#include "kbearapi.h"
#include "kbearcore.h"
#include "transfermanager.h"
#include "transfergroup.h"
#include "transfer.h"
#include "kbearmainwiniface.h"



using namespace KBear;

#include "kbearfilediffplugin.moc"

//-----------------------------------------------
QStringList KBearFileDiffPlugin::s_externalParts;
//-----------------------------------------------
typedef KGenericFactory<KBearFileDiffPlugin> KBearFileDiffPluginFactory;
K_EXPORT_COMPONENT_FACTORY( kbearfilediff, KBearFileDiffPluginFactory( "kbearfilediff" ) );
//-----------------------------------------------
KBearFileDiffPlugin::KBearFileDiffPlugin(QObject *parent, const char*, const QStringList& )
 	:	KBearPlugin( parent, "KBearFileDiffPlugin" ),
		m_diffTmpFile( 0L ), m_diffProcess( 0L ),
		m_sourceFinished( false ), m_destFinished( false ),
		m_externalPart( 0L )
{
	(void) KGlobal::locale()->insertCatalogue("kbear");
	setInstance(KBearFileDiffPluginFactory::instance());

	setXMLFile( "kbearfilediff.rc" );

	m_outputWidget = new QWidget( 0L, "KBearFileDiffPluginOutputWidget" );
	QHBoxLayout* layout = new QHBoxLayout( m_outputWidget, 11, 6 );
	m_separator = new QFrame( m_outputWidget, "m_separator" );
	m_separator->setFrameShape( QFrame::VLine );
	m_sourceDropZone = new FileDiffDropWidget( i18n("Drop source file here"), FileDiffDropWidget::Source, m_outputWidget, "SourceDropZone" );
	m_destDropZone = new FileDiffDropWidget( i18n("Drop destination file here"), FileDiffDropWidget::Destination, m_outputWidget, "DestDropZone" );
	m_diffEdit = new KBearDiffTextEdit( m_outputWidget, "m_diffEdit" );

	layout->addWidget( m_sourceDropZone );
	layout->addWidget( m_separator );
	layout->addWidget( m_destDropZone );
	layout->addWidget( m_diffEdit );
	m_diffEdit->hide();

	m_clearDiff = new KAction( i18n("Reset File Difference view"), 0, this, SLOT( slotClear() ), actionCollection(), "clear_diff" );
	m_saveAs = KStdAction::saveAs( this, SLOT( slotSaveAs() ), actionCollection(), KStdAction::stdName( KStdAction::SaveAs ) );
	m_toggleHighlight = new KToggleAction( i18n( "Highlight syntax" ), 0, this, SLOT( slotToggleSyntaxHighlight() ), actionCollection(), "highlight" );

	connect( m_diffEdit, SIGNAL( popupMenuRequest( QPopupMenu* ) ), this, SLOT( slotPopupMenuRequest( QPopupMenu* ) ) );
	connect( m_sourceDropZone, SIGNAL( dropped( QDropEvent* ) ), this, SLOT( slotSourceDrop( QDropEvent* ) ) );
	connect( m_destDropZone, SIGNAL( dropped( QDropEvent* ) ), this, SLOT( slotDestDrop( QDropEvent* ) ) );

	KConfig* config = KBearFileDiffPluginFactory::instance()->config();
	config->setGroup( "FileDiff" );
	bool highlight = config->readBoolEntry( "Highlight", true );
	m_toggleHighlight->setChecked( highlight );

	searchForExternalParts();
}
//-----------------------------------------------
KBearFileDiffPlugin::~KBearFileDiffPlugin() {
  slotClear();

  KConfig* config = KBearFileDiffPluginFactory::instance()->config();
  config->setGroup( "FileDiff" );
  config->writeEntry( "Highlight", m_toggleHighlight->isChecked() );

	mainWindow()->removeOutputPluginView( m_outputWidget );
	delete m_outputWidget;
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotPopupMenuRequest( QPopupMenu* menu ) {
	int index = 0;

	KAction* action = 0L;
	for( QStringList::Iterator it = s_externalParts.begin(); it != s_externalParts.end(); ++it ) {
		action = new KAction( i18n( "Show in %1" ).arg( *it ), 0, this, SLOT( slotExternalActivated() ), actionCollection(), QString::number( index ).latin1() );
		action->plug( menu, index++ );
		}
	if( ! s_externalParts.isEmpty() )
		menu->insertSeparator( index++ );

	m_toggleHighlight->plug( menu, index++ );
	menu->insertSeparator( index++ );
	m_saveAs->plug( menu, index++ );
	menu->insertSeparator( index++ );
	menu->insertSeparator();
	m_clearDiff->plug( menu );
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotExternalActivated() {
	QObject* obj = (QObject*)sender();
	KAction* action = dynamic_cast<KAction*>( obj );
	if( action ) {
		QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) );
		if( m_externalPart ) {
			m_externalPart->widget()->hide();
			delete m_externalPart;
			m_externalPart = 0L;
		}

		KService::Ptr extService = KService::serviceByName( s_externalParts[ QString( action->name() ).toInt() ] );
		if( ! extService )
			return;

		m_externalPart = KParts::ComponentFactory::createPartInstanceFromService<KParts::ReadOnlyPart>( extService, m_outputWidget, 0, this, 0 );
		if( ! m_externalPart || ! m_externalPart->widget() )
			return;

		m_outputWidget->layout()->add( m_externalPart->widget() );

		m_externalPart->widget()->show();

		bool ok = false;
		if( m_diffTmpFile && m_diffEdit->paragraphs() > 0 )
			ok = m_externalPart->openURL( m_diffTmpFile->name() );
		if( ok )
			m_diffEdit->hide();
		else
			m_externalPart->widget()->hide();

		QApplication::restoreOverrideCursor();
  }
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotSaveAs() {
	QString fileName = KFileDialog::getSaveFileName();
	if( fileName.isEmpty() )
		return;

	QFile f( fileName );
	if( f.open( IO_WriteOnly ) ) {
		QTextStream stream( &f );
		int pCount = m_diffEdit->paragraphs();
		for( int i = 0; i < pCount; ++i )
			stream << m_diffEdit->text( i ) << "\n";
		f.close();
	}
	else {
		KMessageBox::sorry( m_outputWidget, i18n("Unable to open file:\n%1").arg( fileName ), i18n("File Difference") );
	}
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotToggleSyntaxHighlight() {
	m_diffEdit->setEnableHighlight( m_toggleHighlight->isChecked() );
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotClear() {
	if( m_externalPart ) {
		m_externalPart->widget()->hide();
		delete m_externalPart;
		m_externalPart = 0L;
	}
	if( m_diffTmpFile ) {
		delete m_diffTmpFile;
		m_diffTmpFile = 0L;
	}
	if( m_diffProcess ) {
		delete m_diffProcess;
		m_diffProcess = 0L;
	}
	m_diffEdit->hide();
	m_sourceFinished = false;
	m_destFinished = false;
	m_sourceDropZone->clear();
	m_destDropZone->clear();
	m_separator->show();
	m_sourceDropZone->show();
	m_destDropZone->show();
	m_stdOut = m_stdErr = QString::null;
	QApplication::restoreOverrideCursor();
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotInit() {
	mainWindow()->embedOutputPluginView( m_outputWidget, i18n( "File Difference" ), i18n( "Display the difference of two files." ) );
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotSourceDrop( QDropEvent* drop ) {
	if( ! KURLDrag::canDecode( drop ) )
		return;

	Transfer* transfer = newDrop( drop, m_sourceURL, m_sourceTmpURL, m_sourceDropZone );

	if( transfer ) {
		connect( transfer, SIGNAL( finished() ), this, SLOT( slotSourceFinished() ) );
	}
	else if( ! m_sourceURL.hasHost() )
		m_sourceFinished = true;

	if( m_sourceFinished && m_destFinished ) {
		execDiff();
	}
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotDestDrop( QDropEvent* drop ) {
	if( ! KURLDrag::canDecode( drop ) )
		return;

	Transfer* transfer = newDrop( drop, m_destURL, m_destTmpURL, m_destDropZone );

	if( transfer ) {
		connect( transfer, SIGNAL( finished() ), this, SLOT( slotDestFinished() ) );
	}
	else if( ! m_destURL.hasHost() )
		m_destFinished = true;

	if( m_sourceFinished && m_destFinished ) {
		execDiff();
	}
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotSourceFinished() {
	QObject* obj = (QObject*)sender();
	disconnect( obj, 0, this, 0 );
	m_sourceFinished = true;
	m_sourceDropZone->slotProgress( -1, 100 );

	if( m_sourceFinished && m_destFinished ) {
		execDiff();
	}
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotDestFinished() {
	QObject* obj = (QObject*)sender();
	disconnect( obj, 0, this, 0 );
	m_destFinished = true;
	m_destDropZone->slotProgress( -1, 100 );

	if( m_sourceFinished && m_destFinished ) {
		execDiff();
	}
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotSourceStatusChanged( long, unsigned int status ) {
	if( status == Transfer::Canceled ) {
		if( QFile::exists( m_sourceTmpURL.url() ) )
			QFile::remove( m_sourceTmpURL.url() );

		m_sourceFinished = false ;
		m_sourceDropZone->clear();
	}
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotDestStatusChanged( long, unsigned int status ) {
	if( status == Transfer::Canceled ) {
		if( QFile::exists( m_destTmpURL.url() ) )
			QFile::remove( m_destTmpURL.url() );

		m_destFinished = false ;
		m_destDropZone->clear();
	}
}
//-----------------------------------------------
Transfer* KBearFileDiffPlugin::newDrop( QDropEvent* drop, KURL& url, KURL& tmpURL, FileDiffDropWidget* dropZone  ) {
	KURL::List urls;
	KIO::MetaData sourceMeta;
	KURLDrag::decode( drop, urls, sourceMeta );

	if( urls.isEmpty() )
		return 0L;

	Transfer* transfer = 0L;
	if( urls.first().protocol() == "file" ) {
		url = tmpURL = urls.first();
	}
	else {
		url = urls.first();
		tmpURL = locateLocal( "tmp", url.fileName() );
		sourceMeta.insert("Action", "copy" );
		sourceMeta.insert("DestID", QString::number(-1) );
		sourceMeta.insert("DestURL", tmpURL.url() );
		KURLDrag* drag = KURLDrag::newDrag( urls, sourceMeta, m_outputWidget );

		TransferGroup* group = m_api->transferManager()->addTransfers( drag );
		transfer = group->transfers()->first();
		if( ! transfer )
			return 0L;

		connect( transfer, SIGNAL( percent( long, unsigned long ) ), dropZone, SLOT( slotProgress( long, unsigned long ) ) );
	}

	KURL tmp = url;
	if( tmp.protocol() == QString::fromLatin1("kbearftp") )
		tmp.setProtocol( QString::fromLatin1("ftp") );

	dropZone->setFileName( tmp.prettyURL() );

	return transfer;
}
//-----------------------------------------------
void KBearFileDiffPlugin::showDiff() {
	m_diffEdit->append( m_stdOut );
	slotToggleSyntaxHighlight();
	m_separator->hide();
	m_sourceDropZone->hide();
	m_destDropZone->hide();
	m_diffEdit->show();
	QApplication::restoreOverrideCursor();
}
//-----------------------------------------------
void KBearFileDiffPlugin::execDiff() {
	kdDebug()<<k_funcinfo<<endl;

	QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) );
	if( m_diffTmpFile )
		delete m_diffTmpFile;
	if( m_diffProcess )
		delete m_diffProcess;

	m_stdOut = QString::null;
	m_diffTmpFile = new KTempFile();
	m_diffTmpFile->setAutoDelete( true );
	m_diffProcess = new KProcess();
	connect( m_diffProcess, SIGNAL( processExited( KProcess* ) ), this, SLOT( slotProcessExited( KProcess* ) ) );
	connect( m_diffProcess, SIGNAL( receivedStdout( KProcess*, char*, int ) ), this, SLOT( slotReceivedStdout( KProcess*, char*, int ) ) );
	connect( m_diffProcess, SIGNAL( receivedStderr( KProcess*, char*, int ) ), this, SLOT( slotReceivedStderr( KProcess*, char*, int ) ) );

	m_diffProcess->setUseShell( true );
	m_diffProcess->setEnvironment( "LANG", "C");
	*m_diffProcess << "diff" << "-U65535" << "-dr";
	*m_diffProcess << KProcess::quote( m_sourceTmpURL.path() );
	*m_diffProcess << KProcess::quote( m_destTmpURL.path() );
	if( ! m_diffProcess->start( KProcess::NotifyOnExit, KProcess::All ) ) {
		QApplication::restoreOverrideCursor();
		KMessageBox::error( m_outputWidget, i18n( "Could not invoke the \"diff\" command." ) );
		delete m_diffProcess;
		m_diffProcess = 0L;
		return;
  }
	m_separator->hide();
	m_sourceDropZone->hide();
	m_destDropZone->setText(i18n( "<h1>Executing \"diff\" command</h1>" ) );

}
//-----------------------------------------------
void KBearFileDiffPlugin::slotProcessExited( KProcess* ) {
	// diff has exit status 0 and 1 for success
	if( m_diffProcess->normalExit() && ( m_diffProcess->exitStatus() == 0 || m_diffProcess->exitStatus() == 1 ) ) {
		if( m_stdOut.isEmpty() ) {
			QApplication::restoreOverrideCursor();
			KMessageBox::information( m_outputWidget, i18n("No differences found.") );
		}
		else {
			if( m_sourceTmpURL != m_sourceURL )
				m_stdOut.replace( QRegExp( m_sourceTmpURL.path()), m_sourceURL.path() );

			if( m_destTmpURL != m_destURL )
				m_stdOut.replace( QRegExp( m_destTmpURL.path()), m_destURL.path() );

			QTextStream* stream = m_diffTmpFile->textStream();
			if( stream )
				*stream << m_stdOut;

			showDiff();
			return;
		}
	}
	else {
		QApplication::restoreOverrideCursor();
		KMessageBox::error( m_outputWidget, i18n("Diff command failed (%1):\n").arg( m_diffProcess->exitStatus() ) + m_stdErr );
	}
	if( QFile::exists( m_sourceTmpURL.path() ) && m_sourceTmpURL != m_sourceURL )
		QFile::remove( m_sourceTmpURL.path() );

	if( QFile::exists( m_destTmpURL.path() ) && m_destTmpURL != m_destURL )
		QFile::remove( m_destTmpURL.path() );

	slotClear();
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotReceivedStdout( KProcess*, char* buffer, int length ) {
//	kdDebug()<<k_funcinfo<<" buffer="<<buffer<<endl;
	m_stdOut += QString::fromLocal8Bit( buffer, length );
}
//-----------------------------------------------
void KBearFileDiffPlugin::slotReceivedStderr( KProcess*, char* buffer, int length ) {
//	kdDebug()<<k_funcinfo<<" : " << QString::fromLocal8Bit( buffer, length ) << endl;
	m_stdErr += QString::fromLocal8Bit( buffer, length );

}
//-----------------------------------------------
void KBearFileDiffPlugin::searchForExternalParts() {
	// only execute once
	static bool init = false;
	if ( init )
		return;
	init = true;

	// search all parts that can handle text/x-diff
	KTrader::OfferList offers = KTrader::self()->query("text/x-diff", "'KParts/ReadOnlyPart' in ServiceTypes");
	KTrader::OfferList::const_iterator it;
	for( it = offers.begin(); it != offers.end(); ++it ) {
		KService::Ptr ptr = (*it);
		s_externalParts << ptr->name();
	}
	return;
}
//-----------------------------------------------


