// PanelSplitFile.cpp

#include "StdAfx.h"

#include "../../../Common/IntToString.h"

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

#include "../GUI/ExtractRes.h"

#include "resource.h"

#include "App.h"
#include "CopyDialog.h"
#include "FormatUtils.h"
#include "LangUtils.h"
#include "SplitDialog.h"
#include "SplitUtils.h"

#include "PropertyNameRes.h"

using namespace NWindows;
using namespace NFile;
using namespace NDir;

static const wchar_t *g_Message_FileWriteError = L"File write error";

struct CVolSeqName
{
  UString UnchangedPart;
  UString ChangedPart;
  CVolSeqName(): ChangedPart(L"000") {};

  void SetNumDigits(UInt64 numVolumes)
  {
    ChangedPart = L"000";
    while (numVolumes > 999)
    {
      numVolumes /= 10;
      ChangedPart += L'0';
    }
  }

  bool ParseName(const UString &name)
  {
    if (name.Len() < 2)
      return false;
    if (name.Back() != L'1' || name[name.Len() - 2] != L'0')
      return false;

    unsigned pos = name.Len() - 2;
    for (; pos > 0 && name[pos - 1] == '0'; pos--);
    UnchangedPart.SetFrom(name, pos);
    ChangedPart = name.Ptr(pos);
    return true;
  }

  UString GetNextName();
};


UString CVolSeqName::GetNextName()
{
  for (int i = (int)ChangedPart.Len() - 1; i >= 0; i--)
  {
    wchar_t c = ChangedPart[i];
    if (c != L'9')
    {
      ChangedPart.ReplaceOneCharAtPos(i, (wchar_t)(c + 1));
      break;
    }
    ChangedPart.ReplaceOneCharAtPos(i, L'0');
    if (i == 0)
      ChangedPart.InsertAtFront(L'1');
  }
  return UnchangedPart + ChangedPart;
}

static const UInt32 kBufSize = (1 << 20);

class CThreadSplit: public CProgressThreadVirt
{
  HRESULT ProcessVirt();
public:
  FString FilePath;
  FString VolBasePath;
  UInt64 NumVolumes;
  CRecordVector<UInt64> VolumeSizes;
};

HRESULT CThreadSplit::ProcessVirt()
{
  NIO::CInFile inFile;
  if (!inFile.Open(FilePath))
    return GetLastError();
  NIO::COutFile outFile;
  CMyBuffer bufferObject;
  if (!bufferObject.Allocate(kBufSize))
    return E_OUTOFMEMORY;
  Byte *buffer = (Byte *)(void *)bufferObject;
  UInt64 curVolSize = 0;
  CVolSeqName seqName;
  seqName.SetNumDigits(NumVolumes);
  UInt64 length;
  if (!inFile.GetLength(length))
    return GetLastError();
  
  CProgressSync &sync = ProgressDialog.Sync;
  sync.Set_NumBytesTotal(length);
  UInt64 pos = 0;
  
  UInt64 numFiles = 0;
  unsigned volIndex = 0;
  
  for (;;)
  {
    UInt64 volSize;
    if (volIndex < VolumeSizes.Size())
      volSize = VolumeSizes[volIndex];
    else
      volSize = VolumeSizes.Back();
    
    UInt32 needSize = (UInt32)(MyMin((UInt64)kBufSize, volSize - curVolSize));
    UInt32 processedSize;
    if (!inFile.Read(buffer, needSize, processedSize))
      return GetLastError();
    if (processedSize == 0)
      break;
    needSize = processedSize;
    if (curVolSize == 0)
    {
      FString name = VolBasePath;
      name += FTEXT('.');
      name += us2fs(seqName.GetNextName());
      sync.Set_FilePath(fs2us(name));
      sync.Set_NumFilesCur(numFiles++);
      if (!outFile.Create(name, false))
      {
        HRESULT res = GetLastError();
        SetErrorPath1(name);
        return res;
      }
    }
    if (!outFile.Write(buffer, needSize, processedSize))
      return GetLastError();
    if (needSize != processedSize)
      throw g_Message_FileWriteError;
    curVolSize += processedSize;
    if (curVolSize == volSize)
    {
      outFile.Close();
      if (volIndex < VolumeSizes.Size())
        volIndex++;
      curVolSize = 0;
    }
    pos += processedSize;
    RINOK(sync.Set_NumBytesCur(pos));
  }
  sync.Set_NumFilesCur(numFiles);
  return S_OK;
}

void CApp::Split()
{
  int srcPanelIndex = GetFocusedPanelIndex();
  CPanel &srcPanel = Panels[srcPanelIndex];
  if (!srcPanel.IsFSFolder())
  {
    srcPanel.MessageBoxErrorLang(IDS_OPERATION_IS_NOT_SUPPORTED);
    return;
  }
  CRecordVector<UInt32> indices;
  srcPanel.GetOperatedItemIndices(indices);
  if (indices.IsEmpty())
    return;
  if (indices.Size() != 1)
  {
    srcPanel.MessageBoxErrorLang(IDS_SELECT_ONE_FILE);
    return;
  }
  int index = indices[0];
  if (srcPanel.IsItem_Folder(index))
  {
    srcPanel.MessageBoxErrorLang(IDS_SELECT_ONE_FILE);
    return;
  }
  const UString itemName = srcPanel.GetItemName(index);

  UString srcPath = srcPanel._currentFolderPrefix + srcPanel.GetItemPrefix(index);
  UString path = srcPath;
  int destPanelIndex = (NumPanels <= 1) ? srcPanelIndex : (1 - srcPanelIndex);
  CPanel &destPanel = Panels[destPanelIndex];
  if (NumPanels > 1)
    if (destPanel.IsFSFolder())
      path = destPanel._currentFolderPrefix;
  CSplitDialog splitDialog;
  splitDialog.FilePath = srcPanel.GetItemRelPath(index);
  splitDialog.Path = path;
  if (splitDialog.Create(srcPanel.GetParent()) != IDOK)
    return;

  NFind::CFileInfo fileInfo;
  if (!fileInfo.Find(us2fs(srcPath + itemName)))
  {
    srcPanel.MessageBoxMyError(L"Can not find file");
    return;
  }
  if (fileInfo.Size <= splitDialog.VolumeSizes.Front())
  {
    srcPanel.MessageBoxErrorLang(IDS_SPLIT_VOL_MUST_BE_SMALLER);
    return;
  }
  const UInt64 numVolumes = GetNumberOfVolumes(fileInfo.Size, splitDialog.VolumeSizes);
  if (numVolumes >= 100)
  {
    wchar_t s[32];
    ConvertUInt64ToString(numVolumes, s);
    if (::MessageBoxW(srcPanel, MyFormatNew(IDS_SPLIT_CONFIRM_MESSAGE, s),
        LangString(IDS_SPLIT_CONFIRM_TITLE),
        MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES)
      return;
  }

  path = splitDialog.Path;
  NName::NormalizeDirPathPrefix(path);
  if (!CreateComplexDir(us2fs(path)))
  {
    srcPanel.MessageBoxError2Lines(MyFormatNew(IDS_CANNOT_CREATE_FOLDER, path), ::GetLastError());
    return;
  }

  {
  CThreadSplit spliter;
  spliter.NumVolumes = numVolumes;

  CProgressDialog &progressDialog = spliter.ProgressDialog;

  UString progressWindowTitle = L"7-Zip"; // LangString(IDS_APP_TITLE, 0x03000000);
  UString title = LangString(IDS_SPLITTING);

  progressDialog.ShowCompressionInfo = false;

  progressDialog.MainWindow = _window;
  progressDialog.MainTitle = progressWindowTitle;
  progressDialog.MainAddTitle = title + L' ';
  progressDialog.Sync.Set_TitleFileName(itemName);


  spliter.FilePath = us2fs(srcPath + itemName);
  spliter.VolBasePath = us2fs(path + itemName);
  spliter.VolumeSizes = splitDialog.VolumeSizes;
  
  // if (splitDialog.VolumeSizes.Size() == 0) return;

  // CPanel::CDisableTimerProcessing disableTimerProcessing1(srcPanel);
  // CPanel::CDisableTimerProcessing disableTimerProcessing2(destPanel);

  if (spliter.Create(title, _window) != 0)
    return;
  }
  RefreshTitleAlways();


  // disableNotify.Restore();
  // disableNotify.Restore();
  // srcPanel.SetFocusToList();
  // srcPanel.RefreshListCtrlSaveFocused();
}


class CThreadCombine: public CProgressThreadVirt
{
  HRESULT ProcessVirt();
public:
  FString InputDirPrefix;
  FStringVector Names;
  FString OutputPath;
  UInt64 TotalSize;
};

HRESULT CThreadCombine::ProcessVirt()
{
  NIO::COutFile outFile;
  if (!outFile.Create(OutputPath, false))
  {
    HRESULT res = GetLastError();
    SetErrorPath1(OutputPath);
    return res;
  }
  
  CProgressSync &sync = ProgressDialog.Sync;
  sync.Set_NumBytesTotal(TotalSize);
  
  CMyBuffer bufferObject;
  if (!bufferObject.Allocate(kBufSize))
    return E_OUTOFMEMORY;
  Byte *buffer = (Byte *)(void *)bufferObject;
  UInt64 pos = 0;
  FOR_VECTOR (i, Names)
  {
    NIO::CInFile inFile;
    const FString nextName = InputDirPrefix + Names[i];
    if (!inFile.Open(nextName))
    {
      HRESULT res = GetLastError();
      SetErrorPath1(nextName);
      return res;
    }
    sync.Set_FilePath(fs2us(nextName));
    for (;;)
    {
      UInt32 processedSize;
      if (!inFile.Read(buffer, kBufSize, processedSize))
      {
        HRESULT res = GetLastError();
        SetErrorPath1(nextName);
        return res;
      }
      if (processedSize == 0)
        break;
      UInt32 needSize = processedSize;
      if (!outFile.Write(buffer, needSize, processedSize))
      {
        HRESULT res = GetLastError();
        SetErrorPath1(OutputPath);
        return res;
      }
      if (needSize != processedSize)
        throw g_Message_FileWriteError;
      pos += processedSize;
      RINOK(sync.Set_NumBytesCur(pos));
    }
  }
  return S_OK;
}

extern void AddValuePair2(UString &s, UINT resourceID, UInt64 num, UInt64 size);

static void AddInfoFileName(UString &dest, const UString &name)
{
  dest += L"\n  ";
  dest += name;
}

void CApp::Combine()
{
  int srcPanelIndex = GetFocusedPanelIndex();
  CPanel &srcPanel = Panels[srcPanelIndex];
  if (!srcPanel.IsFSFolder())
  {
    srcPanel.MessageBoxErrorLang(IDS_OPERATION_IS_NOT_SUPPORTED);
    return;
  }
  CRecordVector<UInt32> indices;
  srcPanel.GetOperatedItemIndices(indices);
  if (indices.IsEmpty())
    return;
  int index = indices[0];
  if (indices.Size() != 1 || srcPanel.IsItem_Folder(index))
  {
    srcPanel.MessageBoxErrorLang(IDS_COMBINE_SELECT_ONE_FILE);
    return;
  }
  const UString itemName = srcPanel.GetItemName(index);

  UString srcPath = srcPanel._currentFolderPrefix + srcPanel.GetItemPrefix(index);
  UString path = srcPath;
  int destPanelIndex = (NumPanels <= 1) ? srcPanelIndex : (1 - srcPanelIndex);
  CPanel &destPanel = Panels[destPanelIndex];
  if (NumPanels > 1)
    if (destPanel.IsFSFolder())
      path = destPanel._currentFolderPrefix;

  CVolSeqName volSeqName;
  if (!volSeqName.ParseName(itemName))
  {
    srcPanel.MessageBoxErrorLang(IDS_COMBINE_CANT_DETECT_SPLIT_FILE);
    return;
  }
  
  {
  CThreadCombine combiner;
  
  UString nextName = itemName;
  combiner.TotalSize = 0;
  for (;;)
  {
    NFind::CFileInfo fileInfo;
    if (!fileInfo.Find(us2fs(srcPath + nextName)) || fileInfo.IsDir())
      break;
    combiner.Names.Add(us2fs(nextName));
    combiner.TotalSize += fileInfo.Size;
    nextName = volSeqName.GetNextName();
  }
  if (combiner.Names.Size() == 1)
  {
    srcPanel.MessageBoxErrorLang(IDS_COMBINE_CANT_FIND_MORE_THAN_ONE_PART);
    return;
  }
  
  if (combiner.TotalSize == 0)
  {
    srcPanel.MessageBoxMyError(L"No data");
    return;
  }
  
  UString info;
  AddValuePair2(info, IDS_PROP_FILES, combiner.Names.Size(), combiner.TotalSize);
  
  info += L"\n";
  info += srcPath;
  
  unsigned i;
  for (i = 0; i < combiner.Names.Size() && i < 2; i++)
    AddInfoFileName(info, fs2us(combiner.Names[i]));
  if (i != combiner.Names.Size())
  {
    if (i + 1 != combiner.Names.Size())
      AddInfoFileName(info, L"...");
    AddInfoFileName(info, fs2us(combiner.Names.Back()));
  }
  
  {
    CCopyDialog copyDialog;
    copyDialog.Value = path;
    LangString(IDS_COMBINE, copyDialog.Title);
    copyDialog.Title += ' ';
    copyDialog.Title += srcPanel.GetItemRelPath(index);
    LangString(IDS_COMBINE_TO, copyDialog.Static);
    copyDialog.Info = info;
    if (copyDialog.Create(srcPanel.GetParent()) != IDOK)
      return;
    path = copyDialog.Value;
  }

  NName::NormalizeDirPathPrefix(path);
  if (!CreateComplexDir(us2fs(path)))
  {
    srcPanel.MessageBoxError2Lines(MyFormatNew(IDS_CANNOT_CREATE_FOLDER, path), ::GetLastError());
    return;
  }
  
  UString outName = volSeqName.UnchangedPart;
  while (!outName.IsEmpty())
  {
    if (outName.Back() != L'.')
      break;
    outName.DeleteBack();
  }
  if (outName.IsEmpty())
    outName = L"file";
  
  NFind::CFileInfo fileInfo;
  UString destFilePath = path + outName;
  combiner.OutputPath = us2fs(destFilePath);
  if (fileInfo.Find(combiner.OutputPath))
  {
    srcPanel.MessageBoxMyError(MyFormatNew(IDS_FILE_EXIST, destFilePath));
    return;
  }
  
    CProgressDialog &progressDialog = combiner.ProgressDialog;
    progressDialog.ShowCompressionInfo = false;
  
    UString progressWindowTitle = L"7-Zip"; // LangString(IDS_APP_TITLE, 0x03000000);
    UString title = LangString(IDS_COMBINING);
    
    progressDialog.MainWindow = _window;
    progressDialog.MainTitle = progressWindowTitle;
    progressDialog.MainAddTitle = title + L' ';
    
    combiner.InputDirPrefix = us2fs(srcPath);
    
    // CPanel::CDisableTimerProcessing disableTimerProcessing1(srcPanel);
    // CPanel::CDisableTimerProcessing disableTimerProcessing2(destPanel);
    
    if (combiner.Create(title, _window) != 0)
      return;
  }
  RefreshTitleAlways();

  // disableNotify.Restore();
  // disableNotify.Restore();
  // srcPanel.SetFocusToList();
  // srcPanel.RefreshListCtrlSaveFocused();
}
