// Rar1Decoder.cpp
// According to unRAR license, this code may not be used to develop
// a program that creates RAR archives
 
#include "StdAfx.h"

#include "Rar1Decoder.h"

namespace NCompress {
namespace NRar1 {

static const unsigned kNumBits = 12;

static const Byte kShortLen1[16 * 3] =
{
  0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0,0,
  1,3,4,4,5,6,7,8,8,4,4,5,6,6,0,0,
  1,4,4,4,5,6,7,8,8,4,4,5,6,6,4,0
};

static const Byte kShortLen2[16 * 3] =
{
  0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0,0,
  2,3,3,3,4,4,5,6,6,4,4,5,6,6,0,0,
  2,3,3,4,4,4,5,6,6,4,4,5,6,6,4,0
};

static const Byte PosL1[kNumBits + 1]  = { 0,0,2,1,2,2,4,5,4,4,8,0,224 };
static const Byte PosL2[kNumBits + 1]  = { 0,0,0,5,2,2,4,5,4,4,8,2,220 };

static const Byte PosHf0[kNumBits + 1] = { 0,0,0,0,8,8,8,9,0,0,0,0,224 };
static const Byte PosHf1[kNumBits + 1] = { 0,0,0,0,0,4,40,16,16,4,0,47,130 };
static const Byte PosHf2[kNumBits + 1] = { 0,0,0,0,0,2,5,46,64,116,24,0,0 };
static const Byte PosHf3[kNumBits + 1] = { 0,0,0,0,0,0,2,14,202,33,6,0,0 };
static const Byte PosHf4[kNumBits + 1] = { 0,0,0,0,0,0,0,0,255,2,0,0,0 };

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

CDecoder::CDecoder():
   _isSolid(false),
   _solidAllowed(false)
   { }

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

HRESULT CDecoder::CopyBlock(UInt32 distance, UInt32 len)
{
  if (len == 0)
    return S_FALSE;
  if (m_UnpackSize < len)
    return S_FALSE;
  m_UnpackSize -= len;
  return m_OutWindowStream.CopyBlock(distance, len) ? S_OK : S_FALSE;
}


UInt32 CDecoder::DecodeNum(const Byte *numTab)
{
  /*
  {
    // we can check that tables are correct
    UInt32 sum = 0;
    for (unsigned i = 0; i <= kNumBits; i++)
      sum += ((UInt32)numTab[i] << (kNumBits - i));
    if (sum != (1 << kNumBits))
      throw 111;
  }
  */

  UInt32 val = m_InBitStream.GetValue(kNumBits);
  UInt32 sum = 0;
  unsigned i = 2;

  for (;;)
  {
    UInt32 num = numTab[i];
    UInt32 cur = num << (kNumBits - i);
    if (val < cur)
      break;
    i++;
    val -= cur;
    sum += num;
  }
  m_InBitStream.MovePos(i);
  return ((val >> (kNumBits - i)) + sum);
}


HRESULT CDecoder::ShortLZ()
{
  NumHuf = 0;

  if (LCount == 2)
  {
    if (ReadBits(1))
      return CopyBlock(LastDist, LastLength);
    LCount = 0;
  }

  UInt32 bitField = m_InBitStream.GetValue(8);

  UInt32 len, dist;
  {
    const Byte *xors = (AvrLn1 < 37) ? kShortLen1 : kShortLen2;
    const Byte *lens = xors + 16 + Buf60;
    for (len = 0; ((bitField ^ xors[len]) >> (8 - lens[len])) != 0; len++);
    m_InBitStream.MovePos(lens[len]);
  }
  
  if (len >= 9)
  {
    if (len == 9)
    {
      LCount++;
      return CopyBlock(LastDist, LastLength);
    }

    LCount = 0;

    if (len == 14)
    {
      len = DecodeNum(PosL2) + 5;
      dist = 0x8000 + ReadBits(15) - 1;
      LastLength = len;
      LastDist = dist;
      return CopyBlock(dist, len);
    }

    UInt32 saveLen = len;
    dist = m_RepDists[(m_RepDistPtr - (len - 9)) & 3];
    
    len = DecodeNum(PosL1);
    
    if (len == 0xff && saveLen == 10)
    {
      Buf60 ^= 16;
      return S_OK;
    }
    if (dist >= 256)
    {
      len++;
      if (dist >= MaxDist3 - 1)
        len++;
    }
  }
  else
  {
    LCount = 0;
    AvrLn1 += len;
    AvrLn1 -= AvrLn1 >> 4;
    
    unsigned distancePlace = DecodeNum(PosHf2) & 0xff;
    
    dist = ChSetA[distancePlace];
    
    if (distancePlace != 0)
    {
      PlaceA[dist]--;
      UInt32 lastDistance = ChSetA[(size_t)distancePlace - 1];
      PlaceA[lastDistance]++;
      ChSetA[distancePlace] = lastDistance;
      ChSetA[(size_t)distancePlace - 1] = dist;
    }
  }

  m_RepDists[m_RepDistPtr++] = dist;
  m_RepDistPtr &= 3;
  len += 2;
  LastLength = len;
  LastDist = dist;
  return CopyBlock(dist, len);
}


HRESULT CDecoder::LongLZ()
{
  UInt32 len;
  UInt32 dist;
  UInt32 distancePlace, newDistancePlace;
  UInt32 oldAvr2, oldAvr3;

  NumHuf = 0;
  Nlzb += 16;
  if (Nlzb > 0xff)
  {
    Nlzb = 0x90;
    Nhfb >>= 1;
  }
  oldAvr2 = AvrLn2;

  if (AvrLn2 >= 64)
    len = DecodeNum(AvrLn2 < 122 ? PosL1 : PosL2);
  else
  {
    UInt32 bitField = m_InBitStream.GetValue(16);
    if (bitField < 0x100)
    {
      len = bitField;
      m_InBitStream.MovePos(16);
    }
    else
    {
      for (len = 0; ((bitField << len) & 0x8000) == 0; len++);
      
      m_InBitStream.MovePos(len + 1);
    }
  }

  AvrLn2 += len;
  AvrLn2 -= AvrLn2 >> 5;

  {
    const Byte *tab;
         if (AvrPlcB >= 0x2900) tab = PosHf2;
    else if (AvrPlcB >= 0x0700) tab = PosHf1;
    else                        tab = PosHf0;
    distancePlace = DecodeNum(tab); // [0, 256]
  }

  AvrPlcB += distancePlace;
  AvrPlcB -= AvrPlcB >> 8;

  distancePlace &= 0xff;
  
  for (;;)
  {
    dist = ChSetB[distancePlace];
    newDistancePlace = NToPlB[dist++ & 0xff]++;
    if (dist & 0xff)
      break;
    CorrHuff(ChSetB,NToPlB);
  }

  ChSetB[distancePlace] = ChSetB[newDistancePlace];
  ChSetB[newDistancePlace] = dist;

  dist = ((dist & 0xff00) >> 1) | ReadBits(7);

  oldAvr3 = AvrLn3;
  
  if (len != 1 && len != 4)
  {
    if (len == 0 && dist <= MaxDist3)
    {
      AvrLn3++;
      AvrLn3 -= AvrLn3 >> 8;
    }
    else if (AvrLn3 > 0)
      AvrLn3--;
  }
  
  len += 3;
  
  if (dist >= MaxDist3)
    len++;
  if (dist <= 256)
    len += 8;
  
  if (oldAvr3 > 0xb0 || (AvrPlc >= 0x2a00 && oldAvr2 < 0x40))
    MaxDist3 = 0x7f00;
  else
    MaxDist3 = 0x2001;
  
  m_RepDists[m_RepDistPtr++] = --dist;
  m_RepDistPtr &= 3;
  LastLength = len;
  LastDist = dist;
  
  return CopyBlock(dist, len);
}


HRESULT CDecoder::HuffDecode()
{
  UInt32 curByte, newBytePlace;
  UInt32 len;
  UInt32 dist;
  unsigned bytePlace;
  {
    const Byte *tab;
    
    if      (AvrPlc >= 0x7600)  tab = PosHf4;
    else if (AvrPlc >= 0x5e00)  tab = PosHf3;
    else if (AvrPlc >= 0x3600)  tab = PosHf2;
    else if (AvrPlc >= 0x0e00)  tab = PosHf1;
    else                        tab = PosHf0;
    
    bytePlace = DecodeNum(tab); // [0, 256]
  }
  
  if (StMode)
  {
    if (bytePlace == 0)
    {
      if (ReadBits(1))
      {
        NumHuf = 0;
        StMode = false;
        return S_OK;
      }
      len = ReadBits(1) + 3;
      dist = DecodeNum(PosHf2);
      dist = (dist << 5) | ReadBits(5);
      if (dist == 0)
        return S_FALSE;
      return CopyBlock(dist - 1, len);
    }
    bytePlace--; // bytePlace is [0, 255]
  }
  else if (NumHuf++ >= 16 && FlagsCnt == 0)
    StMode = true;
  
  bytePlace &= 0xff;
  AvrPlc += bytePlace;
  AvrPlc -= AvrPlc >> 8;
  Nhfb += 16;
  
  if (Nhfb > 0xff)
  {
    Nhfb = 0x90;
    Nlzb >>= 1;
  }

  m_UnpackSize--;
  m_OutWindowStream.PutByte((Byte)(ChSet[bytePlace] >> 8));

  for (;;)
  {
    curByte = ChSet[bytePlace];
    newBytePlace = NToPl[curByte++ & 0xff]++;
    if ((curByte & 0xff) <= 0xa1)
      break;
    CorrHuff(ChSet, NToPl);
  }

  ChSet[bytePlace] = ChSet[newBytePlace];
  ChSet[newBytePlace] = curByte;
  return S_OK;
}


void CDecoder::GetFlagsBuf()
{
  UInt32 flags, newFlagsPlace;
  UInt32 flagsPlace = DecodeNum(PosHf2); // [0, 256]

  if (flagsPlace >= ARRAY_SIZE(ChSetC))
    return;

  for (;;)
  {
    flags = ChSetC[flagsPlace];
    FlagBuf = flags >> 8;
    newFlagsPlace = NToPlC[flags++ & 0xff]++;
    if ((flags & 0xff) != 0)
      break;
    CorrHuff(ChSetC, NToPlC);
  }

  ChSetC[flagsPlace] = ChSetC[newFlagsPlace];
  ChSetC[newFlagsPlace] = flags;
}


void CDecoder::CorrHuff(UInt32 *CharSet,UInt32 *NumToPlace)
{
  int i;
  for (i = 7; i >= 0; i--)
    for (int j = 0; j < 32; j++, CharSet++)
      *CharSet = (*CharSet & ~0xff) | i;
  memset(NumToPlace, 0, sizeof(NToPl));
  for (i = 6; i >= 0; i--)
    NumToPlace[i] = (7 - i) * 32;
}



HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream,
    const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo * /* progress */)
{
  if (!inSize || !outSize)
    return E_INVALIDARG;

  if (_isSolid && !_solidAllowed)
    return S_FALSE;

  _solidAllowed = false;

  if (!m_OutWindowStream.Create(kHistorySize))
    return E_OUTOFMEMORY;
  if (!m_InBitStream.Create(1 << 20))
    return E_OUTOFMEMORY;

  m_UnpackSize = *outSize;

  m_OutWindowStream.SetStream(outStream);
  m_OutWindowStream.Init(_isSolid);
  m_InBitStream.SetStream(inStream);
  m_InBitStream.Init();

  // InitData

  FlagsCnt = 0;
  FlagBuf = 0;
  StMode = false;
  LCount = 0;

  if (!_isSolid)
  {
    AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = NumHuf = Buf60 = 0;
    AvrPlc = 0x3500;
    MaxDist3 = 0x2001;
    Nhfb = Nlzb = 0x80;

    {
      // InitStructures
      for (unsigned i = 0; i < kNumRepDists; i++)
        m_RepDists[i] = 0;
      m_RepDistPtr = 0;
      LastLength = 0;
      LastDist = 0;
    }
    
    // InitHuff
    
    for (UInt32 i = 0; i < 256; i++)
    {
      Place[i] = PlaceA[i] = PlaceB[i] = i;
      UInt32 c = (~i + 1) & 0xff;
      PlaceC[i] = c;
      ChSet[i] = ChSetB[i] = i << 8;
      ChSetA[i] = i;
      ChSetC[i] = c << 8;
    }
    memset(NToPl, 0, sizeof(NToPl));
    memset(NToPlB, 0, sizeof(NToPlB));
    memset(NToPlC, 0, sizeof(NToPlC));
    CorrHuff(ChSetB, NToPlB);
  }
   
  if (m_UnpackSize > 0)
  {
    GetFlagsBuf();
    FlagsCnt = 8;
  }

  while (m_UnpackSize != 0)
  {
    if (!StMode)
    {
      if (--FlagsCnt < 0)
      {
        GetFlagsBuf();
        FlagsCnt = 7;
      }
      
      if (FlagBuf & 0x80)
      {
        FlagBuf <<= 1;
        if (Nlzb > Nhfb)
        {
          RINOK(LongLZ());
          continue;
        }
      }
      else
      {
        FlagBuf <<= 1;
        
        if (--FlagsCnt < 0)
        {
          GetFlagsBuf();
          FlagsCnt = 7;
        }

        if ((FlagBuf & 0x80) == 0)
        {
          FlagBuf <<= 1;
          RINOK(ShortLZ());
          continue;
        }
        
        FlagBuf <<= 1;
        
        if (Nlzb <= Nhfb)
        {
          RINOK(LongLZ());
          continue;
        }
      }
    }

    RINOK(HuffDecode());
  }
  
  _solidAllowed = true;
  return m_OutWindowStream.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 CLzOutWindowException &e) { return e.ErrorCode; }
  catch(...) { return S_FALSE; }
}

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

}}
