/* 7zDecode.c */

#include <memory.h>

/* BEGIN PHYSFS CHANGE */
#include <string.h>
/* END PHYSFS CHANGE */

#include "7zDecode.h"
#ifdef _SZ_ONE_DIRECTORY
#include "LzmaDecode.h"
#else
#include "../../Compress/Lzma/LzmaDecode.h"
#include "../../Compress/Branch/BranchX86.h"
#include "../../Compress/Branch/BranchX86_2.h"
#endif

#define k_Copy 0
#define k_LZMA 0x30101
#define k_BCJ 0x03030103
#define k_BCJ2 0x0303011B

#ifdef _LZMA_IN_CB

typedef struct _CLzmaInCallbackImp
{
  ILzmaInCallback InCallback;
  ISzInStream *InStream;
  CFileSize Size;
} CLzmaInCallbackImp;

int LzmaReadImp(void *object, const unsigned char **buffer, SizeT *size)
{
  CLzmaInCallbackImp *cb = (CLzmaInCallbackImp *)object;
  size_t processedSize;
  SZ_RESULT res;
  size_t curSize = (1 << 20);
  if (curSize > cb->Size)
    curSize = (size_t)cb->Size;
  *size = 0;
  res = cb->InStream->Read((void *)cb->InStream, (void **)buffer, curSize, &processedSize);
  *size = (SizeT)processedSize;
  if (processedSize > curSize)
    return (int)SZE_FAIL;
  cb->Size -= processedSize;
  if (res == SZ_OK)
    return 0;
  return (int)res;
}

#endif

SZ_RESULT SzDecodeLzma(CCoderInfo *coder, CFileSize inSize,
    #ifdef _LZMA_IN_CB
    ISzInStream *inStream,
    #else
    const Byte *inBuffer,
    #endif
    Byte *outBuffer, size_t outSize, ISzAlloc *allocMain)
{
  #ifdef _LZMA_IN_CB
  CLzmaInCallbackImp lzmaCallback;
  #else
  SizeT inProcessed;
  #endif
  
  CLzmaDecoderState state;  /* it's about 24-80 bytes structure, if int is 32-bit */
  int result;
  SizeT outSizeProcessedLoc;
  
  #ifdef _LZMA_IN_CB
  lzmaCallback.Size = inSize;
  lzmaCallback.InStream = inStream;
  lzmaCallback.InCallback.Read = LzmaReadImp;
  #endif
  
  if (LzmaDecodeProperties(&state.Properties, coder->Properties.Items, 
      (unsigned)coder->Properties.Capacity) != LZMA_RESULT_OK)
    return SZE_FAIL;
  
  state.Probs = (CProb *)allocMain->Alloc(LzmaGetNumProbs(&state.Properties) * sizeof(CProb));
  if (state.Probs == 0)
    return SZE_OUTOFMEMORY;
  
  #ifdef _LZMA_OUT_READ
  if (state.Properties.DictionarySize == 0)
    state.Dictionary = 0;
  else
  {
    state.Dictionary = (unsigned char *)allocMain->Alloc(state.Properties.DictionarySize);
    if (state.Dictionary == 0)
    {
      allocMain->Free(state.Probs);
      return SZE_OUTOFMEMORY;
    }
  }
  LzmaDecoderInit(&state);
  #endif
  
  result = LzmaDecode(&state,
  #ifdef _LZMA_IN_CB
    &lzmaCallback.InCallback,
  #else
    inBuffer, (SizeT)inSize, &inProcessed,
  #endif
    outBuffer, (SizeT)outSize, &outSizeProcessedLoc);
  allocMain->Free(state.Probs);
  #ifdef _LZMA_OUT_READ
  allocMain->Free(state.Dictionary);
  #endif
  if (result == LZMA_RESULT_DATA_ERROR)
    return SZE_DATA_ERROR;
  if (result != LZMA_RESULT_OK)
    return SZE_FAIL;
  return (outSizeProcessedLoc == outSize) ? SZ_OK : SZE_DATA_ERROR;
}

#ifdef _LZMA_IN_CB
SZ_RESULT SzDecodeCopy(CFileSize inSize, ISzInStream *inStream, Byte *outBuffer)
{
  while (inSize > 0)
  {
    void *inBuffer;
    size_t processedSize, curSize = (1 << 18);
    if (curSize > inSize)
      curSize = (size_t)(inSize);
    RINOK(inStream->Read((void *)inStream, (void **)&inBuffer, curSize, &processedSize));
    if (processedSize == 0)
      return SZE_DATA_ERROR;
    if (processedSize > curSize)
      return SZE_FAIL;
    memcpy(outBuffer, inBuffer, processedSize);
    outBuffer += processedSize;
    inSize -= processedSize;
  }
  return SZ_OK;
}
#endif

#define IS_UNSUPPORTED_METHOD(m) ((m) != k_Copy && (m) != k_LZMA)
#define IS_UNSUPPORTED_CODER(c) (IS_UNSUPPORTED_METHOD(c.MethodID) || c.NumInStreams != 1 || c.NumOutStreams != 1)
#define IS_NO_BCJ(c) (c.MethodID != k_BCJ || c.NumInStreams != 1 || c.NumOutStreams != 1)
#define IS_NO_BCJ2(c) (c.MethodID != k_BCJ2 || c.NumInStreams != 4 || c.NumOutStreams != 1)

SZ_RESULT CheckSupportedFolder(const CFolder *f)
{
  if (f->NumCoders < 1 || f->NumCoders > 4)
    return SZE_NOTIMPL;
  if (IS_UNSUPPORTED_CODER(f->Coders[0]))
    return SZE_NOTIMPL;
  if (f->NumCoders == 1)
  {
    if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBindPairs != 0)
      return SZE_NOTIMPL;
    return SZ_OK;
  }
  if (f->NumCoders == 2)
  {
    if (IS_NO_BCJ(f->Coders[1]) ||
        f->NumPackStreams != 1 || f->PackStreams[0] != 0 ||
        f->NumBindPairs != 1 ||
        f->BindPairs[0].InIndex != 1 || f->BindPairs[0].OutIndex != 0)
      return SZE_NOTIMPL;
    return SZ_OK;
  }
  if (f->NumCoders == 4)
  {
    if (IS_UNSUPPORTED_CODER(f->Coders[1]) ||
        IS_UNSUPPORTED_CODER(f->Coders[2]) ||
        IS_NO_BCJ2(f->Coders[3]))
      return SZE_NOTIMPL;
    if (f->NumPackStreams != 4 || 
        f->PackStreams[0] != 2 ||
        f->PackStreams[1] != 6 ||
        f->PackStreams[2] != 1 ||
        f->PackStreams[3] != 0 ||
        f->NumBindPairs != 3 ||
        f->BindPairs[0].InIndex != 5 || f->BindPairs[0].OutIndex != 0 ||
        f->BindPairs[1].InIndex != 4 || f->BindPairs[1].OutIndex != 1 ||
        f->BindPairs[2].InIndex != 3 || f->BindPairs[2].OutIndex != 2)
      return SZE_NOTIMPL;
    return SZ_OK;
  }
  return SZE_NOTIMPL;
}

CFileSize GetSum(const CFileSize *values, UInt32 index)
{
  CFileSize sum = 0;
  UInt32 i;
  for (i = 0; i < index; i++)
    sum += values[i];
  return sum;
}

SZ_RESULT SzDecode2(const CFileSize *packSizes, const CFolder *folder,
    #ifdef _LZMA_IN_CB
    ISzInStream *inStream, CFileSize startPos,
    #else
    const Byte *inBuffer,
    #endif
    Byte *outBuffer, size_t outSize, ISzAlloc *allocMain,
    Byte *tempBuf[])
{
  UInt32 ci;
  size_t tempSizes[3] = { 0, 0, 0};
  size_t tempSize3 = 0;
  Byte *tempBuf3 = 0;

  RINOK(CheckSupportedFolder(folder));

  for (ci = 0; ci < folder->NumCoders; ci++)
  {
    CCoderInfo *coder = &folder->Coders[ci];

    if (coder->MethodID == k_Copy || coder->MethodID == k_LZMA)
    {
      UInt32 si = 0;
      CFileSize offset;
      CFileSize inSize;
      Byte *outBufCur = outBuffer;
      size_t outSizeCur = outSize;
      if (folder->NumCoders == 4)
      {
        UInt32 indices[] = { 3, 2, 0 };
        CFileSize unpackSize = folder->UnPackSizes[ci];
        si = indices[ci];
        if (ci < 2)
        {
          Byte *temp;
          outSizeCur = (size_t)unpackSize;
          if (outSizeCur != unpackSize)
            return SZE_OUTOFMEMORY;
          temp = (Byte *)allocMain->Alloc(outSizeCur);
          if (temp == 0 && outSizeCur != 0)
            return SZE_OUTOFMEMORY;
          outBufCur = tempBuf[1 - ci] = temp;
          tempSizes[1 - ci] = outSizeCur;
        }
        else if (ci == 2)
        {
          if (unpackSize > outSize)
            return SZE_OUTOFMEMORY;
          tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize);
          tempSize3 = outSizeCur = (size_t)unpackSize;
        }
        else
          return SZE_NOTIMPL;
      }
      offset = GetSum(packSizes, si);
      inSize = packSizes[si];
      #ifdef _LZMA_IN_CB
      RINOK(inStream->Seek(inStream, startPos + offset));
      #endif

      if (coder->MethodID == k_Copy)
      {
        if (inSize != outSizeCur)
          return SZE_DATA_ERROR;
        
        #ifdef _LZMA_IN_CB
        RINOK(SzDecodeCopy(inSize, inStream, outBufCur));
        #else
        memcpy(outBufCur, inBuffer + (size_t)offset, (size_t)inSize);
        #endif
      }
      else
      {
        SZ_RESULT res = SzDecodeLzma(coder, inSize,
            #ifdef _LZMA_IN_CB
            inStream,
            #else
            inBuffer + (size_t)offset,
            #endif
            outBufCur, outSizeCur, allocMain);
        RINOK(res)
      }
    }
    else if (coder->MethodID == k_BCJ)
    {
      UInt32 state;
      if (ci != 1)
        return SZE_NOTIMPL;
      x86_Convert_Init(state);
      x86_Convert(outBuffer, outSize, 0, &state, 0);
    }
    else if (coder->MethodID == k_BCJ2)
    {
      CFileSize offset = GetSum(packSizes, 1);
      CFileSize s3Size = packSizes[1];
      SZ_RESULT res;
      if (ci != 3)
        return SZE_NOTIMPL;

      #ifdef _LZMA_IN_CB
      RINOK(inStream->Seek(inStream, startPos + offset));
      tempSizes[2] = (size_t)s3Size;
      if (tempSizes[2] != s3Size)
        return SZE_OUTOFMEMORY;
      tempBuf[2] = (Byte *)allocMain->Alloc(tempSizes[2]);
      if (tempBuf[2] == 0 && tempSizes[2] != 0)
        return SZE_OUTOFMEMORY;
      res = SzDecodeCopy(s3Size, inStream, tempBuf[2]);
      RINOK(res)
      #endif

      res = x86_2_Decode(
          tempBuf3, tempSize3, 
          tempBuf[0], tempSizes[0], 
          tempBuf[1], tempSizes[1], 
          #ifdef _LZMA_IN_CB
          tempBuf[2], tempSizes[2], 
          #else
          inBuffer + (size_t)offset, (size_t)s3Size, 
          #endif
          outBuffer, outSize);
      RINOK(res)
    }
    else 
      return SZE_NOTIMPL;
  }
  return SZ_OK;
}

SZ_RESULT SzDecode(const CFileSize *packSizes, const CFolder *folder,
    #ifdef _LZMA_IN_CB
    ISzInStream *inStream, CFileSize startPos,
    #else
    const Byte *inBuffer,
    #endif
    Byte *outBuffer, size_t outSize, ISzAlloc *allocMain)
{
  Byte *tempBuf[3] = { 0, 0, 0};
  int i;
  SZ_RESULT res = SzDecode2(packSizes, folder,
      #ifdef _LZMA_IN_CB
      inStream, startPos,
      #else
      inBuffer,
      #endif
      outBuffer, outSize, allocMain, tempBuf);
  for (i = 0; i < 3; i++)
    allocMain->Free(tempBuf[i]);
  return res;
}
