// OpenArchive.cpp

#include "StdAfx.h"

#include "Common/Wildcard.h"

#include "Windows/FileDir.h"
#include "Windows/PropVariant.h"

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

#include "DefaultName.h"
#include "OpenArchive.h"

using namespace NWindows;

// Static-SFX (for Linux) can be big.
const UInt64 kMaxCheckStartPosition = 1 << 22;

HRESULT GetArchiveItemBoolProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
{
  NCOM::CPropVariant prop;
  result = false;
  RINOK(archive->GetProperty(index, propID, &prop));
  if (prop.vt == VT_BOOL)
    result = VARIANT_BOOLToBool(prop.boolVal);
  else if (prop.vt != VT_EMPTY)
    return E_FAIL;
  return S_OK;
}

HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
{
  return GetArchiveItemBoolProp(archive, index, kpidIsDir, result);
}

HRESULT CArc::GetItemPath(UInt32 index, UString &result) const
{
  {
    NCOM::CPropVariant prop;
    RINOK(Archive->GetProperty(index, kpidPath, &prop));
    if (prop.vt == VT_BSTR)
      result = prop.bstrVal;
    else if (prop.vt == VT_EMPTY)
      result.Empty();
    else
      return E_FAIL;
  }
  if (result.IsEmpty())
  {
    result = DefaultName;
    NCOM::CPropVariant prop;
    RINOK(Archive->GetProperty(index, kpidExtension, &prop));
    if (prop.vt == VT_BSTR)
    {
      result += L'.';
      result += prop.bstrVal;
    }
    else if (prop.vt != VT_EMPTY)
      return E_FAIL;
  }
  return S_OK;
}

HRESULT CArc::GetItemMTime(UInt32 index, FILETIME &ft, bool &defined) const
{
  NCOM::CPropVariant prop;
  defined = false;
  ft.dwHighDateTime = ft.dwLowDateTime = 0;
  RINOK(Archive->GetProperty(index, kpidMTime, &prop));
  if (prop.vt == VT_FILETIME)
  {
    ft = prop.filetime;
    defined = true;
  }
  else if (prop.vt != VT_EMPTY)
    return E_FAIL;
  else if (MTimeDefined)
  {
    ft = MTime;
    defined = true;
  }
  return S_OK;
}

#ifndef _SFX
static inline bool TestSignature(const Byte *p1, const Byte *p2, size_t size)
{
  for (size_t i = 0; i < size; i++)
    if (p1[i] != p2[i])
      return false;
  return true;
}
#endif

#ifdef UNDER_CE
static const int kNumHashBytes = 1;
#define HASH_VAL(buf, pos) ((buf)[pos])
#else
static const int kNumHashBytes = 2;
#define HASH_VAL(buf, pos) ((buf)[pos] | ((UInt32)(buf)[pos + 1] << 8))
#endif


HRESULT CArc::OpenStream(
    CCodecs *codecs,
    int formatIndex,
    IInStream *stream,
    ISequentialInStream *seqStream,
    IArchiveOpenCallback *callback)
{
  Archive.Release();
  ErrorMessage.Empty();
  const UString fileName = ExtractFileNameFromPath(Path);
  UString extension;
  {
    int dotPos = fileName.ReverseFind(L'.');
    if (dotPos >= 0)
      extension = fileName.Mid(dotPos + 1);
  }
  CIntVector orderIndices;
  if (formatIndex >= 0)
    orderIndices.Add(formatIndex);
  else
  {

  int i;
  int numFinded = 0;
  for (i = 0; i < codecs->Formats.Size(); i++)
    if (codecs->Formats[i].FindExtension(extension) >= 0)
      orderIndices.Insert(numFinded++, i);
    else
      orderIndices.Add(i);
  
  if (!stream)
  {
    if (numFinded != 1)
      return E_NOTIMPL;
    orderIndices.DeleteFrom(1);
  }

  #ifndef _SFX
  if (orderIndices.Size() >= 2 && (numFinded == 0 || extension.CompareNoCase(L"exe") == 0))
  {
    CIntVector orderIndices2;
    CByteBuffer byteBuffer;
    const size_t kBufferSize = (1 << 21);
    byteBuffer.SetCapacity(kBufferSize);
    RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
    size_t processedSize = kBufferSize;
    RINOK(ReadStream(stream, byteBuffer, &processedSize));
    if (processedSize == 0)
      return S_FALSE;

    const Byte *buf = byteBuffer;
    CByteBuffer hashBuffer;
    const UInt32 kNumVals = 1 << (kNumHashBytes * 8);
    hashBuffer.SetCapacity(kNumVals);
    Byte *hash = hashBuffer;
    memset(hash, 0xFF, kNumVals);
    Byte prevs[256];
    if (orderIndices.Size() >= 256)
      return S_FALSE;
    int i;
    for (i = 0; i < orderIndices.Size(); i++)
    {
      const CArcInfoEx &ai = codecs->Formats[orderIndices[i]];
      const CByteBuffer &sig = ai.StartSignature;
      if (sig.GetCapacity() < kNumHashBytes)
        continue;
      UInt32 v = HASH_VAL(sig, 0);
      prevs[i] = hash[v];
      hash[v] = (Byte)i;
    }

    processedSize -= (kNumHashBytes - 1);
    for (UInt32 pos = 0; pos < processedSize; pos++)
    {
      for (; pos < processedSize && hash[HASH_VAL(buf, pos)] == 0xFF; pos++) {}
      if (pos == processedSize)
        break;
      UInt32 v = HASH_VAL(buf, pos);
      Byte *ptr = &hash[v];
      int i = *ptr;
      do
      {
        int index = orderIndices[i];
        const CArcInfoEx &ai = codecs->Formats[index];
        const CByteBuffer &sig = ai.StartSignature;
        if (sig.GetCapacity() != 0 && pos + sig.GetCapacity() <= processedSize + (kNumHashBytes - 1) &&
            TestSignature(buf + pos, sig, sig.GetCapacity()))
        {
          orderIndices2.Add(index);
          orderIndices[i] = 0xFF;
          *ptr = prevs[i];
        }
        else
          ptr = &prevs[i];
        i = *ptr;
      }
      while (i != 0xFF);
    }
    
    for (i = 0; i < orderIndices.Size(); i++)
    {
      int val = orderIndices[i];
      if (val != 0xFF)
        orderIndices2.Add(val);
    }
    orderIndices = orderIndices2;
  }
  else if (extension == L"000" || extension == L"001")
  {
    CByteBuffer byteBuffer;
    const size_t kBufferSize = (1 << 10);
    byteBuffer.SetCapacity(kBufferSize);
    Byte *buffer = byteBuffer;
    RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
    size_t processedSize = kBufferSize;
    RINOK(ReadStream(stream, buffer, &processedSize));
    if (processedSize >= 16)
    {
      Byte kRarHeader[] = {0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00};
      if (TestSignature(buffer, kRarHeader, 7) && buffer[9] == 0x73 && (buffer[10] & 1) != 0)
      {
        for (int i = 0; i < orderIndices.Size(); i++)
        {
          int index = orderIndices[i];
          const CArcInfoEx &ai = codecs->Formats[index];
          if (ai.Name.CompareNoCase(L"rar") != 0)
            continue;
          orderIndices.Delete(i--);
          orderIndices.Insert(0, index);
          break;
        }
      }
    }
  }
  if (orderIndices.Size() >= 2)
  {
    int isoIndex = codecs->FindFormatForArchiveType(L"iso");
    int udfIndex = codecs->FindFormatForArchiveType(L"udf");
    int iIso = -1;
    int iUdf = -1;
    for (int i = 0; i < orderIndices.Size(); i++)
    {
      if (orderIndices[i] == isoIndex) iIso = i;
      if (orderIndices[i] == udfIndex) iUdf = i;
    }
    if (iUdf > iIso && iIso >= 0)
    {
      orderIndices[iUdf] = isoIndex;
      orderIndices[iIso] = udfIndex;
    }
  }

  #endif
  }

  for (int i = 0; i < orderIndices.Size(); i++)
  {
    if (stream)
    {
      RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
    }
    CMyComPtr<IInArchive> archive;

    FormatIndex = orderIndices[i];
    RINOK(codecs->CreateInArchive(FormatIndex, archive));
    if (!archive)
      continue;

    #ifdef EXTERNAL_CODECS
    {
      CMyComPtr<ISetCompressCodecsInfo> setCompressCodecsInfo;
      archive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo);
      if (setCompressCodecsInfo)
      {
        RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(codecs));
      }
    }
    #endif

    // OutputDebugStringW(codecs->Formats[FormatIndex].Name);

    HRESULT result;
    if (stream)
      result = archive->Open(stream, &kMaxCheckStartPosition, callback);
    else
    {
      CMyComPtr<IArchiveOpenSeq> openSeq;
      archive.QueryInterface(IID_IArchiveOpenSeq, (void **)&openSeq);
      if (!openSeq)
        return E_NOTIMPL;
      result = openSeq->OpenSeq(seqStream);
    }

    if (result == S_FALSE)
      continue;
    RINOK(result);

    {
      NCOM::CPropVariant prop;
      archive->GetArchiveProperty(kpidError, &prop);
      if (prop.vt != VT_EMPTY)
        ErrorMessage = (prop.vt == VT_BSTR) ? prop.bstrVal : L"Unknown error";
    }
    
    Archive = archive;
    const CArcInfoEx &format = codecs->Formats[FormatIndex];
    if (format.Exts.Size() == 0)
      DefaultName = GetDefaultName2(fileName, L"", L"");
    else
    {
      int subExtIndex = format.FindExtension(extension);
      if (subExtIndex < 0)
        subExtIndex = 0;
      const CArcExtInfo &extInfo = format.Exts[subExtIndex];
      DefaultName = GetDefaultName2(fileName, extInfo.Ext, extInfo.AddExt);
    }
    return S_OK;
  }
  return S_FALSE;
}

HRESULT CArc::OpenStreamOrFile(
    CCodecs *codecs,
    int formatIndex,
    bool stdInMode,
    IInStream *stream,
    IArchiveOpenCallback *callback)
{
  CMyComPtr<IInStream> fileStream;
  CMyComPtr<ISequentialInStream> seqStream;
  if (stdInMode)
    seqStream = new CStdInFileStream;
  else if (!stream)
  {
    CInFileStream *fileStreamSpec = new CInFileStream(true);
    fileStream = fileStreamSpec;
    if (!fileStreamSpec->Open(Path))
      return GetLastError();
    stream = fileStream;
  }

  /*
  if (callback)
  {
    UInt64 fileSize;
    RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize));
    RINOK(callback->SetTotal(NULL, &fileSize))
  }
  */

  return OpenStream(codecs, formatIndex, stream, seqStream, callback);
}

HRESULT CArchiveLink::Close()
{
  for (int i = Arcs.Size() - 1;  i >= 0; i--)
  {
    RINOK(Arcs[i].Archive->Close());
  }
  IsOpen = false;
  return S_OK;
}

void CArchiveLink::Release()
{
  while (!Arcs.IsEmpty())
    Arcs.DeleteBack();
}

HRESULT CArchiveLink::Open(
    CCodecs *codecs,
    const CIntVector &formatIndices,
    bool stdInMode,
    IInStream *stream,
    const UString &filePath,
    IArchiveOpenCallback *callback)
{
  Release();
  if (formatIndices.Size() >= 32)
    return E_NOTIMPL;
  
  HRESULT resSpec;

  for (;;)
  {
    resSpec = S_OK;
    int formatIndex = -1;
    if (formatIndices.Size() >= 1)
    {
      if (Arcs.Size() >= formatIndices.Size())
        break;
      formatIndex = formatIndices[formatIndices.Size() - Arcs.Size() - 1];
    }
    else if (Arcs.Size() >= 32)
      break;

    if (Arcs.IsEmpty())
    {
      CArc arc;
      arc.Path = filePath;
      arc.SubfileIndex = (UInt32)(Int32)-1;
      RINOK(arc.OpenStreamOrFile(codecs, formatIndex, stdInMode, stream, callback));
      Arcs.Add(arc);
      continue;
    }
    
    const CArc &arc = Arcs.Back();
    
    resSpec = (formatIndices.Size() == 0 ? S_OK : E_NOTIMPL);
    
    UInt32 mainSubfile;
    {
      NCOM::CPropVariant prop;
      RINOK(arc.Archive->GetArchiveProperty(kpidMainSubfile, &prop));
      if (prop.vt == VT_UI4)
        mainSubfile = prop.ulVal;
      else
        break;
      UInt32 numItems;
      RINOK(arc.Archive->GetNumberOfItems(&numItems));
      if (mainSubfile >= numItems)
        break;
    }

  
    CMyComPtr<IInArchiveGetStream> getStream;
    if (arc.Archive->QueryInterface(IID_IInArchiveGetStream, (void **)&getStream) != S_OK || !getStream)
      break;
    
    CMyComPtr<ISequentialInStream> subSeqStream;
    if (getStream->GetStream(mainSubfile, &subSeqStream) != S_OK || !subSeqStream)
      break;
    
    CMyComPtr<IInStream> subStream;
    if (subSeqStream.QueryInterface(IID_IInStream, &subStream) != S_OK || !subStream)
      break;
    
    CArc arc2;
    RINOK(arc.GetItemPath(mainSubfile, arc2.Path));
    
    CMyComPtr<IArchiveOpenSetSubArchiveName> setSubArchiveName;
    callback->QueryInterface(IID_IArchiveOpenSetSubArchiveName, (void **)&setSubArchiveName);
    if (setSubArchiveName)
      setSubArchiveName->SetSubArchiveName(arc2.Path);
    
    arc2.SubfileIndex = mainSubfile;
    HRESULT result = arc2.OpenStream(codecs, formatIndex, subStream, NULL, callback);
    resSpec = (formatIndices.Size() == 0 ? S_OK : S_FALSE);
    if (result == S_FALSE)
      break;
    RINOK(result);
    RINOK(arc.GetItemMTime(mainSubfile, arc2.MTime, arc2.MTimeDefined));
    Arcs.Add(arc2);
  }
  IsOpen = !Arcs.IsEmpty();
  return S_OK;
}

static void SetCallback(const UString &filePath,
    IOpenCallbackUI *callbackUI,
    IArchiveOpenCallback *reOpenCallback,
    CMyComPtr<IArchiveOpenCallback> &callback)
{
  COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
  callback = openCallbackSpec;
  openCallbackSpec->Callback = callbackUI;
  openCallbackSpec->ReOpenCallback = reOpenCallback;

  UString fullName;
  int fileNamePartStartIndex;
  NFile::NDirectory::MyGetFullPathName(filePath, fullName, fileNamePartStartIndex);
  openCallbackSpec->Init(
      fullName.Left(fileNamePartStartIndex),
      fullName.Mid(fileNamePartStartIndex));
}

HRESULT CArchiveLink::Open2(CCodecs *codecs,
    const CIntVector &formatIndices,
    bool stdInMode,
    IInStream *stream,
    const UString &filePath,
    IOpenCallbackUI *callbackUI)
{
  VolumesSize = 0;
  COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
  CMyComPtr<IArchiveOpenCallback> callback = openCallbackSpec;
  openCallbackSpec->Callback = callbackUI;

  UString fullName, prefix, name;
  if (!stream && !stdInMode)
  {
    int fileNamePartStartIndex;
    if (!NFile::NDirectory::MyGetFullPathName(filePath, fullName, fileNamePartStartIndex))
      return GetLastError();
    prefix = fullName.Left(fileNamePartStartIndex);
    name = fullName.Mid(fileNamePartStartIndex);
    openCallbackSpec->Init(prefix, name);
  }
  else
  {
    openCallbackSpec->SetSubArchiveName(filePath);
  }

  RINOK(Open(codecs, formatIndices, stdInMode, stream, filePath, callback));
  VolumePaths.Add(prefix + name);
  for (int i = 0; i < openCallbackSpec->FileNames.Size(); i++)
    VolumePaths.Add(prefix + openCallbackSpec->FileNames[i]);
  VolumesSize = openCallbackSpec->TotalSize;
  return S_OK;
}

HRESULT CArchiveLink::ReOpen(CCodecs *codecs, const UString &filePath,
    IArchiveOpenCallback *callback)
{
  if (Arcs.Size() > 1)
    return E_NOTIMPL;

  if (Arcs.Size() == 0)
    return Open2(codecs, CIntVector(), false, NULL, filePath, 0);

  CMyComPtr<IArchiveOpenCallback> openCallbackNew;
  SetCallback(filePath, NULL, callback, openCallbackNew);

  CInFileStream *fileStreamSpec = new CInFileStream(true);
  CMyComPtr<IInStream> stream(fileStreamSpec);
  if (!fileStreamSpec->Open(filePath))
    return GetLastError();
  HRESULT res = GetArchive()->Open(stream, &kMaxCheckStartPosition, callback);
  IsOpen = (res == S_OK);
  return res;
}
