// ExtractCallback.h

#ifndef __EXTRACT_CALLBACK_H
#define __EXTRACT_CALLBACK_H

#include "../../../../C/Alloc.h"

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

#ifndef _SFX
#include "../Agent/IFolderArchive.h"
#endif

#include "../Common/ArchiveExtractCallback.h"
#include "../Common/ArchiveOpenCallback.h"

#ifndef _NO_CRYPTO
#include "../../IPassword.h"
#endif

#ifndef _SFX
#include "IFolder.h"
#endif

#include "ProgressDialog2.h"

#ifdef LANG
#include "LangUtils.h"
#endif

#ifndef _SFX

class CGrowBuf
{
  Byte *_items;
  size_t _size;

  CGrowBuf(const CGrowBuf &buffer);
  void operator=(const CGrowBuf &buffer);

public:
  bool ReAlloc_KeepData(size_t newSize, size_t keepSize)
  {
    void *buf = MyAlloc(newSize);
    if (!buf)
      return false;
    memcpy(buf, _items, keepSize);
    MyFree(_items);
    _items = (Byte *)buf;
    _size = newSize;
    return true;
  }

  CGrowBuf(): _items(0), _size(0) {}
  ~CGrowBuf() { MyFree(_items); }

  operator Byte *() { return _items; };
  operator const Byte *() const { return _items; };
  size_t Size() const { return _size; }
};

struct CVirtFile
{
  CGrowBuf Data;
  
  UInt64 Size; // real size
  UInt64 ExpectedSize; // the size from props request. 0 if unknown

  UString Name;

  bool CTimeDefined;
  bool ATimeDefined;
  bool MTimeDefined;
  bool AttribDefined;
  
  bool IsDir;
  bool IsAltStream;
  
  DWORD Attrib;

  FILETIME CTime;
  FILETIME ATime;
  FILETIME MTime;

  CVirtFile():
    CTimeDefined(false),
    ATimeDefined(false),
    MTimeDefined(false),
    AttribDefined(false),
    IsDir(false),
    IsAltStream(false) {}
};

class CVirtFileSystem:
  public ISequentialOutStream,
  public CMyUnknownImp
{
  UInt64 _totalAllocSize;

  size_t _pos;
  unsigned _numFlushed;
  bool _fileIsOpen;
  bool _fileMode;
  COutFileStream *_outFileStreamSpec;
  CMyComPtr<ISequentialOutStream> _outFileStream;
public:
  CObjectVector<CVirtFile> Files;
  UInt64 MaxTotalAllocSize;
  FString DirPrefix;
 
  CVirtFile &AddNewFile()
  {
    if (!Files.IsEmpty())
    {
      MaxTotalAllocSize -= Files.Back().Data.Size();
    }
    return Files.AddNew();
  }
  HRESULT CloseMemFile()
  {
    if (_fileMode)
    {
      return FlushToDisk(true);
    }
    CVirtFile &file = Files.Back();
    if (file.Data.Size() != file.Size)
    {
      file.Data.ReAlloc_KeepData((size_t)file.Size, (size_t)file.Size);
    }
    return S_OK;
  }

  bool IsStreamInMem() const
  {
    if (_fileMode)
      return false;
    if (Files.Size() < 1 || Files[0].IsAltStream || Files[0].IsDir)
      return false;
    return true;
  }
  size_t GetMemStreamWrittenSize() const { return _pos; }

  CVirtFileSystem(): _outFileStreamSpec(NULL), MaxTotalAllocSize((UInt64)0 - 1) {}

  void Init()
  {
    _totalAllocSize = 0;
    _fileMode = false;
    _pos = 0;
    _numFlushed = 0;
    _fileIsOpen = false;
  }

  HRESULT CloseFile(const FString &path);
  HRESULT FlushToDisk(bool closeLast);
  size_t GetPos() const { return _pos; }

  MY_UNKNOWN_IMP
  STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
};

#endif
  
class CExtractCallbackImp:
  public IExtractCallbackUI, // it includes IFolderArchiveExtractCallback
  public IOpenCallbackUI,
  #ifndef _SFX
  public IFolderOperationsExtractCallback,
  public IFolderExtractToStreamCallback,
  public ICompressProgressInfo,
  #endif
  #ifndef _NO_CRYPTO
  public ICryptoGetTextPassword,
  #endif
  public CMyUnknownImp
{
  HRESULT MessageError(const char *message, const FString &path);
public:
  MY_QUERYINTERFACE_BEGIN2(IFolderArchiveExtractCallback)
  #ifndef _SFX
  MY_QUERYINTERFACE_ENTRY(IFolderOperationsExtractCallback)
  MY_QUERYINTERFACE_ENTRY(IFolderExtractToStreamCallback)
  MY_QUERYINTERFACE_ENTRY(ICompressProgressInfo)
  #endif
  #ifndef _NO_CRYPTO
  MY_QUERYINTERFACE_ENTRY(ICryptoGetTextPassword)
  #endif
  MY_QUERYINTERFACE_END
  MY_ADDREF_RELEASE

  INTERFACE_IProgress(;)
  INTERFACE_IOpenCallbackUI(;)

  // IFolderArchiveExtractCallback
  // STDMETHOD(SetTotalFiles)(UInt64 total);
  // STDMETHOD(SetCompletedFiles)(const UInt64 *value);
  STDMETHOD(AskOverwrite)(
      const wchar_t *existName, const FILETIME *existTime, const UInt64 *existSize,
      const wchar_t *newName, const FILETIME *newTime, const UInt64 *newSize,
      Int32 *answer);
  STDMETHOD (PrepareOperation)(const wchar_t *name, bool isFolder, Int32 askExtractMode, const UInt64 *position);

  STDMETHOD(MessageError)(const wchar_t *message);
  STDMETHOD(SetOperationResult)(Int32 operationResult, bool encrypted);

  // IExtractCallbackUI
  
  HRESULT BeforeOpen(const wchar_t *name);
  HRESULT OpenResult(const wchar_t *name, HRESULT result, bool encrypted);
  HRESULT SetError(int level, const wchar_t *name,
      UInt32 errorFlags, const wchar_t *errors,
      UInt32 warningFlags, const wchar_t *warnings);
  HRESULT ThereAreNoFiles();
  HRESULT ExtractResult(HRESULT result);
  HRESULT OpenTypeWarning(const wchar_t *name, const wchar_t *okType, const wchar_t *errorType);

  #ifndef _NO_CRYPTO
  HRESULT SetPassword(const UString &password);
  #endif

  #ifndef _SFX
  // IFolderOperationsExtractCallback
  STDMETHOD(AskWrite)(
      const wchar_t *srcPath,
      Int32 srcIsFolder,
      const FILETIME *srcTime,
      const UInt64 *srcSize,
      const wchar_t *destPathRequest,
      BSTR *destPathResult,
      Int32 *writeAnswer);
  STDMETHOD(ShowMessage)(const wchar_t *message);
  STDMETHOD(SetCurrentFilePath)(const wchar_t *filePath);
  STDMETHOD(SetNumFiles)(UInt64 numFiles);
  INTERFACE_IFolderExtractToStreamCallback(;)
  STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize);
  #endif

  // ICryptoGetTextPassword
  #ifndef _NO_CRYPTO
  STDMETHOD(CryptoGetTextPassword)(BSTR *password);
  #endif

private:
  UString _currentArchivePath;
  bool _needWriteArchivePath;

  UString _currentFilePath;
  bool _isFolder;

  bool _isAltStream;
  UInt64 _curSize;
  bool _curSizeDefined;
  UString _filePath;
  // bool _extractMode;
  // bool _testMode;
  bool _newVirtFileWasAdded;
  bool _needUpdateStat;


  HRESULT SetCurrentFilePath2(const wchar_t *filePath);
  void AddError_Message(LPCWSTR message);

  #ifndef _SFX
  bool _hashStreamWasUsed;
  COutStreamWithHash *_hashStreamSpec;
  CMyComPtr<ISequentialOutStream> _hashStream;
  IHashCalc *_hashCalc; // it's for stat in Test operation
  #endif

public:

  #ifndef _SFX
  CVirtFileSystem *VirtFileSystemSpec;
  CMyComPtr<ISequentialOutStream> VirtFileSystem;
  #endif

  bool ProcessAltStreams;

  bool StreamMode;

  CProgressDialog *ProgressDialog;
  #ifndef _SFX
  UInt64 NumFolders;
  UInt64 NumFiles;
  bool NeedAddFile;
  #endif
  UInt32 NumArchiveErrors;
  bool ThereAreMessageErrors;
  NExtract::NOverwriteMode::EEnum OverwriteMode;

  #ifndef _NO_CRYPTO
  bool PasswordIsDefined;
  bool PasswordWasAsked;
  UString Password;
  #endif

  CExtractCallbackImp():
    #ifndef _NO_CRYPTO
    PasswordIsDefined(false),
    PasswordWasAsked(false),
    #endif
    OverwriteMode(NExtract::NOverwriteMode::kAsk),
    StreamMode(false),
    ProcessAltStreams(true)
    #ifndef _SFX
    , _hashCalc(NULL)
    #endif
    {}
   
  ~CExtractCallbackImp();
  void Init();

  #ifndef _SFX
  void SetHashCalc(IHashCalc *hashCalc) { _hashCalc = hashCalc; }

  void SetHashMethods(IHashCalc *hash)
  {
    if (!hash)
      return;
    _hashStreamSpec = new COutStreamWithHash;
    _hashStream = _hashStreamSpec;
    _hashStreamSpec->_hash = hash;
  }
  #endif

  bool IsOK() const { return NumArchiveErrors == 0 && !ThereAreMessageErrors; }
};

#endif