// PpmdZip.cpp

#include "StdAfx.h"

#include "../../../C/CpuArch.h"

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

#include "PpmdZip.h"

namespace NCompress {
namespace NPpmdZip {

CDecoder::CDecoder(bool fullFileMode):
  _fullFileMode(fullFileMode)
{
  Ppmd8_Construct(&_ppmd);
  _ppmd.Stream.In = &_inStream.vt;
}

CDecoder::~CDecoder()
{
  Ppmd8_Free(&_ppmd, &g_BigAlloc);
}

STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
    const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)
{
  // try {

  if (!_outStream.Alloc())
    return E_OUTOFMEMORY;
  if (!_inStream.Alloc(1 << 20))
    return E_OUTOFMEMORY;

  _inStream.Stream = inStream;
  _inStream.Init();

  {
    Byte buf[2];
    for (int i = 0; i < 2; i++)
      buf[i] = _inStream.ReadByte();
    if (_inStream.Extra)
      return S_FALSE;
    
    UInt32 val = GetUi16(buf);
    unsigned order = (val & 0xF) + 1;
    UInt32 mem = ((val >> 4) & 0xFF) + 1;
    unsigned restor = (val >> 12);
    if (order < 2 || restor > 2)
      return S_FALSE;
    
    #ifndef PPMD8_FREEZE_SUPPORT
    if (restor == 2)
      return E_NOTIMPL;
    #endif
    
    if (!Ppmd8_Alloc(&_ppmd, mem << 20, &g_BigAlloc))
      return E_OUTOFMEMORY;
    
    if (!Ppmd8_Init_RangeDec(&_ppmd))
      return S_FALSE;
    Ppmd8_Init(&_ppmd, order, restor);
  }

  bool wasFinished = false;
  UInt64 processedSize = 0;

  for (;;)
  {
    size_t size = kBufSize;
    if (outSize)
    {
      const UInt64 rem = *outSize - processedSize;
      if (size > rem)
      {
        size = (size_t)rem;
        if (size == 0)
          break;
      }
    }

    int sym;
    Byte *buf = _outStream.Buf;
    const Byte *lim = buf + size;
    
    do
    {
      sym = Ppmd8_DecodeSymbol(&_ppmd);
      if (_inStream.Extra || sym < 0)
        break;
      *buf++ = (Byte)sym;
    }
    while (buf != lim);
    
    size_t cur = (size_t)(buf - _outStream.Buf);
    processedSize += cur;

    RINOK(WriteStream(outStream, _outStream.Buf, cur));

    RINOK(_inStream.Res);
    if (_inStream.Extra)
      return S_FALSE;

    if (sym < 0)
    {
      if (sym != -1)
        return S_FALSE;
      wasFinished = true;
      break;
    }
    
    if (progress)
    {
      const UInt64 inProccessed = _inStream.GetProcessed();
      RINOK(progress->SetRatioInfo(&inProccessed, &processedSize));
    }
  }
  
  RINOK(_inStream.Res);
  
  if (_fullFileMode)
  {
    if (!wasFinished)
    {
      int res = Ppmd8_DecodeSymbol(&_ppmd);
      RINOK(_inStream.Res);
      if (_inStream.Extra || res != -1)
        return S_FALSE;
    }
    if (!Ppmd8_RangeDec_IsFinishedOK(&_ppmd))
      return S_FALSE;

    if (inSize && *inSize != _inStream.GetProcessed())
      return S_FALSE;
  }
  
  return S_OK;

  // } catch (...) { return E_FAIL; }
}


STDMETHODIMP CDecoder::SetFinishMode(UInt32 finishMode)
{
  _fullFileMode = (finishMode != 0);
  return S_OK;
}

STDMETHODIMP CDecoder::GetInStreamProcessedSize(UInt64 *value)
{
  *value = _inStream.GetProcessed();
  return S_OK;
}



// ---------- Encoder ----------

void CEncProps::Normalize(int level)
{
  if (level < 0) level = 5;
  if (level == 0) level = 1;
  if (level > 9) level = 9;
  if (MemSizeMB == (UInt32)(Int32)-1)
    MemSizeMB = 1 << (level - 1);
  const unsigned kMult = 16;
  for (UInt32 m = 1; m < MemSizeMB; m <<= 1)
    if (ReduceSize <= (m << 20) / kMult)
    {
      MemSizeMB = m;
      break;
    }
  if (Order == -1) Order = 3 + level;
  if (Restor == -1)
    Restor = level < 7 ?
      PPMD8_RESTORE_METHOD_RESTART :
      PPMD8_RESTORE_METHOD_CUT_OFF;
}

CEncoder::~CEncoder()
{
  Ppmd8_Free(&_ppmd, &g_BigAlloc);
}

STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)
{
  int level = -1;
  CEncProps props;
  for (UInt32 i = 0; i < numProps; i++)
  {
    const PROPVARIANT &prop = coderProps[i];
    PROPID propID = propIDs[i];
    if (propID > NCoderPropID::kReduceSize)
      continue;
    if (propID == NCoderPropID::kReduceSize)
    {
      props.ReduceSize = (UInt32)(Int32)-1;
      if (prop.vt == VT_UI8 && prop.uhVal.QuadPart < (UInt32)(Int32)-1)
        props.ReduceSize = (UInt32)prop.uhVal.QuadPart;
      continue;
    }
    if (prop.vt != VT_UI4)
      return E_INVALIDARG;
    UInt32 v = (UInt32)prop.ulVal;
    switch (propID)
    {
      case NCoderPropID::kUsedMemorySize:
        if (v < (1 << 20) || v > (1 << 28))
          return E_INVALIDARG;
        props.MemSizeMB = v >> 20;
        break;
      case NCoderPropID::kOrder:
        if (v < PPMD8_MIN_ORDER || v > PPMD8_MAX_ORDER)
          return E_INVALIDARG;
        props.Order = (Byte)v;
        break;
      case NCoderPropID::kNumThreads: break;
      case NCoderPropID::kLevel: level = (int)v; break;
      case NCoderPropID::kAlgorithm:
        if (v >= PPMD8_RESTORE_METHOD_UNSUPPPORTED)
          return E_INVALIDARG;
        props.Restor = (int)v;
        break;
      default: return E_INVALIDARG;
    }
  }
  props.Normalize(level);
  _props = props;
  return S_OK;
}

CEncoder::CEncoder()
{
  _props.Normalize(-1);
  _ppmd.Stream.Out = &_outStream.vt;
  Ppmd8_Construct(&_ppmd);
}

STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
      const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress)
{
  if (!_inStream.Alloc())
    return E_OUTOFMEMORY;
  if (!_outStream.Alloc(1 << 20))
    return E_OUTOFMEMORY;
  if (!Ppmd8_Alloc(&_ppmd, _props.MemSizeMB << 20, &g_BigAlloc))
    return E_OUTOFMEMORY;

  _outStream.Stream = outStream;
  _outStream.Init();

  Ppmd8_Init_RangeEnc(&_ppmd);
  Ppmd8_Init(&_ppmd, (unsigned)_props.Order, (unsigned)_props.Restor);

  {
    UInt32 val = (UInt32)(((unsigned)_props.Order - 1) + ((_props.MemSizeMB - 1) << 4) + ((unsigned)_props.Restor << 12));
    _outStream.WriteByte((Byte)(val & 0xFF));
    _outStream.WriteByte((Byte)(val >> 8));
  }
  RINOK(_outStream.Res);

  UInt64 processed = 0;
  for (;;)
  {
    UInt32 size;
    RINOK(inStream->Read(_inStream.Buf, kBufSize, &size));
    if (size == 0)
    {
      Ppmd8_EncodeSymbol(&_ppmd, -1);
      Ppmd8_Flush_RangeEnc(&_ppmd);
      return _outStream.Flush();
    }

    processed += size;
    const Byte *buf = _inStream.Buf;
    const Byte *lim = buf + size;
    do
    {
      Ppmd8_EncodeSymbol(&_ppmd, *buf);
      if (_outStream.Res != S_OK)
        break;
    }
    while (++buf != lim);

    RINOK(_outStream.Res);

    if (progress)
    {
      const UInt64 outProccessed = _outStream.GetProcessed();
      RINOK(progress->SetRatioInfo(&processed, &outProccessed));
    }
  }
}




}}
