// FSFolderCopy.cpp

#include "StdAfx.h"

// #include <Winbase.h>

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

#include "../../../Windows/DLL.h"
#include "../../../Windows/ErrorMsg.h"
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileName.h"

#include "../../Common/FilePathAutoRename.h"

#include "FSFolder.h"

using namespace NWindows;
using namespace NFile;
using namespace NDir;
using namespace NName;
using namespace NFind;

#ifndef _UNICODE
extern bool g_IsNT;
#endif

namespace NFsFolder {

/*
static bool IsItWindows2000orHigher()
{
  OSVERSIONINFO versionInfo;
  versionInfo.dwOSVersionInfoSize = sizeof(versionInfo);
  if (!::GetVersionEx(&versionInfo))
    return false;
  return (versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
      (versionInfo.dwMajorVersion >= 5);
}
*/

struct CProgressInfo
{
  UInt64 StartPos;
  IProgress *Progress;
};

#ifdef _WIN32

#ifndef PROGRESS_CONTINUE

#define PROGRESS_CONTINUE 0
#define PROGRESS_CANCEL 1

#define COPY_FILE_FAIL_IF_EXISTS 0x00000001


typedef
DWORD
(WINAPI* LPPROGRESS_ROUTINE)(
    LARGE_INTEGER TotalFileSize,
    LARGE_INTEGER TotalBytesTransferred,
    LARGE_INTEGER StreamSize,
    LARGE_INTEGER StreamBytesTransferred,
    DWORD dwStreamNumber,
    DWORD dwCallbackReason,
    HANDLE hSourceFile,
    HANDLE hDestinationFile,
    LPVOID lpData
    );

#endif

static DWORD CALLBACK CopyProgressRoutine(
  LARGE_INTEGER /* TotalFileSize */,          // file size
  LARGE_INTEGER TotalBytesTransferred,  // bytes transferred
  LARGE_INTEGER /* StreamSize */,             // bytes in stream
  LARGE_INTEGER /* StreamBytesTransferred */, // bytes transferred for stream
  DWORD /* dwStreamNumber */,                 // current stream
  DWORD /* dwCallbackReason */,               // callback reason
  HANDLE /* hSourceFile */,                   // handle to source file
  HANDLE /* hDestinationFile */,              // handle to destination file
  LPVOID lpData                         // from CopyFileEx
)
{
  CProgressInfo &progressInfo = *(CProgressInfo *)lpData;
  UInt64 completed = progressInfo.StartPos + TotalBytesTransferred.QuadPart;
  if (progressInfo.Progress->SetCompleted(&completed) != S_OK)
    return PROGRESS_CANCEL;
  return PROGRESS_CONTINUE;
}

typedef BOOL (WINAPI * CopyFileExPointer)(
    IN LPCSTR lpExistingFileName,
    IN LPCSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN LPBOOL pbCancel OPTIONAL,
    IN DWORD dwCopyFlags
    );

typedef BOOL (WINAPI * CopyFileExPointerW)(
    IN LPCWSTR lpExistingFileName,
    IN LPCWSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN LPBOOL pbCancel OPTIONAL,
    IN DWORD dwCopyFlags
    );

#endif


static bool FsCopyFile(CFSTR oldFile, CFSTR newFile, IProgress *progress, UInt64 &completedSize)
{
#ifdef _WIN32
  CProgressInfo progressInfo;
  progressInfo.Progress = progress;
  progressInfo.StartPos = completedSize;
  BOOL CancelFlag = FALSE;
  #ifndef _UNICODE
  if (g_IsNT)
  #endif
  {
    const wchar_t *k_DllName =
        #ifdef UNDER_CE
        L"coredll.dll"
        #else
        L"kernel32.dll"
        #endif
        ;
    CopyFileExPointerW copyFunctionW = (CopyFileExPointerW)
        My_GetProcAddress(::GetModuleHandleW(k_DllName), "CopyFileExW");
    
    IF_USE_MAIN_PATH_2(oldFile, newFile)
    {
      if (copyFunctionW == 0)
        return BOOLToBool(::CopyFileW(fs2us(oldFile), fs2us(newFile), TRUE));
      if (copyFunctionW(fs2us(oldFile), fs2us(newFile), CopyProgressRoutine,
          &progressInfo, &CancelFlag, COPY_FILE_FAIL_IF_EXISTS))
        return true;
    }
    #ifdef WIN_LONG_PATH
    if (USE_SUPER_PATH_2)
    {
      UString longPathExisting, longPathNew;
      if (!GetSuperPaths(oldFile, newFile, longPathExisting, longPathNew, USE_MAIN_PATH_2))
        return false;
      if (copyFunctionW(longPathExisting, longPathNew, CopyProgressRoutine,
          &progressInfo, &CancelFlag, COPY_FILE_FAIL_IF_EXISTS))
        return true;
    }
    #endif
    return false;
  }
  #ifndef _UNICODE
  else
  {
    CopyFileExPointer copyFunction = (CopyFileExPointer)
        ::GetProcAddress(::GetModuleHandleA("kernel32.dll"),
        "CopyFileExA");
    if (copyFunction != 0)
    {
      if (copyFunction(fs2fas(oldFile), fs2fas(newFile),
          CopyProgressRoutine,&progressInfo, &CancelFlag, COPY_FILE_FAIL_IF_EXISTS))
        return true;
      if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
        return false;
    }
    return BOOLToBool(::CopyFile(fs2fas(oldFile), fs2fas(newFile), TRUE));
  }
  #endif

#else
  extern bool wxw_CopyFile(LPCWSTR existingFile, LPCWSTR newFile, bool overwrite);
  return wxw_CopyFile(oldFile, newFile, true);
#endif
}

#ifdef _WIN32
typedef BOOL (WINAPI * MoveFileWithProgressPointer)(
    IN LPCWSTR lpExistingFileName,
    IN LPCWSTR lpNewFileName,
    IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
    IN LPVOID lpData OPTIONAL,
    IN DWORD dwFlags
    );

#endif

#ifdef UNDER_CE
#define NON_CE_VAR(_v_)
#else
#define NON_CE_VAR(_v_) _v_
#endif


static bool FsMoveFile(CFSTR oldFile, CFSTR newFile,
    IProgress * NON_CE_VAR(progress),
    UInt64 & NON_CE_VAR(completedSize))
{
  #if 0 // FIXME #ifndef UNDER_CE
  // if (IsItWindows2000orHigher())
  // {
    CProgressInfo progressInfo;
    progressInfo.Progress = progress;
    progressInfo.StartPos = completedSize;

    MoveFileWithProgressPointer moveFunction = (MoveFileWithProgressPointer)
        My_GetProcAddress(::GetModuleHandle(TEXT("kernel32.dll")),
        "MoveFileWithProgressW");
    if (moveFunction != 0)
    {
      IF_USE_MAIN_PATH_2(oldFile, newFile)
      {
        if (moveFunction(fs2us(oldFile), fs2us(newFile), CopyProgressRoutine,
            &progressInfo, MOVEFILE_COPY_ALLOWED))
          return true;
      }
      #ifdef WIN_LONG_PATH
      if ((!(USE_MAIN_PATH_2) || ::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) && USE_SUPER_PATH_2)
      {
        UString longPathExisting, longPathNew;
        if (!GetSuperPaths(oldFile, newFile, longPathExisting, longPathNew, USE_MAIN_PATH_2))
          return false;
        if (moveFunction(longPathExisting, longPathNew, CopyProgressRoutine,
            &progressInfo, MOVEFILE_COPY_ALLOWED))
          return true;
      }
      #endif
      if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
        return false;
    }
  // }
  // else
  #endif
    return MyMoveFile(oldFile, newFile);
}

static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback,
    const wchar_t *message, const FString &fileName)
{
  return callback->ShowMessage(message + fs2us(fileName));
}

static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback,
    const char *message, const FString &fileName)
{
  return SendMessageError(callback, MultiByteToUnicodeString(message), fileName);
}

static HRESULT FsCopyFile(
    const FString &srcPath,
    const CFileInfo &srcFileInfo,
    const FString &destPathSpec,
    IFolderOperationsExtractCallback *callback,
    UInt64 &completedSize)
{
  FString destPath = destPathSpec;
  if (CompareFileNames(destPath, srcPath) == 0)
  {
    RINOK(SendMessageError(callback, "can not copy file onto itself: ", destPath));
    return E_ABORT;
  }

  Int32 writeAskResult;
  CMyComBSTR destPathResult;
  RINOK(callback->AskWrite(
      fs2us(srcPath),
      BoolToInt(false),
      &srcFileInfo.MTime, &srcFileInfo.Size,
      fs2us(destPath),
      &destPathResult,
      &writeAskResult));
  if (IntToBool(writeAskResult))
  {
    // FString destPathNew = us2fs(destPathResult);
    FString destPathNew( us2fs(destPathResult) );
    RINOK(callback->SetCurrentFilePath(fs2us(srcPath)));
    if (!FsCopyFile(srcPath, destPathNew, callback, completedSize))
    {
      RINOK(SendMessageError(callback, NError::MyFormatMessage(GetLastError()) + L" : ", destPathNew));
      return E_ABORT;
    }
  }
  completedSize += srcFileInfo.Size;
  return callback->SetCompleted(&completedSize);
}

static FString CombinePath(const FString &folderPath, const FString &fileName)
{
  return folderPath + FCHAR_PATH_SEPARATOR + fileName;
}

static bool IsDestChild(const FString &src, const FString &dest)
{
  unsigned len = src.Len();
  if (dest.Len() < len)
    return false;
  if (dest.Len() != len && dest[len] != FCHAR_PATH_SEPARATOR)
    return false;
  return CompareFileNames(dest.Left(len), src) == 0;
}

static HRESULT CopyFolder(
    const FString &srcPath,
    const FString &destPath,
    IFolderOperationsExtractCallback *callback,
    UInt64 &completedSize)
{
  RINOK(callback->SetCompleted(&completedSize));

  if (IsDestChild(srcPath, destPath))
  {
    RINOK(SendMessageError(callback, "can not copy folder onto itself: ", destPath));
    return E_ABORT;
  }

  if (!CreateComplexDir(destPath))
  {
    RINOK(SendMessageError(callback, "can not create folder: ", destPath));
    return E_ABORT;
  }
  CEnumerator enumerator(CombinePath(srcPath, FSTRING_ANY_MASK));
  CDirItem fi;
  while (enumerator.Next(fi))
  {
    const FString srcPath2 = CombinePath(srcPath, fi.Name);
    const FString destPath2 = CombinePath(destPath, fi.Name);
    if (fi.IsDir())
    {
      RINOK(CopyFolder(srcPath2, destPath2, callback, completedSize))
    }
    else
    {
      RINOK(FsCopyFile(srcPath2, fi, destPath2, callback, completedSize));
    }
  }
  return S_OK;
}

/////////////////////////////////////////////////
// Move Operations

static HRESULT FsMoveFile(
    const FString &srcPath,
    const CFileInfo &srcFileInfo,
    const FString &destPath,
    IFolderOperationsExtractCallback *callback,
    UInt64 &completedSize)
{
  if (CompareFileNames(destPath, srcPath) == 0)
  {
    RINOK(SendMessageError(callback, "can not move file onto itself: ", srcPath));
    return E_ABORT;
  }

  Int32 writeAskResult;
  CMyComBSTR destPathResult;
  RINOK(callback->AskWrite(
      fs2us(srcPath),
      BoolToInt(false),
      &srcFileInfo.MTime, &srcFileInfo.Size,
      fs2us(destPath),
      &destPathResult,
      &writeAskResult));
  if (IntToBool(writeAskResult))
  {
    FString destPathNew( us2fs(destPathResult) );
    RINOK(callback->SetCurrentFilePath(fs2us(srcPath)));
    if (!FsMoveFile(srcPath, destPathNew, callback, completedSize))
    {
      RINOK(SendMessageError(callback, "can not move to file: ", destPathNew));
    }
  }
  completedSize += srcFileInfo.Size;
  RINOK(callback->SetCompleted(&completedSize));
  return S_OK;
}

static HRESULT FsMoveFolder(
    const FString &srcPath,
    const FString &destPath,
    IFolderOperationsExtractCallback *callback,
    UInt64 &completedSize)
{
  if (IsDestChild(srcPath, destPath))
  {
    RINOK(SendMessageError(callback, "can not move folder onto itself: ", destPath));
    return E_ABORT;
  }

  if (FsMoveFile(srcPath, destPath, callback, completedSize))
    return S_OK;

  if (!CreateComplexDir(destPath))
  {
    RINOK(SendMessageError(callback, "can not create folder: ", destPath));
    return E_ABORT;
  }
  {
    CEnumerator enumerator(CombinePath(srcPath, FSTRING_ANY_MASK));
    CDirItem fi;
    while (enumerator.Next(fi))
    {
      const FString srcPath2 = CombinePath(srcPath, fi.Name);
      const FString destPath2 = CombinePath(destPath, fi.Name);
      if (fi.IsDir())
      {
        RINOK(FsMoveFolder(srcPath2, destPath2, callback, completedSize));
      }
      else
      {
        RINOK(FsMoveFile(srcPath2, fi, destPath2, callback, completedSize));
      }
    }
  }
  if (!RemoveDir(srcPath))
  {
    RINOK(SendMessageError(callback, "can not remove folder: ", srcPath));
    return E_ABORT;
  }
  return S_OK;
}

STDMETHODIMP CFSFolder::CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems,
    Int32 /* includeAltStreams */, Int32 /* replaceAltStreamColon */,
    const wchar_t *path, IFolderOperationsExtractCallback *callback)
{
  if (numItems == 0)
    return S_OK;

  CFsFolderStat stat;
  stat.Progress = callback;
  RINOK(GetItemsFullSize(indices, numItems, stat));
  RINOK(callback->SetTotal(stat.Size));
  RINOK(callback->SetNumFiles(stat.NumFiles));

  FString destPath = us2fs(path);
  if (destPath.IsEmpty())
    return E_INVALIDARG;
  bool directName = (destPath.Back() != FCHAR_PATH_SEPARATOR);
  if (directName)
  {
    if (numItems > 1)
      return E_INVALIDARG;
  }
  else
  {
    // Does CreateComplexDir work in network ?
    if (!CreateComplexDir(destPath))
    {
      RINOK(SendMessageError(callback, "can not create folder: ", destPath));
      return E_ABORT;
    }
  }

  UInt64 completedSize = 0;
  RINOK(callback->SetCompleted(&completedSize));
  for (UInt32 i = 0; i < numItems; i++)
  {
    UInt32 index = indices[i];
    if (index >= (UInt32)Files.Size())
      continue;
    const CDirItem &fi = Files[index];
    FString destPath2 = destPath;
    if (!directName)
      destPath2 += fi.Name;
    FString srcPath;
    GetFullPath(fi, srcPath);
    if (fi.IsDir())
    {
      if (moveMode)
      {
        RINOK(FsMoveFolder(srcPath, destPath2, callback, completedSize));
      }
      else
      {
        RINOK(CopyFolder(srcPath, destPath2, callback, completedSize));
      }
    }
    else
    {
      if (moveMode)
      {
        RINOK(FsMoveFile(srcPath, fi, destPath2, callback, completedSize));
      }
      else
      {
        RINOK(FsCopyFile(srcPath, fi, destPath2, callback, completedSize));
      }
    }
  }
  return S_OK;
}

STDMETHODIMP CFSFolder::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */,
    const wchar_t ** /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */)
{
  /*
  UInt64 numFolders, numFiles, totalSize;
  numFiles = numFolders = totalSize = 0;
  UInt32 i;
  for (i = 0; i < numItems; i++)
  {
    UString path = (UString)fromFolderPath + itemsPaths[i];

    CFileInfo fi;
    if (!FindFile(path, fi))
      return ::GetLastError();
    if (fi.IsDir())
    {
      UInt64 subFolders, subFiles, subSize;
      RINOK(GetFolderSize(CombinePath(path, fi.Name), subFolders, subFiles, subSize, progress));
      numFolders += subFolders;
      numFolders++;
      numFiles += subFiles;
      totalSize += subSize;
    }
    else
    {
      numFiles++;
      totalSize += fi.Size;
    }
  }
  RINOK(progress->SetTotal(totalSize));
  RINOK(callback->SetNumFiles(numFiles));
  for (i = 0; i < numItems; i++)
  {
    UString path = (UString)fromFolderPath + itemsPaths[i];
  }
  return S_OK;
  */
  return E_NOTIMPL;
}

STDMETHODIMP CFSFolder::CopyFromFile(UInt32 /* index */, const wchar_t * /* fullFilePath */, IProgress * /* progress */)
{
  return E_NOTIMPL;
}

}
