// QuantumDecoder.h

#ifndef __COMPRESS_QUANTUM_DECODER_H
#define __COMPRESS_QUANTUM_DECODER_H

#include "../../Common/MyCom.h"

#include "../ICoder.h"

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

#include "LzOutWindow.h"

namespace NCompress {
namespace NQuantum {

class CStreamBitDecoder
{
  UInt32 Value;
  CInBuffer Stream;
public:
  bool Create(UInt32 bufferSize) { return Stream.Create(bufferSize); }
  void SetStream(ISequentialInStream *stream) { Stream.SetStream(stream); }
  void ReleaseStream() { Stream.ReleaseStream(); }

  void Finish() { Value = 0x10000; }

  void Init()
  {
    Stream.Init();
    Value = 0x10000;
  }

  UInt64 GetProcessedSize() const { return Stream.GetProcessedSize(); }
  bool WasFinished() const { return Stream.WasFinished(); }
  
  UInt32 ReadBit()
  {
    if (Value >= 0x10000)
      Value = 0x100 | Stream.ReadByte();
    UInt32 res = (Value >> 7) & 1;
    Value <<= 1;
    return res;
  }

  UInt32 ReadBits(int numBits) // numBits > 0
  {
    UInt32 res = 0;
    do
      res = (res << 1) | ReadBit();
    while (--numBits != 0);
    return res;
  }
};

const unsigned kNumLitSelectorBits = 2;
const unsigned kNumLitSelectors = (1 << kNumLitSelectorBits);
const unsigned kNumLitSymbols = 1 << (8 - kNumLitSelectorBits);
const unsigned kNumMatchSelectors = 3;
const unsigned kNumSelectors = kNumLitSelectors + kNumMatchSelectors;
const unsigned kNumSymbolsMax = kNumLitSymbols; // 64

namespace NRangeCoder {

class CDecoder
{
  UInt32 Low;
  UInt32 Range;
  UInt32 Code;
public:
  CStreamBitDecoder Stream;
  bool Create(UInt32 bufferSize) { return Stream.Create(bufferSize); }
  void SetStream(ISequentialInStream *stream) { Stream.SetStream(stream); }
  void ReleaseStream() { Stream.ReleaseStream(); }

  void Init()
  {
    Stream.Init();
    Low = 0;
    Range = 0x10000;
    Code = Stream.ReadBits(16);
  }

  void Finish()
  {
    // we need these extra two Bit_reads
    Stream.ReadBit();
    Stream.ReadBit();
    Stream.Finish();
  }

  UInt64 GetProcessedSize() const { return Stream.GetProcessedSize(); }

  UInt32 GetThreshold(UInt32 total) const
  {
    return ((Code + 1) * total - 1) / Range; // & 0xFFFF is not required;
  }

  void Decode(UInt32 start, UInt32 end, UInt32 total)
  {
    UInt32 high = Low + end * Range / total - 1;
    UInt32 offset = start * Range / total;
    Code -= offset;
    Low += offset;
    for (;;)
    {
      if ((Low & 0x8000) != (high & 0x8000))
      {
        if ((Low & 0x4000) == 0 || (high & 0x4000) != 0)
          break;
        Low &= 0x3FFF;
        high |= 0x4000;
      }
      Low = (Low << 1) & 0xFFFF;
      high = ((high << 1) | 1) & 0xFFFF;
      Code = ((Code << 1) | Stream.ReadBit());
    }
    Range = high - Low + 1;
  }
};

const UInt16 kUpdateStep = 8;
const UInt16 kFreqSumMax = 3800;
const UInt16 kReorderCountStart = 4;
const UInt16 kReorderCount = 50;

class CModelDecoder
{
  unsigned NumItems;
  unsigned ReorderCount;
  UInt16 Freqs[kNumSymbolsMax + 1];
  Byte Values[kNumSymbolsMax];
public:
  void Init(unsigned numItems)
  {
    NumItems = numItems;
    ReorderCount = kReorderCountStart;
    for (unsigned i = 0; i < numItems; i++)
    {
      Freqs[i] = (UInt16)(numItems - i);
      Values[i] = (Byte)i;
    }
    Freqs[numItems] = 0;
  }
  
  unsigned Decode(CDecoder *rangeDecoder)
  {
    UInt32 threshold = rangeDecoder->GetThreshold(Freqs[0]);
    unsigned i;
    for (i = 1; Freqs[i] > threshold; i++);
    rangeDecoder->Decode(Freqs[i], Freqs[i - 1], Freqs[0]);
    unsigned res = Values[--i];
    do
      Freqs[i] += kUpdateStep;
    while (i-- != 0);

    if (Freqs[0] > kFreqSumMax)
    {
      if (--ReorderCount == 0)
      {
        ReorderCount = kReorderCount;
        for (i = 0; i < NumItems; i++)
          Freqs[i] = (UInt16)(((Freqs[i] - Freqs[i + 1]) + 1) >> 1);
        for (i = 0; i < NumItems - 1; i++)
          for (unsigned j = i + 1; j < NumItems; j++)
            if (Freqs[i] < Freqs[j])
            {
              UInt16 tmpFreq = Freqs[i];
              Byte tmpVal = Values[i];
              Freqs[i] = Freqs[j];
              Values[i] = Values[j];
              Freqs[j] = tmpFreq;
              Values[j] = tmpVal;
            }
        do
          Freqs[i] = (UInt16)(Freqs[i] + Freqs[i + 1]);
        while (i-- != 0);
      }
      else
      {
        i = NumItems - 1;
        do
        {
          Freqs[i] >>= 1;
          if (Freqs[i] <= Freqs[i + 1])
            Freqs[i] = (UInt16)(Freqs[i + 1] + 1);
        }
        while (i-- != 0);
      }
    }
    return res;
  }
};

}

class CDecoder:
  public ICompressCoder,
  public ICompressSetInStream,
  public ICompressSetOutStreamSize,
  public CMyUnknownImp
{
  CLzOutWindow _outWindowStream;
  NRangeCoder::CDecoder _rangeDecoder;

  UInt64 _outSize;
  int _remainLen; // -1 means end of stream. // -2 means need Init
  UInt32 _rep0;

  int _numDictBits;
  bool _keepHistory;

  NRangeCoder::CModelDecoder m_Selector;
  NRangeCoder::CModelDecoder m_Literals[kNumLitSelectors];
  NRangeCoder::CModelDecoder m_PosSlot[kNumMatchSelectors];
  NRangeCoder::CModelDecoder m_LenSlot;
  void Init();
  HRESULT CodeSpec(UInt32 size);
public:
  MY_UNKNOWN_IMP2(
      ICompressSetInStream,
      ICompressSetOutStreamSize)

  void ReleaseStreams()
  {
    _outWindowStream.ReleaseStream();
    ReleaseInStream();
  }

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

  HRESULT Flush() {  return _outWindowStream.Flush(); }

  HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream,
      const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress);
  
  STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream,
      const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress);

  STDMETHOD(SetInStream)(ISequentialInStream *inStream);
  STDMETHOD(ReleaseInStream)();
  STDMETHOD(SetOutStreamSize)(const UInt64 *outSize);

  void SetParams(int numDictBits) { _numDictBits = numDictBits; }
  void SetKeepHistory(bool keepHistory) { _keepHistory = keepHistory; }
  CDecoder(): _keepHistory(false) {}
  virtual ~CDecoder() {}
};

}}

#endif
