// PpmdDecoder.cpp
// 2020-07-03 : Igor Pavlov : Public domain

#include "StdAfx.h"

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

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

#include "PpmdDecoder.h"

namespace NCompress {
namespace NPpmd {

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

enum
{
  kStatus_NeedInit,
  kStatus_Normal,
  kStatus_Finished_With_Mark,
  kStatus_Error
};

CDecoder::~CDecoder()
{
  ::MidFree(_outBuf);
  Ppmd7_Free(&_ppmd, &g_BigAlloc);
}

STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *props, UInt32 size)
{
  if (size < 5)
    return E_INVALIDARG;
  _order = props[0];
  UInt32 memSize = GetUi32(props + 1);
  if (
      // _order < PPMD7_MIN_ORDER ||
      _order > PPMD7_MAX_ORDER ||
      memSize < PPMD7_MIN_MEM_SIZE ||
      memSize > PPMD7_MAX_MEM_SIZE)
    return E_NOTIMPL;
  if (!_inStream.Alloc(1 << 20))
    return E_OUTOFMEMORY;
  if (!Ppmd7_Alloc(&_ppmd, memSize, &g_BigAlloc))
    return E_OUTOFMEMORY;
  return S_OK;
}

#define _rangeDec _ppmd.rc.dec

#define CHECK_EXTRA_ERROR \
    if (_inStream.Extra) { \
      _status = kStatus_Error; \
      return (_res = (_inStream.Res != SZ_OK ? _inStream.Res: S_FALSE)); }


HRESULT CDecoder::CodeSpec(Byte *memStream, UInt32 size)
{
  if (_res != S_OK)
    return _res;
  
  switch (_status)
  {
    case kStatus_Finished_With_Mark: return S_OK;
    case kStatus_Error: return S_FALSE;
    case kStatus_NeedInit:
      _inStream.Init();
      if (!Ppmd7z_RangeDec_Init(&_rangeDec))
      {
        _status = kStatus_Error;
        return (_res = S_FALSE);
      }
      CHECK_EXTRA_ERROR
      _status = kStatus_Normal;
      Ppmd7_Init(&_ppmd, _order);
      break;
  }
  
  if (_outSizeDefined)
  {
    const UInt64 rem = _outSize - _processedSize;
    if (size > rem)
      size = (UInt32)rem;
  }

  int sym = 0;
  {
    Byte *buf = memStream;
    const Byte *lim = buf + size;
    for (; buf != lim; buf++)
    {
      sym = Ppmd7z_DecodeSymbol(&_ppmd);
      if (_inStream.Extra || sym < 0)
        break;
      *buf = (Byte)sym;
    }
    /*
    buf = Ppmd7z_DecodeSymbols(&_ppmd, buf, lim);
    sym = _ppmd.LastSymbol;
    */
    _processedSize += (size_t)(buf - memStream);
  }

  CHECK_EXTRA_ERROR
  
  if (sym >= 0)
  {
    if (!FinishStream
        || !_outSizeDefined
        || _outSize != _processedSize
        || _rangeDec.Code == 0)
      return S_OK;
    /*
    // We can decode additional End Marker here:
    sym = Ppmd7z_DecodeSymbol(&_ppmd);
    CHECK_EXTRA_ERROR
    */
  }

  if (sym != PPMD7_SYM_END || _rangeDec.Code != 0)
  {
    _status = kStatus_Error;
    return (_res = S_FALSE);
  }
  
  _status = kStatus_Finished_With_Mark;
  return S_OK;
}



STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
    const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)
{
  if (!_outBuf)
  {
    _outBuf = (Byte *)::MidAlloc(kBufSize);
    if (!_outBuf)
      return E_OUTOFMEMORY;
  }
  
  _inStream.Stream = inStream;
  SetOutStreamSize(outSize);

  do
  {
    const UInt64 startPos = _processedSize;
    HRESULT res = CodeSpec(_outBuf, kBufSize);
    size_t processed = (size_t)(_processedSize - startPos);
    RINOK(WriteStream(outStream, _outBuf, processed));
    RINOK(res);
    if (_status == kStatus_Finished_With_Mark)
      break;
    if (progress)
    {
      const UInt64 inProcessed = _inStream.GetProcessed();
      RINOK(progress->SetRatioInfo(&inProcessed, &_processedSize));
    }
  }
  while (!_outSizeDefined || _processedSize < _outSize);

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

  return S_OK;
}


STDMETHODIMP CDecoder::SetOutStreamSize(const UInt64 *outSize)
{
  _outSizeDefined = (outSize != NULL);
  if (_outSizeDefined)
    _outSize = *outSize;
  _processedSize = 0;
  _status = kStatus_NeedInit;
  _res = SZ_OK;
  return S_OK;
}

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

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

#ifndef NO_READ_FROM_CODER

STDMETHODIMP CDecoder::SetInStream(ISequentialInStream *inStream)
{
  InSeqStream = inStream;
  _inStream.Stream = inStream;
  return S_OK;
}

STDMETHODIMP CDecoder::ReleaseInStream()
{
  InSeqStream.Release();
  return S_OK;
}

STDMETHODIMP CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)
{
  const UInt64 startPos = _processedSize;
  HRESULT res = CodeSpec((Byte *)data, size);
  if (processedSize)
    *processedSize = (UInt32)(_processedSize - startPos);
  return res;
}

#endif

}}
