/**------------------------------------------------------------------------
 * @file
 *
 * Copyright 2001 by the University of Toronto (UT).
 *
 *                       All Rights Reserved
 * 
 * Permission to copy and modify this software and its documentation 
 * only for internal use in your organization is hereby granted, 
 * provided that this notice is retained thereon and on all copies.  
 * UT makes no representations as to the suitability and operability 
 * of this software for any purpose.  It is provided "as is" without 
 * express or implied warranty.
 *
 * UT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
 * SHALL UT BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * No other rights, including for example, the right to redistribute 
 * this software and its documentation or the right to prepare 
 * derivative works, are granted unless specifically provided in a 
 * separate license agreement.
 *
 * Author: Diego Macrini
 * 
 * $Log: DirWalker.cpp,v $
 * Revision 1.2  2002/05/16 22:49:29  dmac
 * Several bugs fixed and a new DAG-derived class added
 *
 * Revision 1.1.1.1  2002/03/10 07:38:07  dmac
 * Imported sources
 *
 *
 *-----------------------------------------------------------------------*/
//#include <stdlib.h>
#include <iostream>
//#include <stdio.h>
#include "DirWalker.h"
#include "BasicUtils.h"

#ifndef WIN32
#include <sys/stat.h>
#define FILE_SEP '/'
#else
#define FILE_SEP '\\'
#endif

// IMPORTANT: The code for WIN32 hasn't been tested and it's very likely
// that it still requires some modifications.

DirWalker::DirWalker()
{ 
	hFile = INVALID_HANDLE_VALUE; 
	*szDirPath = '\0';
	nDirPathLen = 0;
}

/*static*/ 
bool DirWalker::CheckFilePath(const char* szFullFileName)
{
	int nLen = strlen(szFullFileName);
#ifdef WIN32
	char cWrongFileSep = '/';
#else
	char cWrongFileSep = '\\';
#endif
	for (int i = 0; i < nLen; i++)
	{
		if (szFullFileName[i] == cWrongFileSep)
		{
			std::cerr << "\nError: path '" << szFullFileName 
				<< "' contains one or more " << cWrongFileSep
				<< " characters.\nUse " << FILE_SEP 
				<< " as the file-path separator character." << std::endl;
			return false;
		}
	}

	return true;
}

bool DirWalker::OpenDir(const char* szDirName)	
{ 
	CloseDir();

	if (!CheckFilePath(szDirName))
		return false;

	nDirPathLen = strlen(szDirName);

#ifdef WIN32
	char szDirNameWC[MAX_PATH_SIZE];
	strcpy(szDirNameWC, szDirName);

	if (szDirPath[nDirPathLen - 1] != FILE_SEP)
		strcat(szDirNameWC, "\\*");
	else
		strcat(szDirNameWC, "*");

	hFile = FindFirstFile(szDirNameWC, &findData); // perhaps szDirName + "*.*"
#else
	hFile = opendir(szDirName); 
#endif

	if (hFile != INVALID_HANDLE_VALUE)
	{
		memcpy(szDirPath, szDirName, nDirPathLen + 1);

		if (szDirPath[nDirPathLen - 1] != FILE_SEP)
		{
			szDirPath[nDirPathLen] = FILE_SEP;
			szDirPath[nDirPathLen + 1] = '\0';
			nDirPathLen++;
		}

		return true;
	}
	
	return false;
}

void DirWalker::CloseDir()
{ 
	if (hFile != INVALID_HANDLE_VALUE)
	{ 
#ifdef WIN32
		FindClose(hFile);
#else
		closedir(hFile); 
#endif
		hFile = INVALID_HANDLE_VALUE;
		*szDirPath = '\0';
		nDirPathLen = 0;
	} 
}

//! buddSize has to be big enough or the function will return false.
bool DirWalker::GetNextFileOrDir(char* szFileName, size_t buffSize)
{
	const char* sz;
	size_t len;

	// Clear out szFileName before hand in case something goes wrong.
	*szFileName = '\0';

	// Read next file
#ifdef WIN32
	if (FindNextFile(hFile, &findData) == 0) 
		return false;

	sz = findData.cFileName;
#else
	struct dirent* dp;

	if ((dp = readdir(hFile)) == 0) 
		return false;

	sz = dp->d_name;
#endif

	len = strlen(sz);

	if (nDirPathLen + len > buffSize - 1)
		return false;

	if (sz[0] == '.' && (len == 1 || (len == 2 && sz[1] == '.')))
	{
		memcpy(szFileName, sz, len + 1);
	}
	else
	{
		memcpy(szFileName, szDirPath, nDirPathLen);
		memcpy(szFileName + nDirPathLen, sz, len + 1);
	}

	return true;
}

/*! 
	\brief Returns the next file in the opened directory.

	@param szFileName pointer to a buffer where the full file name is to be written.

	@buffSize size of th ebuffer poninted by szFileName.

	@szFileExt pointer to a buffer in which the file extention is to be stored.
	The buffer has to be large enough to hold 3 characters. If szFileExt is NULL,
	no file extention is returned.
*/
bool DirWalker::GetNextFile(char* szFileName, size_t buffSize, char* szFileExt /*=NULL*/)
{
	bool bSuccess;

	do {
		bSuccess = GetNextFileOrDir(szFileName, buffSize);

	} while(bSuccess && IsDirectory(szFileName));

	if (szFileExt && bSuccess)
	{
		*szFileExt = '\0'; // Init the string
		size_t len = strlen(szFileName + nDirPathLen);
		char* pEnd = szFileName + nDirPathLen + len - 1;

		for (int i = 0; i < 4; i++, pEnd--)
		{
			if (*pEnd == '.')
			{
				strcpy(szFileExt, pEnd + 1);
				break;
			}
		}
	}

	return bSuccess;
}

bool DirWalker::GetNextDir(char* szFileName, size_t buffSize)
{
	bool bSuccess, bDotDirectory;

	do {
		bSuccess = GetNextFileOrDir(szFileName, buffSize);

		// Ignore dot and dot-dot directory
		if (!strcmp(szFileName, ".") || !strcmp(szFileName, ".."))
			bDotDirectory = true;
		else
			bDotDirectory = false;

	} while(bSuccess && (bDotDirectory || !IsDirectory(szFileName)));

	return bSuccess;
}

bool DirWalker::ParseFileName(const char* szFileName)
{
	using namespace std;
	
	string str(szFileName);
	string::size_type extPos = str.rfind('.');
	string::size_type namePos = str.rfind(FILE_SEP, extPos);
	
	if (extPos == string::npos)
		extPos = str.size() - 1;
	
	if (namePos == string::npos)
		namePos = 0;
	else
		namePos++;
	
	ext = str.substr(extPos + 1, str.size() - extPos + 1);
	name = str.substr(namePos, extPos - namePos);
	
	if (namePos > 0)
		path = str.substr(0, namePos);
	else
		path.resize(0);
		//path.clear();

	//cerr << "[" << path << "]" << "[" << name << "]" << "[" << ext << "]" << endl;
	
	return true;
}

/*!
	@brief Parses a file name of the form prefixNumer0Param1Number1Param2Number2....
	
	The results are left in the 'params' map, such that the first key in the map is prefix
	and its value is Number0. The remaining param-number pairs are added in order. The map
	can then be accessed sequentially or by key.
	
	@return the 'params' maps is filled in ONLY with the string-value pairs in the file name. That is, 
	params is empty if there is no valied pair, and params would not include trailing text in the name.
*/
bool DirWalker::ParseFileNameParams()
{
	using namespace std;
	
	string prefix("null");
	string number;
	bool bLastChar, bIsNum, bReadingText = true;
	
	params.clear();

	for (unsigned int s = 0, e = 1; e < name.size(); e++)
	{
		bLastChar = (e == name.size() - 1);
		bIsNum = (isdigit(name[e]) != 0);

		if (bReadingText && (bIsNum || bLastChar))
		{
			prefix = name.substr(s, (bIsNum) ? e-s:e-s+1);
			s = e;
			bReadingText = false;
			
			// Handle special case: single last digit.
			if (bIsNum && bLastChar) e--;
		}
		else if (!bReadingText && (!bIsNum ||  bLastChar))
		{
			number = name.substr(s, (!bIsNum) ? e-s:e-s+1);
			params[prefix] = atoi(number.c_str());
			s = e;
			bReadingText = true;
		}
	}

	return params.empty();
}

/*static*/ 
bool DirWalker::IsDirectory(const char* szFileName)
{
#ifdef WIN32
	DWORD info = GetFileAttributes(szFileName);
	return (info & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
	struct stat info;

	if (stat(szFileName, &info) != 0)
		return false;
	else
		return (info.st_mode & S_IFDIR) != 0;
#endif
}

/*static*/ 
const char* DirWalker::FindFileExtention(const char* szFileName)
{
	size_t len = strlen(szFileName);
	const char* pEnd = szFileName + len - 1;

	for (; pEnd > szFileName && *pEnd != FILE_SEP; pEnd--)
		if (*pEnd == '.')
			return pEnd + 1;

	return szFileName + len;
}

/*static*/ 
bool DirWalker::CompFileExt(const char* szFileName, const char* szFileExt)
{
	return strcmp(szFileExt, FindFileExtention(szFileName)) == 0;
}

/*static*/ 
void DirWalker::ChangeFileExt(const char* szFileName, const char* szExt, char* szNewName)
{
	const char* szOldExt = FindFileExtention(szFileName);
	int nBytes = szOldExt - szFileName;

	memcpy(szNewName, szFileName, nBytes);

	if (szExt)
	{
		if (szNewName[nBytes - 1] != '.')
			szNewName[nBytes++] = '.';

		strcpy(szNewName + nBytes, szExt);
	}
	/* szExt == NULL*/
	else if (szNewName[nBytes - 1] == '.')
		szNewName[nBytes - 1] = '\0';
	else
		szNewName[nBytes] = '\0';
}

/*!
	@brief Returs the last-modification time difference between szFileName1 and szFileName2
	returns:
		-1 First file time is earlier than second file time. 
		0 First file time is equal to second file time. 
		1 First file time is later than second file time. 
*/
/*static*/
int DirWalker::CompareLastModifDate(const char* szFileName1, const char* szFileName2)
{
#ifndef WIN32
	struct stat info1, info2;

	info1.st_mtime = info2.st_mtime = 0;

	stat(szFileName1, &info1);
	stat(szFileName2, &info2);

	return info1.st_mtime - info2.st_mtime;
#else
	WIN32_FIND_DATA findData1, findData2;
	HANDLE hFile;
	
	if ((hFile = FindFirstFile(szFileName1, &findData1)) == INVALID_HANDLE_VALUE)
		return 1;
	
	FindClose(hFile);

	if ((hFile = FindFirstFile(szFileName2, &findData2)) == INVALID_HANDLE_VALUE)
		return 1;

	FindClose(hFile);

	return CompareFileTime(&findData1.ftLastWriteTime, &findData2.ftLastWriteTime);
#endif
}

/*!
	\brief Creates a file name with the given prefix and extension of the 
	form 'prefixNNN.ext'.

	NNN will be the next number corresponding to the gratest NNN of file name 
	in the given directory with the same name form.

	\param szDirName directory name.
	\param szPrefix prefix for the file
	\param szFileExt extension of the file
	\param szFileName buffer where the created file name is stored. Should be of size MAX_PATH_SIZE.
*/
/*static*/ 
int DirWalker::CreateFileName(const char* szDirName, const char* szPrefix, 
							  const char* szFileExt, char szFileName[])
{
	DirWalker dw;

	*szFileName = '\0';	// just for safety

	if (!DirWalker::IsDirectory(szDirName) || !dw.OpenDir(szDirName))
		return -1;

	int nPrefixLen = strlen(szPrefix), nPathLen = dw.GetDirPathLen();
	char szExt[10];
	int n, nMax = -1;

	while (dw.GetNextFile(szFileName, MAX_PATH_SIZE, szExt))
	{
		if (!strncmp(szPrefix, szFileName + nPathLen, nPrefixLen) && 
			!strcmp(szFileExt, szExt))
		{
			n = atoi(szFileName + nPathLen + nPrefixLen);

			if (n > nMax)
				nMax = n;
		}
	}

	sprintf(szFileName, "%s%s%d.%s", dw.GetDirPath(), szPrefix, ++nMax, szFileExt);

	return nMax;
}

/*!The following sample code will search the current  directory
     for the entry name:*/
/*int ReadDir(char* name)
{
	DIR* dirp = opendir(".");
	struct dirent* dp;
	int errno;

	while (dirp) 
	{
		errno = 0;

		if ((dp = readdir(dirp)) != NULL) 
		{
			DBG_MSG(dp->d_name)
			if (strcmp(dp->d_name, name) == 0) 
			{
				closedir(dirp);
				return FOUND;
			}
		} 
		else 
		{
			if (errno == 0) 
			{
				closedir(dirp);
				return NOT_FOUND;
			}

			closedir(dirp);

			return READ_ERROR;
		}
	}

	return OPEN_ERROR;
}*/
