/**************************************************************************
**                             tdefile_diff.cpp
**                              -------------------
**      begin                   : Sun Jan 20 23:25:44 2002
**      copyright               : (C) 2002-2003 by Otto Bruggeman
**      email                   : otto.bruggeman@home.nl
**
***************************************************************************/
/***************************************************************************
**
**   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.
**
***************************************************************************/

/*
** Patch by Volker Augustin for empty diff files. Februari 8, 2002
**
** Patched to work with CVS from after February 26, 2002 Otto
**
** Patched to work with CVS from after March 24, 2002 Otto
**
** Added support for Perforce diffs, April 26, 2003 Otto Bruggeman
**
** Added support for Subversion diffs, September 11, 2003 Otto Bruggeman
*/

#include <tqcstring.h>
#include <tqdatetime.h>
#include <tqdict.h>
#include <tqfile.h>
#include <tqregexp.h>
#include <tqvalidator.h>

#include <kdebug.h>
#include <kgenericfactory.h>
#include <tdelocale.h>
#include <tdeprocess.h>
#include <kurl.h>

#include "tdefile_diff.h"

K_EXPORT_COMPONENT_FACTORY(tdefile_diff, KGenericFactory<KDiffPlugin>("tdefile_diff"))

KDiffPlugin::KDiffPlugin(TQObject *parent, const char *name,
                         const TQStringList &preferredItems)
	: KFilePlugin(parent, name, preferredItems)
{
	kdDebug(7034) << "diff plugin" << endl;

	KFileMimeTypeInfo* info = addMimeTypeInfo( "text/x-diff" );

	KFileMimeTypeInfo::GroupInfo* group;
	group = addGroupInfo( info, "General", i18n( "General" ) );
	addItemInfo( group, "Files", i18n( "Files" ), TQVariant::UInt );
	addItemInfo( group, "First", i18n( "First File" ), TQVariant::String );
	addItemInfo( group, "Format", i18n( "Format" ), TQVariant::String );
	addItemInfo( group, "DiffProgram", i18n( "Diff Program" ), TQVariant::String );
	addItemInfo( group, "Hunks", i18n( "Hunks" ), TQVariant::UInt );
	group = addGroupInfo( info, "Statistics", i18n( "Statistics" ) );
	addItemInfo( group, "Insert", i18n( "Insertions" ), TQVariant::UInt );
	addItemInfo( group, "Modify", i18n( "Changes" ),    TQVariant::UInt );
	addItemInfo( group, "Delete", i18n( "Deletions" ),  TQVariant::UInt );
}

bool KDiffPlugin::readInfo( KFileMetaInfo& info, uint what )
{
	// This is a hack to avoid using the what stuff, since it is not yet implemented
	what = 0;

	// Used to determine if false or true should be returned
	bool dataSet = false;

	KFileMetaInfoGroup group;

	TQFile file( info.path() );
	TQStringList lines;

	if( file.open( IO_ReadOnly ) )
	{
		TQTextStream stream( &file );
		while (!stream.eof())
		{
			lines.append( stream.readLine() );
		}
		file.close();
	}

	TQString format;
	TQString program;

	enum KDiffPlugin::Format       diffFormat;
	enum KDiffPlugin::DiffProgram  diffProgram;

	diffFormat  = determineDiffFormat ( lines );

	format = determineI18nedFormat( diffFormat );

	diffProgram = determineDiffProgram( lines );

	program = determineI18nedProgram( diffProgram );

	int numberOfAdditions = 0;
	int numberOfDeletions = 0;
	int numberOfChanges = 0;
	int numberOfHunks = 0;
	int numberOfFiles = 0;


	if ( what != KFileMetaInfo::Fastest )
	{
		determineDiffInfo( lines, diffFormat, &numberOfFiles, &numberOfHunks, &numberOfAdditions, &numberOfChanges, &numberOfDeletions );
	}

	TQString filename;
	TQRegExp firstFile( "^Index: (.*)" );
	TQStringList::ConstIterator it = lines.begin();

	it = lines.begin();
	while ( it != lines.end() )
	{
		if ( firstFile.exactMatch( (*it) ) )
		{
			filename = firstFile.cap(1);
			// only interested in the first filename
			break;
		}
		++it;
	}

	kdDebug(7034) << "Diff Format         : " << format << endl; // i18n-ed but that is not a problem unless i get i18n-ed debug output ah well, we'll figure something out when then happens

	if (what != KFileMetaInfo::Fastest )
	{
		// These dont get calculated in fastest mode...
		kdDebug(7034) << "Number of additions : " << numberOfAdditions << endl;
		kdDebug(7034) << "Number of deletions : " << numberOfDeletions << endl;
		kdDebug(7034) << "Number of changes   : " << numberOfChanges << endl;
		kdDebug(7034) << "Number of hunks     : " << numberOfHunks << endl;
	}

	group = appendGroup( info, "General" );

	if ( numberOfFiles != 0 && what != KFileMetaInfo::Fastest )
	{
		appendItem( group, "Files", numberOfFiles );
		dataSet = true;
	}

	if ( !filename.isEmpty() )
	{
		appendItem( group, "First", filename );
		dataSet = true;
	}

	if ( !format.isEmpty() )
	{
		appendItem( group, "Format", format );
		dataSet = true;
	}

	if ( !program.isEmpty() )
	{
		appendItem( group, "DiffProgram", program );
		dataSet = true;
	}

	if ( numberOfHunks != 0 && what != KFileMetaInfo::Fastest )
	{
		appendItem( group, "Hunks", numberOfHunks );
		dataSet = true;
	}

	group = appendGroup( info, "Statistics" );

	if ( numberOfAdditions != 0 && what != KFileMetaInfo::Fastest )
	{
		appendItem( group, "Insert", numberOfAdditions );
		dataSet = true;
	}

	if ( numberOfChanges != 0 && what != KFileMetaInfo::Fastest )
	{
		appendItem( group, "Modify", numberOfChanges );
		dataSet = true;
	}

	if ( numberOfDeletions != 0 && what != KFileMetaInfo::Fastest )
	{
		appendItem( group, "Delete", numberOfDeletions );
		dataSet = true;
	}

	return dataSet;
}

enum KDiffPlugin::Format KDiffPlugin::determineDiffFormat( const TQStringList lines ) const
{
	TQString line;

	if ( lines.count() == 0 )
	{
		return KDiffPlugin::Empty;
	}

	TQStringList::ConstIterator it = lines.begin();

	while ( it != lines.end() )
	{
		line = (*it);
		if ( line.find( TQRegExp( "^[0-9]+[0-9,]*[acd][0-9]+[0-9,]*$" ), 0 ) == 0 )
		{
			return KDiffPlugin::Normal;
		}
		else if ( line.find( TQRegExp( "^--- " ), 0 ) == 0 )
		{
			// unified has first a '^--- ' line, then a '^+++ ' line
			return KDiffPlugin::Unified;
		}
		else if ( line.find( TQRegExp( "^\\*\\*\\* [^\\t]+\\t" ), 0 ) == 0 )
		{
			// context has first a '^*** ' line, then a '^--- ' line
			return KDiffPlugin::Context;
		}
		else if ( line.find( TQRegExp( "^[acd][0-9]+ [0-9]+" ), 0 ) == 0 )
		{
			return KDiffPlugin::RCS;
		}
		else if ( line.find( TQRegExp( "^[0-9]+[0-9,]*[acd]" ), 0 ) == 0 )
		{
			return KDiffPlugin::Ed;
		}
		++it;
	}
	return KDiffPlugin::Unknown;
}

enum KDiffPlugin::DiffProgram KDiffPlugin::determineDiffProgram( const TQStringList lines ) const
{
	if ( lines.count() == 0 )
	{
		return KDiffPlugin::Undeterminable;
	}

	TQStringList::ConstIterator it = lines.begin();
	// very crude, might need some more refining
	TQRegExp diffRE( "^diff .*" );
	TQRegExp p4sRE("^==== ");

	bool indexFound = false;

	while ( it != lines.end() )
	{
		if ( (*it).startsWith( "Index:" ) )
			indexFound = true;
		else if ( (*it).startsWith( "retrieving revision") )
			return KDiffPlugin::CVSDiff;
		else if ( diffRE.exactMatch( *it ) )
			return KDiffPlugin::Diff;
		else if ( p4sRE.exactMatch( *it ) )
			return KDiffPlugin::Perforce;

		++it;
	}

	if ( indexFound ) // but no "retrieving revision" found like only cvs diff adds.
		return KDiffPlugin::SubVersion;

	return KDiffPlugin::Undeterminable;
}

const TQString KDiffPlugin::determineI18nedFormat( enum KDiffPlugin::Format diffFormat ) const
{
	TQString format;
	switch( diffFormat )
	{
	case KDiffPlugin::Context:
		format = i18n( "Context" );
		break;
	case KDiffPlugin::Ed:
		format = i18n( "Ed" );
		break;
	case KDiffPlugin::Normal:
		format = i18n( "Normal" );
		break;
	case KDiffPlugin::RCS:
		format = i18n( "RCS" );
		break;
	case KDiffPlugin::Unified:
		format = i18n( "Unified" );
		break;
	case KDiffPlugin::Empty:
		format = i18n( "Not Available (file empty)" );
		break;
	case KDiffPlugin::Unknown:
		format = i18n( "Unknown" );
		break;
	case KDiffPlugin::SideBySide:
		format = i18n( "Side by Side" );
	}
	return format;
}

const TQString KDiffPlugin::determineI18nedProgram( enum KDiffPlugin::DiffProgram diffProgram ) const
{
	TQString program;

	switch( diffProgram )
	{
	case KDiffPlugin::CVSDiff:
		program = i18n( "CVSDiff" );
		break;
	case KDiffPlugin::Diff:
		program = i18n( "Diff" );
		break;
	case KDiffPlugin::Diff3:
		program = i18n( "Diff3" );
		break;
	case KDiffPlugin::Perforce:
		program = i18n( "Perforce" );
		break;
	case KDiffPlugin::SubVersion:
		program = i18n( "SubVersion" );
		break;
	case KDiffPlugin::Undeterminable:
		program = i18n( "Unknown" );
		break;
	}
	return program;
}

void KDiffPlugin::determineDiffInfo( const TQStringList lines,
                                     enum KDiffPlugin::Format diffFormat,
                                     int* numberOfFiles,
                                     int* numberOfHunks,
                                     int* numberOfAdditions,
                                     int* numberOfChanges,
                                     int* numberOfDeletions )
{
	TQString line;

	TQRegExp edAdd( "([0-9]+)(|,([0-9]+))a" );
	TQRegExp edDel( "([0-9]+)(|,([0-9]+))d" );
	TQRegExp edMod( "([0-9]+)(|,([0-9]+))c" );

	TQRegExp normalAdd( "[0-9]+a([0-9]+)(|,([0-9]+))" );
	TQRegExp normalDel( "([0-9]+)(|,([0-9]+))d(|[0-9]+)" );
	TQRegExp normalMod( "([0-9]+)(|,([0-9]+))c([0-9]+)(|,([0-9]+))" );

	TQRegExp rcsAdd( "a[0-9]+ ([0-9]+)" );
	TQRegExp rcsDel( "d[0-9]+ ([0-9]+)" );

	TQStringList::ConstIterator it = lines.begin();

	switch( diffFormat )
	{
	case KDiffPlugin::Context:
		while ( it != lines.end() )
		{
			if ( (*it).startsWith("***************") )
			{
				(*numberOfHunks)++;
//				kdDebug(7034) << "Context Hunk      : " << (*it) << endl;
			}
			else if ( (*it).startsWith("***") )
			{
				(*numberOfFiles)++;
//				kdDebug(7034) << "Context File      : " << (*it) << endl;
			}
			else if ( (*it).startsWith("---") ) {} // ignore
			else if ( (*it).startsWith("+") )
			{
				(*numberOfAdditions)++;
//				kdDebug(7034) << "Context Insertion : " << (*it) << endl;
			}
			else if ( (*it).startsWith("-") )
			{
				(*numberOfDeletions)++;
//				kdDebug(7034) << "Context Deletion  : " << (*it) << endl;
			}
			else if ( (*it).startsWith("!") )
			{
				(*numberOfChanges)++;
//				kdDebug(7034) << "Context Modified  : " << (*it) << endl;
			}
			else if ( (*it).startsWith(" ") )
			{
//				kdDebug(7034) << "Context Context   : " << (*it) << endl;
			}
			else
			{
//				kdDebug(7034) << "Context Unknown   : " << (*it) << endl;
			}

			++it;
		}
		(*numberOfChanges) /= 2; // changes are in both parts of the hunks
		(*numberOfFiles) -= (*numberOfHunks); // it counts old parts of a hunk as files :(
		break;
	case KDiffPlugin::Ed:
		while ( it != lines.end() )
		{
			if ( (*it).startsWith( "diff" ) )
			{
				(*numberOfFiles)++;
//				kdDebug(7034) << "Ed File         : " << (*it) << endl;
			}
			else if ( edAdd.exactMatch( (*it) ) )
			{
//				kdDebug(7034) << "Ed Insertion    : " << (*it) << endl;
				(*numberOfHunks)++;
				++it;
				while( it != lines.end() && !(*it).startsWith(".") )
				{
					(*numberOfAdditions)++;
//					kdDebug(7034) << "Ed Insertion    : " << (*it) << endl;
					++it;
				}
			}
			else if ( edDel.exactMatch( (*it) ) )
			{
//				kdDebug(7034) << "Ed Deletion     : " << (*it) << endl;
				(*numberOfHunks)++;
				(*numberOfDeletions) += (edDel.cap(3).isEmpty() ? 1 : edDel.cap(3).toInt() - edDel.cap(1).toInt() + 1);
//				kdDebug(7034) << "Ed noOfLines    : " << (edDel.cap(3).isEmpty() ? 1 : edDel.cap(3).toInt() - edDel.cap(1).toInt() + 1) << endl;
			}
			else if ( edMod.exactMatch( (*it) ) )
			{
//				kdDebug(7034) << "Ed Modification : " << (*it) << endl;
				if ( edMod.cap(3).isEmpty() )
					(*numberOfDeletions)++;
				else
					(*numberOfDeletions) += edMod.cap(3).toInt() - edMod.cap(1).toInt() + 1;
				(*numberOfHunks)++;
				++it;
				while( it != lines.end() && !(*it).startsWith(".") )
				{
					(*numberOfAdditions)++;
//					kdDebug(7034) << "Ed Modification : " << (*it) << endl;
					++it;
				}
			}
			else
			{
//				kdDebug(7034) << "Ed Unknown      : " << (*it) << endl;
			}

			++it;
		}
		break;
	case KDiffPlugin::Normal:
		while ( it != lines.end() )
		{
			if ( (*it).startsWith( "diff" ) )
			{
				(*numberOfFiles)++;
//				kdDebug(7034) << "Normal File         : " << (*it) << endl;
			}
			else if ( normalAdd.exactMatch( *it ) )
			{
//				kdDebug(7034) << "Normal Insertion    : " << (*it) << endl;
				(*numberOfHunks)++;
				if ( normalAdd.cap(3).isEmpty() )
				{
					(*numberOfAdditions)++;
//					kdDebug(7034) << "Normal Addition : " << 1 << endl;
				}
				else
				{
					(*numberOfAdditions) += normalAdd.cap(3).toInt() - normalAdd.cap(1).toInt() + 1;
//					kdDebug(7034) << "Normal Addition : " << normalAdd.cap(3).toInt() - normalAdd.cap(1).toInt() + 1 << endl;
				}
			}
			else if ( normalDel.exactMatch( *it ) )
			{
//				kdDebug(7034) << "Normal Deletion     : " << (*it) << endl;
				(*numberOfHunks)++;
				if ( normalDel.cap(3).isEmpty() )
				{
					(*numberOfDeletions)++;
//					kdDebug(7034) << "Normal Deletion : " << 1 << endl;
				}
				else
				{
					(*numberOfDeletions) += normalDel.cap(3).toInt() - normalDel.cap(1).toInt() + 1;
//					kdDebug(7034) << "Normal Deletion : " << normalDel.cap(3).toInt() - normalDel.cap(1).toInt() + 1 << endl;
				}
			}
			else if ( normalMod.exactMatch( *it ) )
			{
//				kdDebug(7034) << "Normal Modification : " << (*it) << endl;
				(*numberOfHunks)++;
				if ( normalMod.cap(3).isEmpty() )
				{
					(*numberOfDeletions)++;
//					kdDebug(7034) << "Normal Deletion : " << 1 << endl;
				}
				else
				{
					(*numberOfDeletions) += normalMod.cap(3).toInt() - normalMod.cap(1).toInt() + 1;
//					kdDebug(7034) << "Normal Deletion : " << normalMod.cap(3).toInt() - normalMod.cap(1).toInt() + 1 << endl;
				}
				if ( normalMod.cap(6).isEmpty() )
				{
					(*numberOfAdditions)++;
//					kdDebug(7034) << "Normal Addition : " << 1 << endl;
				}
				else
				{
					(*numberOfAdditions) += normalMod.cap(6).toInt() - normalMod.cap(4).toInt() + 1;
//					kdDebug(7034) << "Normal Addition : " << normalMod.cap(6).toInt() - normalMod.cap(4).toInt() + 1 << endl;
				}
			}
			else if ( (*it).startsWith(">") )
			{
//				numberOfAdditions++;
//				kdDebug(7034) << "Normal Insertion    : " << (*it) << endl;
			}
			else if ( (*it).startsWith("<") )
			{
//				numberOfDeletions++;
//				kdDebug(7034) << "Normal Deletion     : " << (*it) << endl;
			}
			else
			{
//				kdDebug(7034) << "Normal Unknown      : " << (*it) << endl;
			}

			++it;
		}
		break;
	case KDiffPlugin::RCS:
		while ( it != lines.end() )
		{
			if ( (*it).startsWith( "diff" ) ) // works for cvs diff, have to test for normal diff
			{
//				kdDebug(7034) << "RCS File      : " << (*it) << endl;
				(*numberOfFiles)++;
			}
			else if ( rcsAdd.exactMatch( *it ) )
			{
//				kdDebug(7034) << "RCS Insertion : " << (*it) << endl;
				(*numberOfHunks)++;
				(*numberOfAdditions) += rcsAdd.cap(1).toInt();
//				kdDebug(7034) << "RCS noOfLines : " << rcsAdd.cap(1).toInt() << endl;
			}
			else if ( rcsDel.exactMatch( *it ) )
			{
//				kdDebug(7034) << "RCS Deletion  : " << (*it) << endl;
				(*numberOfHunks)++;
				(*numberOfDeletions) += rcsDel.cap(1).toInt();
//				kdDebug(7034) << "RCS noOfLines : " << rcsDel.cap(1).toInt() << endl;
			}
			else
			{
//				kdDebug(7034) << "RCS Unknown   : " << (*it) << endl;
			}

			++it;
		}
		break;
	case KDiffPlugin::Unified:
		while ( it != lines.end() )
		{
			if ( (*it).startsWith("@@ ") )
			{
				(*numberOfHunks)++;
//				kdDebug(7034) << "Unified Hunk      : " << (*it) << endl;
			}
			else if ( (*it).startsWith("---") )
			{
				(*numberOfFiles)++;
//				kdDebug(7034) << "Unified File      : " << (*it) << endl;
			}
			else if ( (*it).startsWith("+++") ) {} // ignore (dont count as insertion)
			else if ( (*it).startsWith("+") )
			{
				(*numberOfAdditions)++;
//				kdDebug(7034) << "Unified Insertion : " << (*it) << endl;
			}
			else if ( (*it).startsWith("-") )
			{
				(*numberOfDeletions)++;
//				kdDebug(7034) << "Unified Deletion  : " << (*it) << endl;
			}
			else if ( (*it).startsWith(" ") )
			{
//				kdDebug(7034) << "Unified Context   : " << (*it) << endl;
			}
			else
			{
//				kdDebug(7034) << "Unified Unknown   : " << (*it) << endl;
			}

			++it;
		}
		break;
	case KDiffPlugin::Empty:
	case KDiffPlugin::Unknown:
	case KDiffPlugin::SideBySide:
		break;
	}
}

#include "tdefile_diff.moc"
