// 7zOut.h

#ifndef __7Z_OUT_H
#define __7Z_OUT_H

#include "7zCompressionMode.h"
#include "7zEncode.h"
#include "7zHeader.h"
#include "7zItem.h"

#include "../../Common/OutBuffer.h"
#include "../../Common/StreamUtils.h"

namespace NArchive {
namespace N7z {

class CWriteBufferLoc
{
  Byte *_data;
  size_t _size;
  size_t _pos;
public:
  CWriteBufferLoc(): _size(0), _pos(0) {}
  void Init(Byte *data, size_t size)
  {
    _data = data;
    _size = size;
    _pos = 0;
  }
  void WriteBytes(const void *data, size_t size)
  {
    if (size == 0)
      return;
    if (size > _size - _pos)
      throw 1;
    memcpy(_data + _pos, data, size);
    _pos += size;
  }
  void WriteByte(Byte b)
  {
    if (_size == _pos)
      throw 1;
    _data[_pos++] = b;
  }
  size_t GetPos() const { return _pos; }
};

struct CHeaderOptions
{
  bool CompressMainHeader;
  /*
  bool WriteCTime;
  bool WriteATime;
  bool WriteMTime;
  */

  CHeaderOptions():
      CompressMainHeader(true)
      /*
      , WriteCTime(false)
      , WriteATime(false)
      , WriteMTime(true)
      */
      {}
};


struct CFileItem2
{
  UInt64 CTime;
  UInt64 ATime;
  UInt64 MTime;
  UInt64 StartPos;
  bool CTimeDefined;
  bool ATimeDefined;
  bool MTimeDefined;
  bool StartPosDefined;
  bool IsAnti;
  // bool IsAux;

  void Init()
  {
    CTimeDefined = false;
    ATimeDefined = false;
    MTimeDefined = false;
    StartPosDefined = false;
    IsAnti = false;
    // IsAux = false;
  }
};

struct COutFolders
{
  CUInt32DefVector FolderUnpackCRCs; // Now we use it for headers only.

  CRecordVector<CNum> NumUnpackStreamsVector;
  CRecordVector<UInt64> CoderUnpackSizes; // including unpack sizes of bond coders

  void OutFoldersClear()
  {
    FolderUnpackCRCs.Clear();
    NumUnpackStreamsVector.Clear();
    CoderUnpackSizes.Clear();
  }

  void OutFoldersReserveDown()
  {
    FolderUnpackCRCs.ReserveDown();
    NumUnpackStreamsVector.ReserveDown();
    CoderUnpackSizes.ReserveDown();
  }
};

struct CArchiveDatabaseOut: public COutFolders
{
  CRecordVector<UInt64> PackSizes;
  CUInt32DefVector PackCRCs;
  CObjectVector<CFolder> Folders;

  CRecordVector<CFileItem> Files;
  UStringVector Names;
  CUInt64DefVector CTime;
  CUInt64DefVector ATime;
  CUInt64DefVector MTime;
  CUInt64DefVector StartPos;
  CRecordVector<bool> IsAnti;

  /*
  CRecordVector<bool> IsAux;

  CByteBuffer SecureBuf;
  CRecordVector<UInt32> SecureSizes;
  CRecordVector<UInt32> SecureIDs;

  void ClearSecure()
  {
    SecureBuf.Free();
    SecureSizes.Clear();
    SecureIDs.Clear();
  }
  */

  void Clear()
  {
    OutFoldersClear();

    PackSizes.Clear();
    PackCRCs.Clear();
    Folders.Clear();
  
    Files.Clear();
    Names.Clear();
    CTime.Clear();
    ATime.Clear();
    MTime.Clear();
    StartPos.Clear();
    IsAnti.Clear();

    /*
    IsAux.Clear();
    ClearSecure();
    */
  }

  void ReserveDown()
  {
    OutFoldersReserveDown();

    PackSizes.ReserveDown();
    PackCRCs.ReserveDown();
    Folders.ReserveDown();
    
    Files.ReserveDown();
    Names.ReserveDown();
    CTime.ReserveDown();
    ATime.ReserveDown();
    MTime.ReserveDown();
    StartPos.ReserveDown();
    IsAnti.ReserveDown();

    /*
    IsAux.ReserveDown();
    */
  }

  bool IsEmpty() const
  {
    return (
      PackSizes.IsEmpty() &&
      NumUnpackStreamsVector.IsEmpty() &&
      Folders.IsEmpty() &&
      Files.IsEmpty());
  }

  bool CheckNumFiles() const
  {
    unsigned size = Files.Size();
    return (
      CTime.CheckSize(size) &&
      ATime.CheckSize(size) &&
      MTime.CheckSize(size) &&
      StartPos.CheckSize(size) &&
      (size == IsAnti.Size() || IsAnti.Size() == 0));
  }

  bool IsItemAnti(unsigned index) const { return (index < IsAnti.Size() && IsAnti[index]); }
  // bool IsItemAux(unsigned index) const { return (index < IsAux.Size() && IsAux[index]); }

  void SetItem_Anti(unsigned index, bool isAnti)
  {
    while (index >= IsAnti.Size())
      IsAnti.Add(false);
    IsAnti[index] = isAnti;
  }
  /*
  void SetItem_Aux(unsigned index, bool isAux)
  {
    while (index >= IsAux.Size())
      IsAux.Add(false);
    IsAux[index] = isAux;
  }
  */

  void AddFile(const CFileItem &file, const CFileItem2 &file2, const UString &name);
};

class COutArchive
{
  UInt64 _prefixHeaderPos;

  HRESULT WriteDirect(const void *data, UInt32 size) { return WriteStream(SeqStream, data, size); }
  
  UInt64 GetPos() const;
  void WriteBytes(const void *data, size_t size);
  void WriteBytes(const CByteBuffer &data) { WriteBytes(data, data.Size()); }
  void WriteByte(Byte b);
  void WriteUInt32(UInt32 value);
  void WriteUInt64(UInt64 value);
  void WriteNumber(UInt64 value);
  void WriteID(UInt64 value) { WriteNumber(value); }

  void WriteFolder(const CFolder &folder);
  HRESULT WriteFileHeader(const CFileItem &itemInfo);
  void WriteBoolVector(const CBoolVector &boolVector);
  void WritePropBoolVector(Byte id, const CBoolVector &boolVector);

  void WriteHashDigests(const CUInt32DefVector &digests);

  void WritePackInfo(
      UInt64 dataOffset,
      const CRecordVector<UInt64> &packSizes,
      const CUInt32DefVector &packCRCs);

  void WriteUnpackInfo(
      const CObjectVector<CFolder> &folders,
      const COutFolders &outFolders);

  void WriteSubStreamsInfo(
      const CObjectVector<CFolder> &folders,
      const COutFolders &outFolders,
      const CRecordVector<UInt64> &unpackSizes,
      const CUInt32DefVector &digests);

  void SkipAlign(unsigned pos, unsigned alignSize);
  void WriteAlignedBoolHeader(const CBoolVector &v, unsigned numDefined, Byte type, unsigned itemSize);
  void WriteUInt64DefVector(const CUInt64DefVector &v, Byte type);

  HRESULT EncodeStream(
      DECL_EXTERNAL_CODECS_LOC_VARS
      CEncoder &encoder, const CByteBuffer &data,
      CRecordVector<UInt64> &packSizes, CObjectVector<CFolder> &folders, COutFolders &outFolders);
  void WriteHeader(
      const CArchiveDatabaseOut &db,
      // const CHeaderOptions &headerOptions,
      UInt64 &headerOffset);
  
  bool _countMode;
  bool _writeToStream;
  size_t _countSize;
  UInt32 _crc;
  COutBuffer _outByte;
  CWriteBufferLoc _outByte2;

  #ifdef _7Z_VOL
  bool _endMarker;
  #endif

  bool _useAlign;

  HRESULT WriteSignature();
  #ifdef _7Z_VOL
  HRESULT WriteFinishSignature();
  #endif
  HRESULT WriteStartHeader(const CStartHeader &h);
  #ifdef _7Z_VOL
  HRESULT WriteFinishHeader(const CFinishHeader &h);
  #endif
  CMyComPtr<IOutStream> Stream;
public:

  COutArchive() { _outByte.Create(1 << 16); }
  CMyComPtr<ISequentialOutStream> SeqStream;
  HRESULT Create(ISequentialOutStream *stream, bool endMarker);
  void Close();
  HRESULT SkipPrefixArchiveHeader();
  HRESULT WriteDatabase(
      DECL_EXTERNAL_CODECS_LOC_VARS
      const CArchiveDatabaseOut &db,
      const CCompressionMethodMode *options,
      const CHeaderOptions &headerOptions);

  #ifdef _7Z_VOL
  static UInt32 GetVolHeadersSize(UInt64 dataSize, int nameLength = 0, bool props = false);
  static UInt64 GetVolPureSize(UInt64 volSize, int nameLength = 0, bool props = false);
  #endif

};

}}

#endif
