/*
** 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- March 1998
 */

/*
 * ImportFilter.cpp : filter the imported files before importing
 */

#include "stdafx.h"

#ifdef WIN32
#	include "resource.h"
#endif /* WIN32 */

#ifdef macintosh
#	include "GUSIInternal.h"
#	include "GUSIFileSpec.h"
#	include "MacMisc.h"
#endif /* macintosh */

#include "ImportFilter.h"
#include "AppConsole.h"

//
// MN - Apply $HOME/.cvsignore to import filter
//
// list_add_type function uses ignore filter to decided whether
// a file should be ignored in importing (user can change this
// in import dialogbox, but this is a default). If user imports
// a whole subdirectory instead of a file then this will
// eliminate ignored files.
//
#include "CvsIgnore.h"	

#if qCvsDebug
#	include "CvsAlert.h"
#endif /* qCvsDebug */

#ifdef macintosh
	static const char *lf = "\r";
#elif defined(WIN32)
	static const char *lf = "\r\n";
#else
	static const char *lf = "\n";
#endif

#ifdef WIN32
#	ifdef _DEBUG
#	define new DEBUG_NEW
#	undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
#	endif
#endif /* WIN32 */

#ifndef NEW
#define NEW new
#endif

ReportConflict::ReportConflict(int mac_type, int mac_creator)
{
	next = 0L;
	status = noConflict;
	ftype = mac_type;
	fcreator = mac_creator;
}

void ReportConflict::PrintOut(CStr & out) const
{
	out = "";
	if(HasConflict())
	{
		out << "Text/Binary conflict, at least these files :";
		out << lf;
		if(!sample1.empty() && !sample2.empty())
		{
			out << "    ";
			out << sample1;
			out << " and ";
			out << sample2;
			out << lf;
		}
	}
	else if(HasTypeCreatorConflict())
	{
		out << "Mac signature mismatch for this type, at least these files :";
		out << lf;
		if(!sample1.empty() && !sample2.empty())
		{
			out << "    ";
			out << sample1;
			out << " and ";
			out << sample2;
			out << lf;
		}
	}
}

#if qCvsDebug
void ReportConflict::PrintOut(void) const
{
	if(pattern.empty())
		return;

	cvs_out("  New type : %s%s (%s)\n",
		(status & hasExtension) ? "*" : "",
		(const char *)pattern,
		(status & isBinary) ? "BINARY" : "TEXT"
	);
	if(!sample1.empty() && !sample2.empty())
	{
		if(HasConflict())
			cvs_out("    Text/Binary CONFLICT ! %s and %s\n",
					(const char *)sample1, (const char *)sample2);
		else if(HasTypeCreatorConflict())
			cvs_out("    Mac signature mismatch ! %s and %s\n",
					(const char *)sample1, (const char *)sample2);
	}
}
#endif /* qCvsDebug */

ReportConflict::~ReportConflict()
{
}

void ReportConflict::AddPattern(const char *newpat, bool isBin, bool isExt,
							 const char *sample)
{
	pattern = newpat;
	sample1 = sample;
	if(isBin)
		status |= isBinary;
	if(isExt)
		status |= hasExtension;
}

bool ReportConflict::Match(const char *pat)
{
	if(pat == 0L || pattern.empty())
		return false;

	int len = strlen(pat);
	int patlen = pattern.length();

	if(status & hasExtension)
	{
		if(len < patlen)
			return false;
		// since it is the server who compares against the pattern,
		// that might be a case compare
#if 0 //def WIN32
		return stricmp(pattern, pat + len - patlen) == 0;
#else /* !WIN32 */
		return strcmp(pattern, pat + len - patlen) == 0;
#endif /* !WIN32 */
	}

#if 0 //def WIN32
	return stricmp(pattern, pat) == 0;
#else /* !WIN32 */
	return strcmp(pattern, pat) == 0;
#endif /* !WIN32 */
}

void ReportConflict::SetConflict(const char *sample)
{
	// one is enough !
	if(HasConflict())
		return;

	sample2 = sample;
	status |= hasConflict;
}

void ReportConflict::SetTypeCreatorConflict(const char *sample)
{
	// one is enough !
	if(HasTypeCreatorConflict() || HasConflict())
		return;

	sample2 = sample;
	status |= hasTypeCreatorConflict;
}

const size_t ReportWarning::maxWarnings = 10;

ReportWarning::ReportWarning(kTextBinTYPE akind)
{
	kind = akind;
	next = 0L;
	tooMuchWarnings = false;
}

ReportWarning::~ReportWarning()
{
}

void ReportWarning::PrintOut(CStr & out) const
{
	out = "";

	if(samples.size() == 0)
		return;

	out = "Warning : ";
	switch(kind)
	{
	default:
	case kFileIsOK: out << "Files are OK"; break;
	case kFileMissing: out << "Files are missing or unreadable"; break;
	case kFileIsAlias: out << "Files are aliases"; break;
	case kFileInvalidName: out << "Files have invalid name"; break;
	case kTextWrongLF: out << "Files have the wrong line feed"; break;
	case kTextIsBinary: out << "Files are binary"; break;
	case kTextEscapeChar: out << "Files have escape characters in it"; break;
	case kTextWrongSig: out << "Text files have the wrong signature"; break;
	case kBinIsText: out << "Files are text, should be binary"; break;
	case kBinWrongSig: out << "Binary files have the wrong signature"; break;
	}
	out << lf;
	std::vector<CStr>::const_iterator i;
	for(i = samples.begin(); i != samples.end(); ++i)
	{
		out << "    ";
		out << *i;
		out << lf;
	}
	if(tooMuchWarnings)
	{
		out << "    More files follow";
		out << lf;
	}
}

#if qCvsDebug
void ReportWarning::PrintOut(void) const
{
	if(samples.size() == 0)
		return;

	cvs_out("  Warning : ");
	switch(kind)
	{
	default:
	case kFileIsOK: cvs_out("Files are OK\n"); break;
	case kFileMissing: cvs_out("Files are missing or unreadable\n"); break;
	case kFileIsAlias: cvs_out("Files are aliases\n"); break;
	case kFileInvalidName: cvs_out("Files have invalid name\n"); break;
	case kTextWrongLF: cvs_out("Files have the wrong line feed\n"); break;
	case kTextIsBinary: cvs_out("Files are binary\n"); break;
	case kTextEscapeChar: cvs_out("Files have escape characters in it\n"); break;
	case kTextWrongSig: cvs_out("Text files have the wrong signature\n"); break;
	case kBinIsText: cvs_out("Files are text, should be binary\n"); break;
	case kBinWrongSig: cvs_out("Binary files have the wrong signature\n"); break;
	}
	std::vector<CStr>::const_iterator i;
	for(i = samples.begin(); i != samples.end(); ++i)
	{
		cvs_out("    %s\n", (const char *)(*i));
	}
	if(tooMuchWarnings)
		cvs_out("    More files follow\n");
}
#endif /* qCvsDebug */

void ReportWarning::AddWarning(const char *sample)
{
	// it's enough !
	if(samples.size() >= maxWarnings)
	{
		tooMuchWarnings = true;
		return;
	}

	samples.push_back(CStr(sample));
}

static void list_add_warning(ReportWarning *& warnings, kTextBinTYPE warn, const char *sample)
{
	// find if it exists already a same kind of warning
	ReportWarning *tmp = warnings;
	while(tmp != 0L)
	{
		if(tmp->Kind() == warn)
			break;
		tmp = tmp->next;
	}
	if(tmp != 0L && tmp->Kind() == warn)
	{
		tmp->AddWarning(sample);
		return;
	}

	// insert new one
	tmp = NEW ReportWarning(warn);
	if(tmp == 0L)
	{
		cvs_err("Impossible to allocate %d bytes !\n", sizeof(ReportWarning));
		return;
	}

	tmp->AddWarning(sample);
	if(warnings == 0L)
	{
		warnings = tmp;
	}
	else
	{
		tmp->next = warnings;
		warnings = tmp;
	}
}

class ImportReport;
static void list_add_type(ImportReport *impReport, ReportConflict *& conflicts, bool isBinary,
						  const char *sample, const char *fullname,
						  int mac_type, int mac_creator);

class ImportReport : public TraversalReport
{
public:
	ImportReport(const char *path, ReportWarning *& w, ReportConflict *& c) : warnings(w), conflicts(c)
	{
		// MN - Apply ignore filter to import file list
		BuildIgnoredList(m_Ignlist, path);	
	}
	virtual ~ImportReport() {}

	ReportWarning *& warnings;
	ReportConflict *& conflicts;
	std::vector<CStr> m_Ignlist;
		// MN - Ignore list

	virtual kTraversal EnterDirectory(const char *fullpath, const char *dirname, const FSSpec * macspec)
	{
		cvs_out("Filtering \'%s\'...\n", fullpath);
		return kContinueTraversal;
	}

	virtual kTraversal ExitDirectory(const char *fullpath)
	{
		return kContinueTraversal;
	}

	virtual kTraversal OnError(const char *err, int errcode)
	{
		list_add_warning(warnings, kFileMissing, err);
		return kContinueTraversal;
	}

	virtual kTraversal OnIdle(const char *fullpath)
	{
		return kContinueTraversal;
	}

	virtual kTraversal OnDirectory(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
#if defined(WIN32) || defined(macintosh)
		if(stricmp(name, "CVS") == 0)
#else
		if(strcmp(name, "CVS") == 0)
#endif
			return kSkipFile;
		return kContinueTraversal;
	}

	virtual kTraversal OnFile(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
		int mac_type = 0;
		int mac_creator = 0;
#ifdef macintosh
#	define MACSPEC , &fsSpec
		GUSIFileSpec inspec(fullname);
		mac_type = inspec.CatInfo()->FileInfo().ioFlFndrInfo.fdType;
		mac_creator = inspec.CatInfo()->FileInfo().ioFlFndrInfo.fdCreator;
		FSSpec fsSpec = inspec;
#else /* !macintosh */
#	define MACSPEC
#endif /* !macintosh */
				
		kTextBinTYPE filetype = FileIsText(name, fullpath MACSPEC);
		if(filetype == kTextIsBinary || filetype == kTextWrongSig)
		{
			kTextBinTYPE bintype = FileIsBinary(name, fullpath MACSPEC);
			if(bintype != kFileIsOK)
				list_add_warning(warnings, bintype, fullname);
			else if(filetype == kTextWrongSig)
			{
				list_add_warning(warnings, filetype, fullname);
				filetype = kTextIsBinary;
			}
		}
		else if(filetype != kFileIsOK)
			list_add_warning(warnings, filetype, fullname);
		
		// add the type
		list_add_type(this, conflicts, filetype == kTextIsBinary,
					  name, fullname, mac_type, mac_creator);

		return kContinueTraversal;
	}

	virtual kTraversal OnAlias(const char *fullpath,
		const char *fullname,
		const char *name,
		const struct stat & dir, const FSSpec * macspec)
	{
		list_add_warning(warnings, kFileIsAlias, fullname);
		return kContinueTraversal;
	}
};

//
// MN - Had to move this function after class ImportReport, because
// this function uses a variable of that type. 
//
// When a file is added to warning/conflict list, it is also
// processed through ignore filter. If file should be ignored
// then the initial state is "IGNORE" in import dialogbox.
//
static void list_add_type(ImportReport *impReport, ReportConflict *& conflicts, bool isBinary,
						  const char *sample, const char *fullname,
						  int mac_type, int mac_creator)
{
	// look for a matching entry
	ReportConflict *entry = conflicts;
	while(entry != 0L)
	{
		if(entry->Match(sample))
		{
			if(entry->HasConflict())
				return;
			
			if(entry->IsBinary() ^ isBinary)
				entry->SetConflict(fullname);
			
			// mac only
			if(entry->GetMacType() != mac_type || entry->GetMacCreator() != mac_creator)
				entry->SetTypeCreatorConflict(fullname);
			
			// it matches perfectly
			return;
		}
		entry = entry->next;
	}

	// find the extension
	const char *tmp = sample, *ext = 0L;
	while((tmp = strchr(tmp, '.')) != 0L)
	{
		ext = tmp++;
	}

	// add a new entry
	ReportConflict *newc = NEW ReportConflict(mac_type, mac_creator);
	if(newc == 0L)
	{
		cvs_err("Impossible to allocate %d bytes !\n", sizeof(ReportConflict));
		return;
	}

	newc->AddPattern(ext != 0L ? ext : sample, isBinary, ext != 0L, fullname);
	if(conflicts == 0L)
		conflicts = newc;
	else
	{
		newc->next = conflicts;
		conflicts = newc;
	}

	// MN - Apply ignore filter. If $HOME/.cvsignore tells that
	// file should be ignored then set object to ignored state.
	if(MatchIgnoredList(sample, impReport->m_Ignlist))
		newc->Ignore();
}

kTraversal list_all_types_recur(const char *path, ReportWarning *& warnings,
								ReportConflict *& conflicts)
{
	ImportReport report(path, warnings, conflicts);
	return FileTraverse(path, report);
}

void free_list_types(ReportWarning * warnings, ReportConflict * conflicts)
{
	ReportWarning *tmpwarn;
	ReportWarning *warn = warnings;
	while(warn != 0L)
	{
		tmpwarn = warn;
		warn = warn->next;
		delete tmpwarn;
	}

	ReportConflict *tmpconf;
	ReportConflict *conf = conflicts;
	while(conf != 0L)
	{
		tmpconf = conf;
		conf = conf->next;
		delete tmpconf;
	}
}

#if qCvsDebug
bool list_all_types(const char *path)
{
	ReportWarning * warnings = 0L;
	ReportConflict * conflicts = 0L;
	list_all_types_recur(path, warnings, conflicts);
	
	cvs_out("Reporting warnings :\n");
	ReportWarning *warn = warnings;
	while(warn != 0L)
	{
		warn->PrintOut();
		warn = warn->next;
	}

	cvs_out("Reporting types :\n");
	ReportConflict *conf = conflicts;
	while(conf != 0L)
	{
		conf->PrintOut();
		conf = conf->next;
	}

	free_list_types(warnings, conflicts);

	return !CvsAlert("Continue importing ?", "Cancel", "Continue");
}
#endif /* qCvsDebug */
