/*
** 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> --- June 1998
 */

/*
 * CvsIgnore.cpp --- parse the .cvsignore
 */

#include "stdafx.h"

#include <stdlib.h>
#include <string.h>

#include "FileTraversal.h"
#include "AppConsole.h"
#include "CvsIgnore.h"
#include "getline.h"
#include "fnmatch.h"
#include "CvsPrefs.h"

#ifndef WIN32
#	include <ctype.h>
#endif /* !WIN32 */

#ifdef qMacCvsPP
#       include "MacCvsApp.h"
#       include "MacMisc.h"
#endif /* qMacCvsPP */

#ifdef WIN32
#	include "wincvs.h"	

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

/* Parse a line of space-separated wildcards and add them to the list. */
/* mostly duplicated from cvs/src/ignore.c */
static void ign_add (char *ign, bool hold, std::vector<CStr> & ignlist)
{
	if (!ign || !*ign)
		return;

	int ign_hold = ignlist.size() == 0 ? -1 : (int)ignlist.size();

	for (; *ign; ign++)
	{
		char *mark;
		char save;

		/* ignore whitespace before the token */
		if (isspace (*ign))
			continue;

		/*
		* if we find a single character !, we must re-set the ignore list
		* (saving it if necessary).  We also catch * as a special case in a
		* global ignore file as an optimization
		*/
		if ((!*(ign+1) || isspace (*(ign+1))) && (*ign == '!' || *ign == '*'))
		{
			if (!hold)
			{
				/* permanently reset the ignore list */
				ignlist.erase(ignlist.begin(), ignlist.end());

				/* if we are doing a '!', continue; otherwise add the '*' */
				if (*ign == '!')
				{
					continue;
				}
			}
			else if (*ign == '!')
			{
				/* temporarily reset the ignore list */
				if (ign_hold >= 0)
				{
					ignlist.erase(ignlist.begin(), ignlist.begin() + ign_hold);
					ign_hold = -1;
				}
				continue;
			}
		}

		/* find the end of this token */
		for (mark = ign; *mark && !isspace (*mark); mark++)
			/* do nothing */ ;

		save = *mark;
		*mark = '\0';

		ignlist.push_back(CStr(ign));

		*mark = save;
		if (save)
			ign = mark;
		else
			ign = mark - 1;
	}
}


// Read .cvsignore file and append entries to ignlist
static bool ReadIgnoredFile(const char* fileName, std::vector<CStr> & ignlist)
{
	FILE *fp;
	char *line = NULL;
	size_t line_allocated = 0;

	fp = fopen(fileName, "r");
	if(fp != 0L)
	{
		while (getline (&line, &line_allocated, fp) >= 0)
			ign_add (line, false, ignlist);

		if (ferror (fp))
			cvs_err("Problem while parsing '%s'", fileName);

		fclose (fp);
	}
	
	if(line != 0L)
		free (line);

	return(fp != 0L);
}


void BuildIgnoredList(std::vector<CStr> & ignlist, const char *path)
{
	ignlist.erase(ignlist.begin(), ignlist.end());

	static std::vector<CStr> deflist;
	static bool deflistparsed = false;
	
	if(!deflistparsed)
	{
#ifdef TARGET_OS_MAC
		UFSSpec theFolder;
		CStr home;
		
		if(MacGetPrefsFolder(theFolder, home) == noErr)
		{
#else /* !TARGET_OS_MAC */
		if(gCvsPrefs.Home() != 0L)
		{
			CStr home(gCvsPrefs.Home());
#endif /* !TARGET_OS_MAC */
			struct stat sb;
			if (stat(home, &sb) != 0 || !S_ISDIR(sb.st_mode))
			{
				cvs_err("Warning : unable to access your HOME path '%s'\n", gCvsPrefs.Home());
				cvs_err("          ~/.cvsignore will not be read\n");
			}
			if(!home.empty() && !home.endsWith(kPathDelimiter))
				home << kPathDelimiter;
			home << ".cvsignore";

			// Read .cvsignore file from HOME directory.
			// If HOME dir doesn't have such a file then
			// try to read global file from executable path.
			if(ReadIgnoredFile(home, deflist) == false)
			{
				CStr appPath;
				
#ifdef WIN32
				((CWincvsApp *)AfxGetApp())->GetAppPath(appPath);
#elif defined(qUnix)
				// Unix version uses only .cvsignore file in HOME dir
#else
#	ifdef qMacCvsPP
				appPath = CMacCvsApp::GetAppPath();
#	endif /* qMacCvsPP */
#endif
				if(!appPath.empty())
				{
					if(!appPath.endsWith(kPathDelimiter))
						appPath << kPathDelimiter;
					
					appPath << ".cvsignore";
					
					ReadIgnoredFile(appPath, deflist);
				}
			}
		}
		deflistparsed = true;
	}
	else
		ignlist = deflist;
	
#if qUnix || TARGET_RT_MAC_MACHO
	// from cvs/src/ignore.c
	CStr ign_default(". .. core RCSLOG tags TAGS RCS SCCS .make.state\
 .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\
 *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe .DS_Store _$* *$");
	ign_add (ign_default, false, ignlist);
#endif

	UStr ignPath(path);
	if(!ignPath.endsWith(kPathDelimiter))
		ignPath << kPathDelimiter;
	ignPath << ".cvsignore";

	// load the file from the current source directory
	ReadIgnoredFile(ignPath, ignlist);
}

bool MatchIgnoredList(const char * filename, const std::vector<CStr> & ignlist)
{
	std::vector<CStr>::const_iterator i;
	for(i = ignlist.begin(); i != ignlist.end(); ++i)
	{
		if(fnmatch (*i, filename, 0) == 0)
			return true;
	}
	return false;
}
