// LzmaDecoder.cpp

#include "StdAfx.h"

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

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

#include "LzmaDecoder.h"

static HRESULT SResToHRESULT(SRes res)
{
  switch (res)
  {
    case SZ_OK: return S_OK;
    case SZ_ERROR_MEM: return E_OUTOFMEMORY;
    case SZ_ERROR_PARAM: return E_INVALIDARG;
    case SZ_ERROR_UNSUPPORTED: return E_NOTIMPL;
    case SZ_ERROR_DATA: return S_FALSE;
  }
  return E_FAIL;
}

namespace NCompress {
namespace NLzma {

CDecoder::CDecoder():
    _inBuf(NULL),
    _lzmaStatus(LZMA_STATUS_NOT_SPECIFIED),
    FinishStream(false),
    _propsWereSet(false),
    _outSizeDefined(false),
    _outStep(1 << 22),
    _inBufSize(0),
    _inBufSizeNew(1 << 20)
{
  _inProcessed = 0;
  _inPos = _inLim = 0;

  LzmaDec_Construct(&_state);
}

CDecoder::~CDecoder()
{
  LzmaDec_Free(&_state, &g_Alloc);
  MyFree(_inBuf);
}

STDMETHODIMP CDecoder::SetInBufSize(UInt32 , UInt32 size) { _inBufSizeNew = size; return S_OK; }
STDMETHODIMP CDecoder::SetOutBufSize(UInt32 , UInt32 size) { _outStep = size; return S_OK; }

HRESULT CDecoder::CreateInputBuffer()
{
  if (!_inBuf || _inBufSizeNew != _inBufSize)
  {
    MyFree(_inBuf);
    _inBufSize = 0;
    _inBuf = (Byte *)MyAlloc(_inBufSizeNew);
    if (!_inBuf)
      return E_OUTOFMEMORY;
    _inBufSize = _inBufSizeNew;
  }
  return S_OK;
}


STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *prop, UInt32 size)
{
  RINOK(SResToHRESULT(LzmaDec_Allocate(&_state, prop, size, &g_Alloc)));
  _propsWereSet = true;
  return CreateInputBuffer();
}


void CDecoder::SetOutStreamSizeResume(const UInt64 *outSize)
{
  _outSizeDefined = (outSize != NULL);
  _outSize = 0;
  if (_outSizeDefined)
    _outSize = *outSize;
  _outProcessed = 0;
  _lzmaStatus = LZMA_STATUS_NOT_SPECIFIED;

  LzmaDec_Init(&_state);
}


STDMETHODIMP CDecoder::SetOutStreamSize(const UInt64 *outSize)
{
  _inProcessed = 0;
  _inPos = _inLim = 0;
  SetOutStreamSizeResume(outSize);
  return S_OK;
}


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


STDMETHODIMP CDecoder::GetInStreamProcessedSize(UInt64 *value)
{
  *value = _inProcessed;
  return S_OK;
}


HRESULT CDecoder::CodeSpec(ISequentialInStream *inStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress)
{
  if (!_inBuf || !_propsWereSet)
    return S_FALSE;
  
  const UInt64 startInProgress = _inProcessed;
  SizeT wrPos = _state.dicPos;
  HRESULT readRes = S_OK;

  for (;;)
  {
    if (_inPos == _inLim && readRes == S_OK)
    {
      _inPos = _inLim = 0;
      readRes = inStream->Read(_inBuf, _inBufSize, &_inLim);
    }

    const SizeT dicPos = _state.dicPos;
    SizeT size;
    {
      SizeT next = _state.dicBufSize;
      if (next - wrPos > _outStep)
        next = wrPos + _outStep;
      size = next - dicPos;
    }

    ELzmaFinishMode finishMode = LZMA_FINISH_ANY;
    if (_outSizeDefined)
    {
      const UInt64 rem = _outSize - _outProcessed;
      if (size >= rem)
      {
        size = (SizeT)rem;
        if (FinishStream)
          finishMode = LZMA_FINISH_END;
      }
    }

    SizeT inProcessed = _inLim - _inPos;
    ELzmaStatus status;
    
    SRes res = LzmaDec_DecodeToDic(&_state, dicPos + size, _inBuf + _inPos, &inProcessed, finishMode, &status);

    _lzmaStatus = status;
    _inPos += (UInt32)inProcessed;
    _inProcessed += inProcessed;
    const SizeT outProcessed = _state.dicPos - dicPos;
    _outProcessed += outProcessed;

    // we check for LZMA_STATUS_NEEDS_MORE_INPUT to allow RangeCoder initialization, if (_outSizeDefined && _outSize == 0)
    bool outFinished = (_outSizeDefined && _outProcessed >= _outSize);

    bool needStop = (res != 0
        || (inProcessed == 0 && outProcessed == 0)
        || status == LZMA_STATUS_FINISHED_WITH_MARK
        || (outFinished && status != LZMA_STATUS_NEEDS_MORE_INPUT));

    if (needStop || outProcessed >= size)
    {
      HRESULT res2 = WriteStream(outStream, _state.dic + wrPos, _state.dicPos - wrPos);

      if (_state.dicPos == _state.dicBufSize)
        _state.dicPos = 0;
      wrPos = _state.dicPos;
      
      RINOK(res2);

      if (needStop)
      {
        if (res != 0)
          return S_FALSE;

        if (status == LZMA_STATUS_FINISHED_WITH_MARK)
        {
          if (FinishStream)
            if (_outSizeDefined && _outSize != _outProcessed)
              return S_FALSE;
          return readRes;
        }
        
        if (outFinished && status != LZMA_STATUS_NEEDS_MORE_INPUT)
          if (!FinishStream || status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)
            return readRes;
          
        return S_FALSE;
      }
    }
    
    if (progress)
    {
      const UInt64 inSize = _inProcessed - startInProgress;
      RINOK(progress->SetRatioInfo(&inSize, &_outProcessed));
    }
  }
}


STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
    const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)
{
  if (!_inBuf)
    return E_INVALIDARG;
  SetOutStreamSize(outSize);
  HRESULT res = CodeSpec(inStream, outStream, progress);
  if (res == S_OK)
    if (FinishStream && inSize && *inSize != _inProcessed)
      res = S_FALSE;
  return res;
}


#ifndef NO_READ_FROM_CODER

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


STDMETHODIMP CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)
{
  if (processedSize)
    *processedSize = 0;

  ELzmaFinishMode finishMode = LZMA_FINISH_ANY;
  if (_outSizeDefined)
  {
    const UInt64 rem = _outSize - _outProcessed;
    if (size >= rem)
    {
      size = (UInt32)rem;
      if (FinishStream)
        finishMode = LZMA_FINISH_END;
    }
  }

  HRESULT readRes = S_OK;
  
  for (;;)
  {
    if (_inPos == _inLim && readRes == S_OK)
    {
      _inPos = _inLim = 0;
      readRes = _inStream->Read(_inBuf, _inBufSize, &_inLim);
    }

    SizeT inProcessed = _inLim - _inPos;
    SizeT outProcessed = size;
    ELzmaStatus status;
    
    SRes res = LzmaDec_DecodeToBuf(&_state, (Byte *)data, &outProcessed,
        _inBuf + _inPos, &inProcessed, finishMode, &status);
    
    _lzmaStatus = status;
    _inPos += (UInt32)inProcessed;
    _inProcessed += inProcessed;
    _outProcessed += outProcessed;
    size -= (UInt32)outProcessed;
    data = (Byte *)data + outProcessed;
    if (processedSize)
      *processedSize += (UInt32)outProcessed;

    if (res != 0)
      return S_FALSE;
    
    /*
    if (status == LZMA_STATUS_FINISHED_WITH_MARK)
      return readRes;

    if (size == 0 && status != LZMA_STATUS_NEEDS_MORE_INPUT)
    {
      if (FinishStream
          && _outSizeDefined && _outProcessed >= _outSize
          && status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)
        return S_FALSE;
      return readRes;
    }
    */

    if (inProcessed == 0 && outProcessed == 0)
      return readRes;
  }
}


HRESULT CDecoder::CodeResume(ISequentialOutStream *outStream, const UInt64 *outSize, ICompressProgressInfo *progress)
{
  SetOutStreamSizeResume(outSize);
  return CodeSpec(_inStream, outStream, progress);
}


HRESULT CDecoder::ReadFromInputStream(void *data, UInt32 size, UInt32 *processedSize)
{
  RINOK(CreateInputBuffer());
  
  if (processedSize)
    *processedSize = 0;

  HRESULT readRes = S_OK;

  while (size != 0)
  {
    if (_inPos == _inLim)
    {
      _inPos = _inLim = 0;
      if (readRes == S_OK)
        readRes = _inStream->Read(_inBuf, _inBufSize, &_inLim);
      if (_inLim == 0)
        break;
    }

    UInt32 cur = _inLim - _inPos;
    if (cur > size)
      cur = size;
    memcpy(data, _inBuf + _inPos, cur);
    _inPos += cur;
    _inProcessed += cur;
    size -= cur;
    data = (Byte *)data + cur;
    if (processedSize)
      *processedSize += cur;
  }
  
  return readRes;
}

#endif

}}
