// LzxDecoder.cpp

#include "StdAfx.h"

#include "../../Common/Defs.h"

#include "LzxDecoder.h"

namespace NCompress {
namespace NLzx {

const int kLenIdNeedInit = -2;

CDecoder::CDecoder(bool wimMode):
  _keepHistory(false),
  _skipByte(false),
  _wimMode(wimMode)
{
  m_x86ConvertOutStreamSpec = new Cx86ConvertOutStream;
  m_x86ConvertOutStream = m_x86ConvertOutStreamSpec;
}

void CDecoder::ReleaseStreams()
{
  m_OutWindowStream.ReleaseStream();
  m_InBitStream.ReleaseStream();
  m_x86ConvertOutStreamSpec->ReleaseStream();
}

STDMETHODIMP CDecoder::Flush()
{
  RINOK(m_OutWindowStream.Flush());
  return m_x86ConvertOutStreamSpec->Flush();
}

UInt32 CDecoder::ReadBits(unsigned numBits) { return m_InBitStream.ReadBits(numBits); }

#define RIF(x) { if (!(x)) return false; }

bool CDecoder::ReadTable(Byte *lastLevels, Byte *newLevels, UInt32 numSymbols)
{
  Byte levelLevels[kLevelTableSize];
  UInt32 i;
  for (i = 0; i < kLevelTableSize; i++)
    levelLevels[i] = (Byte)ReadBits(kNumBitsForPreTreeLevel);
  RIF(m_LevelDecoder.SetCodeLengths(levelLevels));
  unsigned num = 0;
  Byte symbol = 0;
  for (i = 0; i < numSymbols;)
  {
    if (num != 0)
    {
      lastLevels[i] = newLevels[i] = symbol;
      i++;
      num--;
      continue;
    }
    UInt32 number = m_LevelDecoder.DecodeSymbol(&m_InBitStream);
    if (number == kLevelSymbolZeros)
    {
      num = kLevelSymbolZerosStartValue + (unsigned)ReadBits(kLevelSymbolZerosNumBits);
      symbol = 0;
    }
    else if (number == kLevelSymbolZerosBig)
    {
      num = kLevelSymbolZerosBigStartValue + (unsigned)ReadBits(kLevelSymbolZerosBigNumBits);
      symbol = 0;
    }
    else if (number == kLevelSymbolSame || number <= kNumHuffmanBits)
    {
      if (number <= kNumHuffmanBits)
        num = 1;
      else
      {
        num = kLevelSymbolSameStartValue + (unsigned)ReadBits(kLevelSymbolSameNumBits);
        number = m_LevelDecoder.DecodeSymbol(&m_InBitStream);
        if (number > kNumHuffmanBits)
          return false;
      }
      symbol = Byte((17 + lastLevels[i] - number) % (kNumHuffmanBits + 1));
    }
    else
      return false;
  }
  return true;
}

bool CDecoder::ReadTables(void)
{
  Byte newLevels[kMaxTableSize];
  {
    if (_skipByte)
      m_InBitStream.DirectReadByte();
    m_InBitStream.Normalize();

    unsigned blockType = (unsigned)ReadBits(kNumBlockTypeBits);
    if (blockType > kBlockTypeUncompressed)
      return false;
    if (_wimMode)
      if (ReadBits(1) == 1)
        m_UnCompressedBlockSize = (1 << 15);
      else
        m_UnCompressedBlockSize = ReadBits(16);
    else
      m_UnCompressedBlockSize = m_InBitStream.ReadBitsBig(kUncompressedBlockSizeNumBits);

    m_IsUncompressedBlock = (blockType == kBlockTypeUncompressed);

    _skipByte = (m_IsUncompressedBlock && ((m_UnCompressedBlockSize & 1) != 0));

    if (m_IsUncompressedBlock)
    {
      ReadBits(16 - m_InBitStream.GetBitPosition());
      if (!m_InBitStream.ReadUInt32(m_RepDistances[0]))
        return false;
      m_RepDistances[0]--;
      for (unsigned i = 1; i < kNumRepDistances; i++)
      {
        UInt32 rep = 0;
        for (unsigned j = 0; j < 4; j++)
          rep |= (UInt32)m_InBitStream.DirectReadByte() << (8 * j);
        m_RepDistances[i] = rep - 1;
      }
      return true;
    }
    m_AlignIsUsed = (blockType == kBlockTypeAligned);
    if (m_AlignIsUsed)
    {
      for (unsigned i = 0; i < kAlignTableSize; i++)
        newLevels[i] = (Byte)ReadBits(kNumBitsForAlignLevel);
      RIF(m_AlignDecoder.SetCodeLengths(newLevels));
    }
  }

  RIF(ReadTable(m_LastMainLevels, newLevels, 256));
  RIF(ReadTable(m_LastMainLevels + 256, newLevels + 256, m_NumPosLenSlots));
  for (UInt32 i = 256 + m_NumPosLenSlots; i < kMainTableSize; i++)
    newLevels[i] = 0;
  RIF(m_MainDecoder.SetCodeLengths(newLevels));

  RIF(ReadTable(m_LastLenLevels, newLevels, kNumLenSymbols));
  return m_LenDecoder.SetCodeLengths(newLevels);
}

class CDecoderFlusher
{
  CDecoder *m_Decoder;
public:
  bool NeedFlush;
  CDecoderFlusher(CDecoder *decoder): m_Decoder(decoder), NeedFlush(true) {}
  ~CDecoderFlusher()
  {
    if (NeedFlush)
      m_Decoder->Flush();
    m_Decoder->ReleaseStreams();
  }
};


void CDecoder::ClearPrevLevels()
{
  unsigned i;
  for (i = 0; i < kMainTableSize; i++)
    m_LastMainLevels[i] = 0;
  for (i = 0; i < kNumLenSymbols; i++)
    m_LastLenLevels[i] = 0;
}


HRESULT CDecoder::CodeSpec(UInt32 curSize)
{
  if (_remainLen == kLenIdNeedInit)
  {
    _remainLen = 0;
    m_InBitStream.Init();
    if (!_keepHistory || !m_IsUncompressedBlock)
      m_InBitStream.Normalize();
    if (!_keepHistory)
    {
      _skipByte = false;
      m_UnCompressedBlockSize = 0;
      ClearPrevLevels();
      UInt32 i86TranslationSize = 12000000;
      bool translationMode = true;
      if (!_wimMode)
      {
        translationMode = (ReadBits(1) != 0);
        if (translationMode)
        {
          i86TranslationSize = ReadBits(16) << 16;
          i86TranslationSize |= ReadBits(16);
        }
      }
      m_x86ConvertOutStreamSpec->Init(translationMode, i86TranslationSize);
      
      for (unsigned i = 0 ; i < kNumRepDistances; i++)
        m_RepDistances[i] = 0;
    }
  }

  while (_remainLen > 0 && curSize > 0)
  {
    m_OutWindowStream.PutByte(m_OutWindowStream.GetByte(m_RepDistances[0]));
    _remainLen--;
    curSize--;
  }

  while (curSize > 0)
  {
    if (m_UnCompressedBlockSize == 0)
      if (!ReadTables())
        return S_FALSE;
    UInt32 next = (Int32)MyMin(m_UnCompressedBlockSize, curSize);
    curSize -= next;
    m_UnCompressedBlockSize -= next;
    if (m_IsUncompressedBlock)
    {
      while (next > 0)
      {
        m_OutWindowStream.PutByte(m_InBitStream.DirectReadByte());
        next--;
      }
    }
    else while (next > 0)
    {
      UInt32 number = m_MainDecoder.DecodeSymbol(&m_InBitStream);
      if (number < 256)
      {
        m_OutWindowStream.PutByte((Byte)number);
        next--;
      }
      else
      {
        UInt32 posLenSlot = number - 256;
        if (posLenSlot >= m_NumPosLenSlots)
          return S_FALSE;
        UInt32 posSlot = posLenSlot / kNumLenSlots;
        UInt32 lenSlot = posLenSlot % kNumLenSlots;
        UInt32 len = kMatchMinLen + lenSlot;
        if (lenSlot == kNumLenSlots - 1)
        {
          UInt32 lenTemp = m_LenDecoder.DecodeSymbol(&m_InBitStream);
          if (lenTemp >= kNumLenSymbols)
            return S_FALSE;
          len += lenTemp;
        }
        
        if (posSlot < kNumRepDistances)
        {
          UInt32 distance = m_RepDistances[posSlot];
          m_RepDistances[posSlot] = m_RepDistances[0];
          m_RepDistances[0] = distance;
        }
        else
        {
          UInt32 distance;
          unsigned numDirectBits;
          if (posSlot < kNumPowerPosSlots)
          {
            numDirectBits = (unsigned)(posSlot >> 1) - 1;
            distance = ((2 | (posSlot & 1)) << numDirectBits);
          }
          else
          {
            numDirectBits = kNumLinearPosSlotBits;
            distance = ((posSlot - 0x22) << kNumLinearPosSlotBits);
          }

          if (m_AlignIsUsed && numDirectBits >= kNumAlignBits)
          {
            distance += (m_InBitStream.ReadBits(numDirectBits - kNumAlignBits) << kNumAlignBits);
            UInt32 alignTemp = m_AlignDecoder.DecodeSymbol(&m_InBitStream);
            if (alignTemp >= kAlignTableSize)
              return S_FALSE;
            distance += alignTemp;
          }
          else
            distance += m_InBitStream.ReadBits(numDirectBits);
          m_RepDistances[2] = m_RepDistances[1];
          m_RepDistances[1] = m_RepDistances[0];
          m_RepDistances[0] = distance - kNumRepDistances;
        }

        UInt32 locLen = len;
        if (locLen > next)
          locLen = next;

        if (!m_OutWindowStream.CopyBlock(m_RepDistances[0], locLen))
          return S_FALSE;

        len -= locLen;
        next -= locLen;
        if (len != 0)
        {
          _remainLen = (int)len;
          return S_OK;
        }
      }
    }
  }
  return S_OK;
}

HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream,
    const UInt64 *, const UInt64 *outSize, ICompressProgressInfo *progress)
{
  if (outSize == NULL)
    return E_INVALIDARG;
  UInt64 size = *outSize;

  RINOK(SetInStream(inStream));
  m_x86ConvertOutStreamSpec->SetStream(outStream);
  m_OutWindowStream.SetStream(m_x86ConvertOutStream);
  RINOK(SetOutStreamSize(outSize));

  CDecoderFlusher flusher(this);

  const UInt64 start = m_OutWindowStream.GetProcessedSize();
  for (;;)
  {
    UInt32 curSize = 1 << 18;
    UInt64 rem = size - (m_OutWindowStream.GetProcessedSize() - start);
    if (curSize > rem)
      curSize = (UInt32)rem;
    if (curSize == 0)
      break;
    RINOK(CodeSpec(curSize));
    if (progress != NULL)
    {
      UInt64 inSize = m_InBitStream.GetProcessedSize();
      UInt64 nowPos64 = m_OutWindowStream.GetProcessedSize() - start;
      RINOK(progress->SetRatioInfo(&inSize, &nowPos64));
    }
  }
  flusher.NeedFlush = false;
  return Flush();
}

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

STDMETHODIMP CDecoder::SetInStream(ISequentialInStream *inStream)
{
  m_InBitStream.SetStream(inStream);
  return S_OK;
}

STDMETHODIMP CDecoder::ReleaseInStream()
{
  m_InBitStream.ReleaseStream();
  return S_OK;
}

STDMETHODIMP CDecoder::SetOutStreamSize(const UInt64 *outSize)
{
  if (outSize == NULL)
    return E_FAIL;
  _remainLen = kLenIdNeedInit;
  m_OutWindowStream.Init(_keepHistory);
  return S_OK;
}

HRESULT CDecoder::SetParams(unsigned numDictBits)
{
  if (numDictBits < kNumDictionaryBitsMin || numDictBits > kNumDictionaryBitsMax)
    return E_INVALIDARG;
  UInt32 numPosSlots;
  if (numDictBits < 20)
    numPosSlots = 30 + (numDictBits - 15) * 2;
  else if (numDictBits == 20)
    numPosSlots = 42;
  else
    numPosSlots = 50;
  m_NumPosLenSlots = numPosSlots * kNumLenSlots;
  if (!m_OutWindowStream.Create(kDictionarySizeMax))
    return E_OUTOFMEMORY;
  if (!m_InBitStream.Create(1 << 16))
    return E_OUTOFMEMORY;
  return S_OK;
}

}}
