// ZDecoder.cpp

#include "StdAfx.h"

#include "ZDecoder.h"

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

#include "../../Common/InBuffer.h"
#include "../../Common/OutBuffer.h"
#include "../../Common/LSBFDecoder.h"

namespace NCompress {
namespace NZ {

static const UInt32 kBufferSize = (1 << 20);
static const Byte kNumBitsMask = 0x1F;   
static const Byte kBlockModeMask = 0x80;   
static const int kNumMinBits = 9;   
static const int kNumMaxBits = 16;   

void CDecoder::Free()
{
  MyFree(_parents);
  _parents = 0;
  MyFree(_suffixes);
  _suffixes = 0;
  MyFree(_stack);
  _stack = 0;
}

bool CDecoder::Alloc(size_t numItems)
{
  Free();
  _parents = (UInt16 *)MyAlloc(numItems * sizeof(UInt16));
  if (_parents == 0)
    return false;
  _suffixes = (Byte *)MyAlloc(numItems * sizeof(Byte));
  if (_suffixes == 0)
    return false;
  _stack = (Byte *)MyAlloc(numItems * sizeof(Byte));
  return _stack != 0;
}

CDecoder::~CDecoder()
{
  Free();
}

STDMETHODIMP CDecoder::CodeReal(ISequentialInStream *inStream,
    ISequentialOutStream *outStream, const UInt64 * /* inSize */, const UInt64 * /* outSize */,
    ICompressProgressInfo *progress)
{
  NStream::NLSBF::CBaseDecoder<CInBuffer> inBuffer;
  COutBuffer outBuffer;

  if (!inBuffer.Create(kBufferSize))
    return E_OUTOFMEMORY;
  inBuffer.SetStream(inStream);
  inBuffer.Init();

  if (!outBuffer.Create(kBufferSize))
    return E_OUTOFMEMORY;
  outBuffer.SetStream(outStream);
  outBuffer.Init();

  int maxbits = _properties & kNumBitsMask;
  if (maxbits < kNumMinBits || maxbits > kNumMaxBits)
    return S_FALSE;
  UInt32 numItems = 1 << maxbits;
  bool blockMode = ((_properties & kBlockModeMask) != 0);
  if (!blockMode)
    return E_NOTIMPL;

  if (maxbits != _numMaxBits || _parents == 0 || _suffixes == 0 || _stack == 0)
  {
    if (!Alloc(numItems))
      return E_OUTOFMEMORY;
    _numMaxBits = maxbits;
  }

  UInt64 prevPos = 0;
  int numBits = kNumMinBits;
  UInt32 head = blockMode ? 257 : 256;

  bool needPrev = false;

  int keepBits = 0;

  _parents[256] = 0; // virus protection
  _suffixes[256] = 0;

  for (;;)
  {
    if (keepBits < numBits)
      keepBits = numBits * 8;
    UInt32 symbol = inBuffer.ReadBits(numBits);
    if (inBuffer.ExtraBitsWereRead())
      break;
    keepBits -= numBits;
    if (symbol >= head)
      return S_FALSE;
    if (blockMode && symbol == 256)
    {
      for (;keepBits > 0; keepBits--)
        inBuffer.ReadBits(1);
      numBits = kNumMinBits;
      head = 257;
      needPrev = false;
      continue;
    }
    UInt32 cur = symbol;
    int i = 0;
    while (cur >= 256)
    {
      _stack[i++] = _suffixes[cur];
      cur = _parents[cur];
    }
    _stack[i++] = (Byte)cur;
    if (needPrev)
    {
      _suffixes[head - 1] = (Byte)cur;
      if (symbol == head - 1)
        _stack[0] = (Byte)cur;
    }
    while (i > 0)
      outBuffer.WriteByte((_stack[--i]));
    if (head < numItems)
    {
      needPrev = true;
      _parents[head++] = (UInt16)symbol;
      if (head > ((UInt32)1 << numBits))
      {
        if (numBits < maxbits)
        {
          numBits++;
          keepBits = numBits * 8;
        }
      }
    }
    else
      needPrev = false;

    UInt64 nowPos = outBuffer.GetProcessedSize();
    if (progress != NULL && nowPos - prevPos > (1 << 18))
    {
      prevPos = nowPos;
      UInt64 packSize = inBuffer.GetProcessedSize();
      RINOK(progress->SetRatioInfo(&packSize, &nowPos));
    }
  }
  return outBuffer.Flush();
}

STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream,
    ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
    ICompressProgressInfo *progress)
{
  try { return CodeReal(inStream, outStream, inSize, outSize, progress); }
  catch(const CInBufferException &e) { return e.ErrorCode; }
  catch(const COutBufferException &e) { return e.ErrorCode; }
  catch(...) { return S_FALSE; }
}

STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size)
{
  if (size < 1)
    return E_INVALIDARG;
  _properties = data[0];
  return S_OK;
}

}}
