// Windows/FileFind.cpp

#include "StdAfx.h"

#include "FileFind.h"
#include "FileIO.h"

#include "../Common/StringConvert.h"


#ifndef _UNICODE
extern bool g_IsNT;
#endif

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

#ifdef ENV_HAVE_LSTAT
extern "C"
{

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.Len()) {
    // true separator
    base = p_path.Ptr(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.Len()) {
    // true separator
    base = p_path.Ptr(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 {

#ifdef SUPPORT_DEVICE_FILE
bool IsDeviceName(CFSTR n);
#endif

#if defined(WIN_LONG_PATH)
bool GetLongPath(CFSTR fileName, UString &res);
#endif

namespace NFind {

bool CFileInfo::IsDots() const
{
  if (!IsDir() || Name.IsEmpty())
    return false;
  if (Name[0] != FTEXT('.'))
    return false;
  return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == FTEXT('.'));
}

#define WIN_FD_TO_MY_FI(fi, fd) \
  fi.Attrib = fd.dwFileAttributes; \
  fi.CTime = fd.ftCreationTime; \
  fi.ATime = fd.ftLastAccessTime; \
  fi.MTime = fd.ftLastWriteTime; \
  fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \
  fi.IsDevice = false;

  /*
  #ifdef UNDER_CE
  fi.ObjectID = fd.dwOID;
  #else
  fi.ReparseTag = fd.dwReserved0;
  #endif
  */

#ifndef _UNICODE

static inline UINT GetCurrentCodePage() { return ::AreFileApisANSI() ? CP_ACP : CP_OEMCP; }

static void ConvertWIN32_FIND_DATA_To_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)
{
  WIN_FD_TO_MY_FI(fi, fd);
  fi.Name = fas2fs(fd.cFileName);
}
#endif

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

bool CFindFile::Close()
{
  if(_dirp == 0)
    return true;
  int ret = closedir(_dirp);
  if (ret == 0)
  {
    _dirp = 0;
    return true;
  }
  return false;
}


static bool originalFilename(const UString & src, AString & res)
{
  // Try to recover the original filename
  res = "";
  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,bool ignoreLink) {
  struct stat stat_info;

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

  // printf("fillin_CFileInfo(%s,%d)=%d  mode=%o\n",filename,(int)ignoreLink,ret,(unsigned)stat_info.st_mode);


  if (ret != 0) return ret;

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

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

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

  RtlSecondsSince1970ToFileTime( stat_info.st_ctime, &fileInfo.CTime );
  RtlSecondsSince1970ToFileTime( stat_info.st_mtime, &fileInfo.MTime );
  RtlSecondsSince1970ToFileTime( stat_info.st_atime, &fileInfo.ATime );

  fileInfo.IsDevice = false;

  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 &fi,const char *dir,const char *name,bool ignoreLink) {
  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; // 1 = strlen("/"); + le zero character
  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'

#ifdef _UNICODE
  fi.Name = GetUnicodeString(name, CP_ACP);
#else
  fi.Name = name;
#endif

  int ret = fillin_CFileInfo(fi,filename,ignoreLink);
  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;
}

bool CFindFile::FindFirst(CFSTR cfWildcard, CFileInfo &fi, bool ignoreLink)
{
  if (!Close())
    return false;

  AString Awildcard = UnicodeStringToMultiByte(cfWildcard, CP_ACP);
  const char * wildcard = (const char *)Awildcard;


  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(fi,(const char *)_directory,dp->d_name,ignoreLink);
      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::FindNext(CFileInfo &fi)
{
  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(fi,(const char *)_directory,dp->d_name,false);
        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;
}

#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;

void CFileInfoBase::Clear()
{
  Size = 0;
  MY_CLEAR_FILETIME(CTime);
  MY_CLEAR_FILETIME(ATime);
  MY_CLEAR_FILETIME(MTime);
  Attrib = 0;
}

bool CFileInfo::Find(CFSTR wildcard, bool ignoreLink)
{
  #ifdef SUPPORT_DEVICE_FILE
  if (IsDeviceName(wildcard))
  {
    Clear();
    IsDevice = true;
    NIO::CInFile inFile;
    if (!inFile.Open(wildcard))
      return false;
    Name = wildcard + 4;
    if (inFile.LengthDefined)
      Size = inFile.Length;
    return true;
  }
  #endif
  CFindFile finder;
  if (finder.FindFirst(wildcard, *this,ignoreLink))
    return true;
  #ifdef _WIN32
  {
    DWORD lastError = GetLastError();
    if (lastError == ERROR_BAD_NETPATH || lastError == ERROR_FILE_NOT_FOUND)
    {
      int len = MyStringLen(wildcard);
      if (len > 2 && wildcard[0] == '\\' && wildcard[1] == '\\')
      {
        int pos = FindCharPosInString(wildcard + 2, FTEXT('\\'));
        if (pos >= 0)
        {
          pos += 2 + 1;
          len -= pos;
          CFSTR remString = wildcard + pos;
          int pos2 = FindCharPosInString(remString, FTEXT('\\'));
          FString s = wildcard;
          if (pos2 < 0 || pos2 == len - 1)
          {
            FString s = wildcard;
            if (pos2 < 0)
            {
              pos2 = len;
              s += FTEXT('\\');
            }
            s += FCHAR_ANY_MASK;
            if (finder.FindFirst(s, *this))
              if (Name == FTEXT("."))
              {
		Name.SetFrom(s.Ptr(pos), pos2);
                return true;
              }
            ::SetLastError(lastError);
          }
        }
      }
    }
  }
  #endif
  return false;
}

bool DoesFileExist(CFSTR name)
{
  CFileInfo fi;
  return fi.Find(name) && !fi.IsDir();
}

bool DoesDirExist(CFSTR name)
{
  CFileInfo fi;
  return fi.Find(name) && fi.IsDir();
}
bool DoesFileOrDirExist(CFSTR name)
{
  CFileInfo fi;
  return fi.Find(name);
}

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

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

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

////////////////////////////////
// CFindChangeNotification
// FindFirstChangeNotification can return 0. MSDN doesn't tell about it.

#ifdef _WIN32
bool CFindChangeNotification::Close()
{
  if (!IsHandleAllocated())
    return true;
  if (!::FindCloseChangeNotification(_handle))
    return false;
  _handle = INVALID_HANDLE_VALUE;
  return true;
}

HANDLE CFindChangeNotification::FindFirst(CFSTR pathName, bool watchSubtree, DWORD notifyFilter)
{
  #ifndef _UNICODE
  if (!g_IsNT)
    _handle = ::FindFirstChangeNotification(fs2fas(pathName), BoolToBOOL(watchSubtree), notifyFilter);
  else
  #endif
  {
    _handle = ::FindFirstChangeNotificationW(fs2us(pathName), BoolToBOOL(watchSubtree), notifyFilter);
    #ifdef WIN_LONG_PATH
    if (!IsHandleAllocated())
    {
      UString longPath;
      if (GetLongPath(pathName, longPath))
        _handle = ::FindFirstChangeNotificationW(longPath, BoolToBOOL(watchSubtree), notifyFilter);
    }
    #endif
  }
  return _handle;
}

#ifndef UNDER_CE

bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
{
  driveStrings.Clear();
  #ifndef _UNICODE
  if (!g_IsNT)
  {
    driveStrings.Clear();
    UINT32 size = GetLogicalDriveStrings(0, NULL);
    if (size == 0)
      return false;
    AString buf;
    UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size));
    if (newSize == 0 || newSize > size)
      return false;
    AString s;
    for (UINT32 i = 0; i < newSize; i++)
    {
      char c = buf[i];
      if (c == '\0')
      {
        driveStrings.Add(fas2fs(s));
        s.Empty();
      }
      else
        s += c;
    }
    return s.IsEmpty();
  }
  else
  #endif
  {
    UINT32 size = GetLogicalDriveStringsW(0, NULL);
    if (size == 0)
      return false;
    UString buf;
    UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size));
    if (newSize == 0 || newSize > size)
      return false;
    UString s;
    for (UINT32 i = 0; i < newSize; i++)
    {
      WCHAR c = buf[i];
      if (c == L'\0')
      {
        driveStrings.Add(us2fs(s));
        s.Empty();
      }
      else
        s += c;
    }
    return s.IsEmpty();
  }
}

#endif

#endif // _WIN32

}}}
