////////////////////////////////////////////////////////////////////////////////
// FileList.cpp
// Copyright Björn Ganster
////////////////////////////////////////////////////////////////////////////////

#include "FileList.h"
#include "BGCPString.h"

#ifdef _WIN32
// Constructor: list all files, with wild cards
FileList::FileList ()
: VirtualClass("FileList", FileListID)
{
	hFind = INVALID_HANDLE_VALUE;
}

// Destructor
FileList::~FileList ()
{
	if (hFind != INVALID_HANDLE_VALUE)
		FindClose(hFind);
}

bool FileList::start (const wchar_t* path)
{
	if (hFind != INVALID_HANDLE_VALUE)
		FindClose(hFind);

	hFind = FindFirstFileW(path, &FindFileData);
	return (hFind != INVALID_HANDLE_VALUE);
}

// Get members
const wchar_t* FileList::getFileName() const
{ return FindFileData.cFileName; }
INT64 FileList::getSize() const
{
   LARGE_INTEGER li;
   li.LowPart = FindFileData.nFileSizeLow;
   li.HighPart = FindFileData.nFileSizeHigh;
   return li.QuadPart; 
}

// Navigate
bool FileList::operator++()
{
   return (FindNextFileW (hFind, &FindFileData) > 0);
}

bool FileList::isDirectory()
{
   if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0)
      return true;
   else
      return false;
}

#else

#include <sys/stat.h>

// Constructor: list all files, with wild cards
FileList::FileList()
: VirtualClass("FileList", FileListID)
{
}

// Destructor
FileList::~FileList()
{
   closedir(dir);
}

void FileList::getStats(const char* fn)
{
	BGCPString fullFN = m_path;
	fullFN.addPathSep(fn);
	struct stat s;
	stat(fullFN.getAsPath(), &s);
	size = s.st_size; 
	isDir = S_ISDIR(s.st_mode);
}

bool FileList::start (const TCHAR* path)
{
	mask = path;
	m_path = BGCPString(path, CP_SYSTEM);

	// Todo: this code probably does not handle all necessary cases
	if (m_path != "*") {
		int pathSepPos = m_path.findLastChar(OSPathSep);
		if (pathSepPos >= 0) {
			mask = m_path.copyPart(pathSepPos+1, mask.getByteCount());
			m_path.setByteCount(pathSepPos);
		}
	}

	dir  = opendir(m_path.getAsPath());
	if (dir == NULL) {
		return false;
	}

	entry = readdir (dir);
	if (entry == NULL) {
		return false;
	}

	bool doContinue;
	do {
		if (matches()) {
			getStats (entry->d_name);
			return true;
		} else {
			doContinue = (operator++());
		}
	} while (doContinue);
	return false;
}

// Get members
const TCHAR* FileList::getFileName() const
{ return entry->d_name; }

// Navigate
bool FileList::operator++()
{
   entry = readdir(dir);

   if (entry == NULL) {
      return false;
   }

   if (mask.empty()) {
      return true;
   }

   bool done;
   do {
      if (matches()) {
	 getStats (entry->d_name);
         return true;
      } else {
         done = !operator++();
      }
   } while (!done);

   return false;
}

INT64 FileList::getSize() const
{
   return size; 
}


bool FileList::isDirectory()
{
   return isDir;
}

// Check if entry matches mask
bool FileList::matches() const
{
   if (mask.empty()) {
      return true;
   }

   int hashPos = mask.findChar('*');
   if (hashPos >= 0) {
      size_t nameLen = strlen(entry->d_name);
      if (nameLen >= mask.getByteCount()-1) {
            const TCHAR* p1 = mask.getPtr<char>();
            const TCHAR* p2 = entry->d_name;
            if (memcmp (p1, p2, hashPos-1)) {
               return false;
			}
         if (hashPos < mask.getByteCount()-1) {
            size_t matchLen = mask.getByteCount()+hashPos;
            const TCHAR* p1 = mask.getPtr<char>(hashPos+1);
            const TCHAR* p2 = &(entry->d_name[strlen(entry->d_name)-matchLen+1]);
            if (memcmp (p1, p2, matchLen)) {
               return false;
			}
         }
         return true;
      } else {
         return false;
	  }
   } else {
      // No hash: only exact matches allowed
      return mask == entry->d_name;
   }
}
#endif

// Find first match for mask
bool FileList::findFirstMatch (const PathChar* mask, BGCPString& result)
{
   FileList list;
   if (list.start (mask)) {
      result = BGCPString(list.getFileName(), CP_SYSTEM);
      return true;
   } else {
      result.clear();
      return false;
   }
}
