// LZMA/Encoder.cpp

#include "StdAfx.h"

#include <stdio.h>

#ifdef _WIN32
#define USE_ALLOCA
#endif

#ifdef USE_ALLOCA
#ifdef _WIN32
#include <malloc.h>
#else
#include <stdlib.h>
#endif
#endif

#include "LZMAEncoder.h"

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

extern "C"
{
#include "../../../../C/Alloc.h"
}

static HRESULT SResToHRESULT(SRes res)
{
  switch(res)
  {
    case SZ_OK: return S_OK;
    case SZ_ERROR_MEM: return E_OUTOFMEMORY;
    case SZ_ERROR_PARAM: return E_INVALIDARG;
    // case SZ_ERROR_THREAD: return E_FAIL;
  }
  return E_FAIL;
}

namespace NCompress {
namespace NLZMA {

static const UInt32 kStreamStepSize = (UInt32)1 << 31;

static SRes MyRead(void *object, void *data, size_t *size)
{
  UInt32 curSize = ((*size < kStreamStepSize) ? (UInt32)*size : kStreamStepSize);
  HRESULT res = ((CSeqInStream *)object)->RealStream->Read(data, curSize, &curSize);
  *size = curSize;
  return (SRes)res;
}

static size_t MyWrite(void *object, const void *data, size_t size)
{
  CSeqOutStream *p = (CSeqOutStream *)object;
  p->Res = WriteStream(p->RealStream, data, size);
  if (p->Res != 0)
    return 0;
  return size;
}

static void *SzBigAlloc(void *, size_t size) { return BigAlloc(size); }
static void SzBigFree(void *, void *address) { BigFree(address); }
static ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree };

static void *SzAlloc(void *, size_t size) { return MyAlloc(size); }
static void SzFree(void *, void *address) { MyFree(address); }
static ISzAlloc g_Alloc = { SzAlloc, SzFree };

CEncoder::CEncoder()
{
  _seqInStream.SeqInStream.Read = MyRead;
  _seqOutStream.SeqOutStream.Write = MyWrite;
  _encoder = 0;
  _encoder = LzmaEnc_Create(&g_Alloc);
  if (_encoder == 0)
    throw 1;
}

CEncoder::~CEncoder()
{
  if (_encoder != 0)
    LzmaEnc_Destroy(_encoder, &g_Alloc, &g_BigAlloc);
}

inline wchar_t GetUpperChar(wchar_t c)
{
  if (c >= 'a' && c <= 'z')
    c -= 0x20;
  return c;
}

static int ParseMatchFinder(const wchar_t *s, int *btMode, int *numHashBytes)
{
  wchar_t c = GetUpperChar(*s++);
  if (c == L'H')
  {
    if (GetUpperChar(*s++) != L'C')
      return 0;
    int numHashBytesLoc = (int)(*s++ - L'0');
    if (numHashBytesLoc < 4 || numHashBytesLoc > 4)
      return 0;
    if (*s++ != 0)
      return 0;
    *btMode = 0;
    *numHashBytes = numHashBytesLoc;
    return 1;
  }
  if (c != L'B')
    return 0;

  if (GetUpperChar(*s++) != L'T')
    return 0;
  int numHashBytesLoc = (int)(*s++ - L'0');
  if (numHashBytesLoc < 2 || numHashBytesLoc > 4)
    return 0;
  c = GetUpperChar(*s++);
  if (c != L'\0')
    return 0;
  *btMode = 1;
  *numHashBytes = numHashBytesLoc;
  return 1;
}

STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs,
    const PROPVARIANT *properties, UInt32 numProperties)
{
  CLzmaEncProps props;
  LzmaEncProps_Init(&props);

  for (UInt32 i = 0; i < numProperties; i++)
  {
    const PROPVARIANT &prop = properties[i];
    switch (propIDs[i])
    {
      case NCoderPropID::kNumFastBytes:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.fb = prop.ulVal; break;
      case NCoderPropID::kMatchFinderCycles:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.mc = prop.ulVal; break;
      case NCoderPropID::kAlgorithm:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.algo = prop.ulVal; break;
      case NCoderPropID::kDictionarySize:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.dictSize = prop.ulVal; break;
      case NCoderPropID::kPosStateBits:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.pb = prop.ulVal; break;
      case NCoderPropID::kLitPosBits:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.lp = prop.ulVal; break;
      case NCoderPropID::kLitContextBits:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.lc = prop.ulVal; break;
      case NCoderPropID::kNumThreads:
        if (prop.vt != VT_UI4) return E_INVALIDARG; props.numThreads = prop.ulVal; break;
      case NCoderPropID::kMultiThread:
        if (prop.vt != VT_BOOL) return E_INVALIDARG; props.numThreads = ((prop.boolVal == VARIANT_TRUE) ? 2 : 1); break;
      case NCoderPropID::kEndMarker:
        if (prop.vt != VT_BOOL) return E_INVALIDARG; props.writeEndMark = (prop.boolVal == VARIANT_TRUE); break;
      case NCoderPropID::kMatchFinder:
        if (prop.vt != VT_BSTR) return E_INVALIDARG;
        if (!ParseMatchFinder(prop.bstrVal, &props.btMode, &props.numHashBytes /* , &_matchFinderBase.skipModeBits */))
          return E_INVALIDARG; break;
      default:
        return E_INVALIDARG;
    }
  }
  return SResToHRESULT(LzmaEnc_SetProps(_encoder, &props));
}

STDMETHODIMP CEncoder::WriteCoderProperties(ISequentialOutStream *outStream)
{
  Byte props[LZMA_PROPS_SIZE];
  size_t size = LZMA_PROPS_SIZE;
  RINOK(LzmaEnc_WriteProperties(_encoder, props, &size));
  return WriteStream(outStream, props, size);
}

STDMETHODIMP CEncoder::SetOutStream(ISequentialOutStream *outStream)
{
  _seqOutStream.RealStream = outStream;
  _seqOutStream.Res = S_OK;
  return S_OK;
}

STDMETHODIMP CEncoder::ReleaseOutStream()
{
  _seqOutStream.RealStream.Release();
  return S_OK;
}

typedef struct _CCompressProgressImp
{
  ICompressProgress p;
  ICompressProgressInfo *Progress;
  HRESULT Res;
} CCompressProgressImp;

#define PROGRESS_UNKNOWN_VALUE ((UInt64)(Int64)-1)

#define CONVERT_PR_VAL(x) (x == PROGRESS_UNKNOWN_VALUE ? NULL : &x)

SRes CompressProgress(void *pp, UInt64 inSize, UInt64 outSize)
{
  CCompressProgressImp *p = (CCompressProgressImp *)pp;
  p->Res = p->Progress->SetRatioInfo(CONVERT_PR_VAL(inSize), CONVERT_PR_VAL(outSize));
  return (SRes)p->Res;
}

STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream,
    ISequentialOutStream *outStream, const UInt64 * /* inSize */,
    const UInt64 * /* outSize */,
    ICompressProgressInfo *progress)
{
  CCompressProgressImp progressImp;
  progressImp.p.Progress = CompressProgress;
  progressImp.Progress = progress;
  progressImp.Res = SZ_OK;

  _seqInStream.RealStream = inStream;
  SetOutStream(outStream);
  SRes res = LzmaEnc_Encode(_encoder, &_seqOutStream.SeqOutStream, &_seqInStream.SeqInStream, progress ? &progressImp.p : NULL, &g_Alloc, &g_BigAlloc);
  ReleaseOutStream();
  if (res == SZ_ERROR_WRITE && _seqOutStream.Res != S_OK)
    return _seqOutStream.Res;
  if (res == SZ_ERROR_PROGRESS && progressImp.Res != S_OK)
    return progressImp.Res;
  return SResToHRESULT(res);
}
  
}}
