// Windows/FileFind.cpp

#include "StdAfx.h"

#include "FileFind.h"
#ifndef _UNICODE
#include "../Common/StringConvert.h"
#endif

#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#ifdef HAVE_LSTAT
extern int global_use_lstat;
int global_use_lstat=1; // default behaviour : p7zip stores symlinks instead of dumping the files they point to
#endif

#define NEED_NAME_WINDOWS_TO_UNIX
#include "myPrivate.h"

// #define TRACEN(u) u;
#define TRACEN(u)  /* */

void my_windows_split_path(const AString &p_path, AString &dir , AString &base) {
  int pos = p_path.ReverseFind('/');
  if (pos == -1) {
    // no separator
    dir  = ".";
    if (p_path.IsEmpty())
      base = ".";
    else
      base = p_path;
  } else if ((pos+1) < p_path.Length()) {
    // true separator
    base = p_path.Mid(pos+1);
    while ((pos >= 1) && (p_path[pos-1] == '/'))
      pos--;
    if (pos == 0)
      dir = "/";
    else
      dir = p_path.Left(pos);
  } else {
    // separator at the end of the path
    // pos = p_path.find_last_not_of("/");
    pos = -1;
    int ind = 0;
    while (p_path[ind]) {
      if (p_path[ind] != '/')
        pos = ind;
      ind++;
    }
    if (pos == -1) {
      base = "/";
      dir = "/";
    } else {
      my_windows_split_path(p_path.Left(pos+1),dir,base);
    }
  }
}

static void my_windows_split_path(const UString &p_path, UString &dir , UString &base) {
  int pos = p_path.ReverseFind(L'/');
  if (pos == -1) {
    // no separator
    dir  = L".";
    if (p_path.IsEmpty())
      base = L".";
    else
      base = p_path;
  } else if ((pos+1) < p_path.Length()) {
    // true separator
    base = p_path.Mid(pos+1);
    while ((pos >= 1) && (p_path[pos-1] == L'/'))
      pos--;
    if (pos == 0)
      dir = L"/";
    else
      dir = p_path.Left(pos);
  } else {
    // separator at the end of the path
    // pos = p_path.find_last_not_of("/");
    pos = -1;
    int ind = 0;
    while (p_path[ind]) {
      if (p_path[ind] != L'/')
        pos = ind;
      ind++;
    }
    if (pos == -1) {
      base = L"/";
      dir = L"/";
    } else {
      my_windows_split_path(p_path.Left(pos+1),dir,base);
    }
  }
}

static int filter_pattern(const char *string , const char *pattern , int flags_nocase) {
  if ((string == 0) || (*string==0)) {
    if (pattern == 0)
      return 1;
    while (*pattern=='*')
      ++pattern;
    return (!*pattern);
  }

  switch (*pattern) {
  case '*':
    if (!filter_pattern(string+1,pattern,flags_nocase))
      return filter_pattern(string,pattern+1,flags_nocase);
    return 1;
  case 0:
    if (*string==0)
      return 1;
    break;
  case '?':
    return filter_pattern(string+1,pattern+1,flags_nocase);
  default:
    if (   ((flags_nocase) && (tolower(*pattern)==tolower(*string)))
           || (*pattern == *string)
       ) {
      return filter_pattern(string+1,pattern+1,flags_nocase);
    }
    break;
  }
  return 0;
}


namespace NWindows {
namespace NFile {
namespace NFind {

static const TCHAR kDot = TEXT('.');

bool CFileInfo::IsDots() const
{ 
  if (!IsDirectory() || Name.IsEmpty())
    return false;
  if (Name[0] != kDot)
    return false;
  return Name.Length() == 1 || (Name[1] == kDot && Name.Length() == 2);
}

#ifndef _UNICODE
bool CFileInfoW::IsDots() const
{ 
  if (!IsDirectory() || Name.IsEmpty())
    return false;
  if (Name[0] != kDot)
    return false;
  return Name.Length() == 1 || (Name[1] == kDot && Name.Length() == 2);
}
#endif

static bool originalFilename(const UString & src, AString & res)
{
  // Try to recover the original filename
  res = "";
  int is_good = 1;
  int i=0;
  while (src[i])
  {
    if (src[i] >= 256) {
      return false;
    } else {
      res += char(src[i]);
    }
    i++;
  }
  return true;
}



// Warning this function cannot update "fileInfo.Name"
static int fillin_CFileInfo(CFileInfo &fileInfo,const char *filename) {
  struct stat stat_info;

  int ret;
#ifdef HAVE_LSTAT
  if (global_use_lstat) {
    ret = lstat(filename,&stat_info);
  } else
#endif
  {
     ret = stat(filename,&stat_info);
  }

  if (ret != 0) return ret;

  /* FIXME : FILE_ATTRIBUTE_HIDDEN ? */
  if (S_ISDIR(stat_info.st_mode)) {
    fileInfo.Attributes = FILE_ATTRIBUTE_DIRECTORY;
  } else {
    fileInfo.Attributes = FILE_ATTRIBUTE_ARCHIVE;
  }

  if (!(stat_info.st_mode & S_IWUSR))
    fileInfo.Attributes |= FILE_ATTRIBUTE_READONLY;

  fileInfo.Attributes |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((stat_info.st_mode & 0xFFFF) << 16);

  RtlSecondsSince1970ToFileTime( stat_info.st_ctime, &fileInfo.CreationTime );
  RtlSecondsSince1970ToFileTime( stat_info.st_mtime, &fileInfo.LastWriteTime );
  RtlSecondsSince1970ToFileTime( stat_info.st_atime, &fileInfo.LastAccessTime );

  if (S_ISDIR(stat_info.st_mode)) {
    fileInfo.Size = 0;
  } else { // file or symbolic link
    fileInfo.Size = stat_info.st_size; // for a symbolic link, size = size of filename
  }
  return 0;
}

static int fillin_CFileInfo(CFileInfo &fileInfo,const char *dir,const char *name) {
  struct stat stat_info;
  char filename[MAX_PATHNAME_LEN];

  size_t dir_len = strlen(dir);
  size_t name_len = strlen(name);
  size_t total = dir_len + 1 + name_len; // 1 = strlen("/");
  if (total >= MAX_PATHNAME_LEN) throw "fillin_CFileInfo - internal error - MAX_PATHNAME_LEN";
  memcpy(filename,dir,dir_len);
  if (dir_len >= 1)
  {
	if (filename[dir_len-1] == CHAR_PATH_SEPARATOR)
	{ // delete the '/'
		dir_len--;
	}
  }
  filename[dir_len] = CHAR_PATH_SEPARATOR;
  memcpy(filename+(dir_len+1),name,name_len+1); // copy also final '\0'

  fileInfo.Name = name;

  int ret = fillin_CFileInfo(fileInfo,filename);
  if (ret != 0) {
	AString err_msg = "stat error for ";
        err_msg += filename;
        err_msg += " (";
        err_msg += strerror(errno);
        err_msg += ")";
        throw err_msg;
  }
  return ret;
}

////////////////////////////////
// CFindFile

bool CFindFile::Close()
{

  if(_dirp == 0)
    return true;
  int ret = closedir(_dirp);
  if (ret == 0)
  {
    _dirp = 0;
    return true;
  }
  return false;
}
           
bool CFindFile::FindFirst(LPCTSTR wildcard, CFileInfo &fileInfo)
{
  Close();

  if ((!wildcard) || (wildcard[0]==0)) {
    SetLastError(ERROR_PATH_NOT_FOUND);
    return false;
  }
 
  my_windows_split_path(nameWindowToUnix(wildcard),_directory,_pattern);
  
  TRACEN((printf("CFindFile::FindFirst : %s (dirname=%s,pattern=%s)\n",wildcard,(const char *)_directory,(const char *)_pattern)))

  _dirp = ::opendir((const char *)_directory);
  TRACEN((printf("CFindFile::FindFirst : opendir=%p\n",_dirp)))

  if ((_dirp == 0) && (global_use_utf16_conversion)) {
    // Try to recover the original filename
    UString ustr = MultiByteToUnicodeString(_directory, 0);
    AString resultString;
    bool is_good = originalFilename(ustr, resultString);
    if (is_good) {
      _dirp = ::opendir((const char *)resultString);
      _directory = resultString;
    }
  }

  if (_dirp == 0) return false;

  struct dirent *dp;
  while ((dp = readdir(_dirp)) != NULL) {
    if (filter_pattern(dp->d_name,(const char *)_pattern,0) == 1) {
      int retf = fillin_CFileInfo(fileInfo,(const char *)_directory,dp->d_name);
      if (retf)
      {
         TRACEN((printf("CFindFile::FindFirst : closedir-1(dirp=%p)\n",_dirp)))
         closedir(_dirp);
         _dirp = 0;
         SetLastError( ERROR_NO_MORE_FILES );
         return false;
      }
      TRACEN((printf("CFindFile::FindFirst -%s- true\n",dp->d_name)))
      return true;
    }
  }

  TRACEN((printf("CFindFile::FindFirst : closedir-2(dirp=%p)\n",_dirp)))
  closedir(_dirp);
  _dirp = 0;
  SetLastError( ERROR_NO_MORE_FILES );
  return false;
}

bool CFindFile::FindFirst(LPCWSTR wildcard, CFileInfoW &fileInfo)
{
  Close();
  CFileInfo fileInfo0;
  AString Awildcard = UnicodeStringToMultiByte(wildcard, CP_ACP);
  bool bret = FindFirst((LPCTSTR)Awildcard, fileInfo0);
  if (bret)
  {
     fileInfo.Attributes = fileInfo0.Attributes;
     fileInfo.CreationTime = fileInfo0.CreationTime;
     fileInfo.LastAccessTime = fileInfo0.LastAccessTime;
     fileInfo.LastWriteTime = fileInfo0.LastWriteTime;
     fileInfo.Size = fileInfo0.Size;
     fileInfo.Name = GetUnicodeString(fileInfo0.Name, CP_ACP);
  }
  return bret;
}

bool CFindFile::FindNext(CFileInfo &fileInfo)
{
  if (_dirp == 0)
  {
    SetLastError( ERROR_INVALID_HANDLE );
    return false;
  }

  struct dirent *dp;
  while ((dp = readdir(_dirp)) != NULL) {
      if (filter_pattern(dp->d_name,(const char *)_pattern,0) == 1) {
        int retf = fillin_CFileInfo(fileInfo,(const char *)_directory,dp->d_name);
        if (retf)
        {
           TRACEN((printf("FindNextFileA -%s- ret_handle=FALSE (errno=%d)\n",dp->d_name,errno)))
           return false;

        }
        TRACEN((printf("FindNextFileA -%s- true\n",dp->d_name)))
        return true;
      }
    }
  TRACEN((printf("FindNextFileA ret_handle=FALSE (ERROR_NO_MORE_FILES)\n")))
  SetLastError( ERROR_NO_MORE_FILES );
  return false;
}

bool CFindFile::FindNext(CFileInfoW &fileInfo)
{
  CFileInfo fileInfo0;
  bool bret = FindNext(fileInfo0);
  if (bret)
  {
     fileInfo.Attributes = fileInfo0.Attributes;
     fileInfo.CreationTime = fileInfo0.CreationTime;
     fileInfo.LastAccessTime = fileInfo0.LastAccessTime;
     fileInfo.LastWriteTime = fileInfo0.LastWriteTime;
     fileInfo.Size = fileInfo0.Size;
     fileInfo.Name = GetUnicodeString(fileInfo0.Name, CP_ACP);
  }
  return bret;
}

bool FindFile(LPCTSTR wildcard, CFileInfo &fileInfo)
{
  // CFindFile finder;
  // return finder.FindFirst(wildcard, fileInfo);
  AString dir,base;
  my_windows_split_path(wildcard, dir , base);
  int ret = fillin_CFileInfo(fileInfo,nameWindowToUnix(wildcard));
  fileInfo.Name = base;
  TRACEN((printf("FindFile(%s,CFileInfo) ret=%d\n",wildcard,ret)))
  return (ret == 0);
}

#ifndef _UNICODE
bool FindFile(LPCWSTR wildcard, CFileInfoW &fileInfo)
{
  // CFindFile finder;
  // return finder.FindFirst(wildcard, fileInfo);
  AString name = UnicodeStringToMultiByte(wildcard, CP_ACP); 
  CFileInfo fileInfo0;
  int ret = fillin_CFileInfo(fileInfo0,nameWindowToUnix((const char *)name));
  TRACEN((printf("FindFile-1(%s,CFileInfo) ret=%d\n",(const char *)name,ret)))
  if (ret != 0)
  {
    // Try to recover the original filename
    AString resultString;
    bool is_good = originalFilename(wildcard, resultString);
    if (is_good) {
       ret = fillin_CFileInfo(fileInfo0,nameWindowToUnix((const char *)resultString));
       TRACEN((printf("FindFile-2(%s,CFileInfo) ret=%d\n",(const char *)resultString,ret)))
    }
  }
  if (ret == 0)
  {
     UString dir,base;
     my_windows_split_path(wildcard, dir , base);
     fileInfo.Attributes = fileInfo0.Attributes;
     fileInfo.CreationTime = fileInfo0.CreationTime;
     fileInfo.LastAccessTime = fileInfo0.LastAccessTime;
     fileInfo.LastWriteTime = fileInfo0.LastWriteTime;
     fileInfo.Size = fileInfo0.Size;
     fileInfo.Name = base;
  }
  return (ret == 0);
}
#endif

bool DoesFileExist(LPCTSTR name)
{
  CFileInfo fileInfo;
  int ret = fillin_CFileInfo(fileInfo,nameWindowToUnix(name));
  TRACEN((printf("DoesFileExist(%s) ret=%d\n",name,ret)))
  return (ret == 0);
}

#ifndef _UNICODE
bool DoesFileExist(LPCWSTR name)
{
  AString Aname = UnicodeStringToMultiByte(name, CP_ACP); 
  bool bret = DoesFileExist((LPCTSTR)Aname);
  if (bret) return bret;

  // Try to recover the original filename
  AString resultString;
  bool is_good = originalFilename(name, resultString);
  if (is_good) {
     bret = DoesFileExist((const char *)resultString);
  }
  return bret;
}
#endif

/////////////////////////////////////
// CEnumerator

bool CEnumerator::NextAny(CFileInfo &fileInfo)
{
  if(_findFile.IsHandleAllocated())
    return _findFile.FindNext(fileInfo);
  else
    return _findFile.FindFirst(_wildcard, fileInfo);
}

bool CEnumerator::Next(CFileInfo &fileInfo)
{
  while(true)
  {
    if(!NextAny(fileInfo))
      return false;
    if(!fileInfo.IsDots())
      return true;
  }
}

bool CEnumerator::Next(CFileInfo &fileInfo, bool &found)
{
  if (Next(fileInfo))
  {
    found = true;
    return true;
  }
  found = false;
  return (::GetLastError() == ERROR_NO_MORE_FILES);
}

#ifndef _UNICODE
bool CEnumeratorW::NextAny(CFileInfoW &fileInfo)
{
  if(_findFile.IsHandleAllocated())
    return _findFile.FindNext(fileInfo);
  else
    return _findFile.FindFirst(_wildcard, fileInfo);
}

bool CEnumeratorW::Next(CFileInfoW &fileInfo)
{
  while(true)
  {
    if(!NextAny(fileInfo))
      return false;
    if(!fileInfo.IsDots())
      return true;
  }
}

bool CEnumeratorW::Next(CFileInfoW &fileInfo, bool &found)
{
  if (Next(fileInfo))
  {
    found = true;
    return true;
  }
  found = false;
  return (::GetLastError() == ERROR_NO_MORE_FILES);
}

#endif

}}}
