#include "rar.hpp"

QuickOpen::QuickOpen()
{
  Buf=NULL;
  Init(NULL,false);
}


QuickOpen::~QuickOpen()
{
  Close();
  delete[] Buf;
}


void QuickOpen::Init(Archive *Arc,bool WriteMode)
{
  if (Arc!=NULL) // Unless called from constructor.
    Close();

  QuickOpen::Arc=Arc;
  QuickOpen::WriteMode=WriteMode;

  ListStart=NULL;
  ListEnd=NULL;

  if (Buf==NULL)
    Buf=new byte[MaxBufSize];

  CurBufSize=0; // Current size of buffered data in write mode.

  Loaded=false;
}


void QuickOpen::Close()
{
  QuickOpenItem *Item=ListStart;
  while (Item!=NULL)
  {
    QuickOpenItem *Next=Item->Next;
    delete[] Item->Header;
    delete Item;
    Item=Next;
  }
}














void QuickOpen::Load(uint64 BlockPos)
{
  if (!Loaded)
  {
    // If loading for the first time, perform additional initialization.
    SeekPos=Arc->Tell();
    UnsyncSeekPos=false;

    int64 SavePos=SeekPos;
    Arc->Seek(BlockPos,SEEK_SET);

    // If BlockPos points to original main header, we'll have the infinite
    // recursion, because ReadHeader() for main header will attempt to load
    // QOpen and call QuickOpen::Load again. If BlockPos points to long chain
    // of other main headers, we'll have multiple recursive calls of this
    // function wasting resources. So we prohibit QOpen temporarily to
    // prevent this. ReadHeader() calls QOpen.Init and sets MainHead Locator
    // and QOpenOffset fields, so we cannot use them to prohibit QOpen.
    Arc->SetProhibitQOpen(true);
    size_t ReadSize=Arc->ReadHeader();
    Arc->SetProhibitQOpen(false);

    if (ReadSize==0 || Arc->GetHeaderType()!=HEAD_SERVICE ||
        !Arc->SubHead.CmpName(SUBHEAD_TYPE_QOPEN))
    {
      Arc->Seek(SavePos,SEEK_SET);
      return;
    }
    QOHeaderPos=Arc->CurBlockPos;
    RawDataStart=Arc->Tell();
    RawDataSize=Arc->SubHead.UnpSize;
    Arc->Seek(SavePos,SEEK_SET);

    Loaded=true; // Set only after all file processing calls like Tell, Seek, ReadHeader.
  }

  if (Arc->SubHead.Encrypted)
  {
    CommandData *Cmd=Arc->GetCommandData();
#ifndef RAR_NOCRYPT
    if (Cmd->Password.IsSet())
      Crypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,Arc->SubHead.Salt,
                         Arc->SubHead.InitV,Arc->SubHead.Lg2Count,
                         Arc->SubHead.HashKey,Arc->SubHead.PswCheck);
    else
#endif
    {
      Loaded=false;
      return;
    }
  }

  RawDataPos=0;
  ReadBufSize=0;
  ReadBufPos=0;
  LastReadHeader.Reset();
  LastReadHeaderPos=0;

  ReadBuffer();
}


bool QuickOpen::Read(void *Data,size_t Size,size_t &Result)
{
  if (!Loaded)
    return false;
  // Find next suitable cached block.
  while (LastReadHeaderPos+LastReadHeader.Size()<=SeekPos)
    if (!ReadNext())
      break;
  if (!Loaded)
  {
    // If something wrong happened, let's set the correct file pointer
    // and stop further quick open processing.
    if (UnsyncSeekPos)
      Arc->File::Seek(SeekPos,SEEK_SET);
    return false;
  }

  if (SeekPos>=LastReadHeaderPos && SeekPos+Size<=LastReadHeaderPos+LastReadHeader.Size())
  {
    memcpy(Data,LastReadHeader+size_t(SeekPos-LastReadHeaderPos),Size);
    Result=Size;
    SeekPos+=Size;
    UnsyncSeekPos=true;
  }
  else
  {
    if (UnsyncSeekPos)
    {
      Arc->File::Seek(SeekPos,SEEK_SET);
      UnsyncSeekPos=false;
    }
    int ReadSize=Arc->File::Read(Data,Size);
    if (ReadSize<0)
    {
      Loaded=false;
      return false;
    }
    Result=ReadSize;
    SeekPos+=ReadSize;
  }
  
  return true;
}


bool QuickOpen::Seek(int64 Offset,int Method)
{
  if (!Loaded)
    return false;

  // Normally we process an archive sequentially from beginning to end,
  // so we read quick open data sequentially. But some operations like
  // archive updating involve several passes. So if we detect that file
  // pointer is moved back, we reload quick open data from beginning.
  if (Method==SEEK_SET && (uint64)Offset<SeekPos && (uint64)Offset<LastReadHeaderPos)
    Load(QOHeaderPos);

  if (Method==SEEK_SET)
    SeekPos=Offset;
  if (Method==SEEK_CUR)
    SeekPos+=Offset;
  UnsyncSeekPos=true;

  if (Method==SEEK_END)
  {
    Arc->File::Seek(Offset,SEEK_END);
    SeekPos=Arc->File::Tell();
    UnsyncSeekPos=false;
  }
  return true;
}


bool QuickOpen::Tell(int64 *Pos)
{
  if (!Loaded)
    return false;
  *Pos=SeekPos;
  return true;
}


uint QuickOpen::ReadBuffer()
{
  int64 SavePos=Arc->Tell();
  Arc->File::Seek(RawDataStart+RawDataPos,SEEK_SET);
  size_t SizeToRead=(size_t)Min(RawDataSize-RawDataPos,MaxBufSize-ReadBufSize);
  if (Arc->SubHead.Encrypted)
    SizeToRead &= ~CRYPT_BLOCK_MASK;
  int ReadSize=0;
  if (SizeToRead!=0)
  {
    ReadSize=Arc->File::Read(Buf+ReadBufSize,SizeToRead);
    if (ReadSize<=0)
      ReadSize=0;
    else
    {
#ifndef RAR_NOCRYPT
      if (Arc->SubHead.Encrypted)
        Crypt.DecryptBlock(Buf+ReadBufSize,ReadSize & ~CRYPT_BLOCK_MASK);
#endif
      RawDataPos+=ReadSize;
      ReadBufSize+=ReadSize;
    }
  }
  Arc->Seek(SavePos,SEEK_SET);
  return ReadSize;
}


// Fill RawRead object from buffer.
bool QuickOpen::ReadRaw(RawRead &Raw)
{
  if (MaxBufSize-ReadBufPos<0x100) // We are close to end of buffer.
  {
    // Ensure that we have enough data to read CRC and header size.
    size_t DataLeft=ReadBufSize-ReadBufPos;
    memcpy(Buf,Buf+ReadBufPos,DataLeft);
    ReadBufPos=0;
    ReadBufSize=DataLeft;
    ReadBuffer();
  }
  const size_t FirstReadSize=7;
  if (ReadBufPos+FirstReadSize>ReadBufSize)
    return false;
  Raw.Read(Buf+ReadBufPos,FirstReadSize);
  ReadBufPos+=FirstReadSize;

  uint SavedCRC=Raw.Get4();
  uint SizeBytes=Raw.GetVSize(4);
  uint64 BlockSize=Raw.GetV();
  int SizeToRead=int(BlockSize);
  SizeToRead-=FirstReadSize-SizeBytes-4; // Adjust overread size bytes if any.
  if (SizeToRead<0 || SizeBytes==0 || BlockSize==0)
  {
    Loaded=false; // Invalid data.
    return false;
  }

  // If rest of block data crosses Buf boundary, read it in loop.
  while (SizeToRead>0)
  {
    size_t DataLeft=ReadBufSize-ReadBufPos;
    size_t CurSizeToRead=Min(DataLeft,(size_t)SizeToRead);
    Raw.Read(Buf+ReadBufPos,CurSizeToRead);
    ReadBufPos+=CurSizeToRead;
    SizeToRead-=int(CurSizeToRead);
    if (SizeToRead>0) // We read the entire buffer and still need more data.
    {
      ReadBufPos=0;
      ReadBufSize=0;
      if (ReadBuffer()==0)
        return false;
    }
  }

  return SavedCRC==Raw.GetCRC50();
}


// Read next cached header.
bool QuickOpen::ReadNext()
{
  RawRead Raw(NULL);
  if (!ReadRaw(Raw)) // Read internal quick open header preceding stored block.
    return false;
  uint Flags=(uint)Raw.GetV();
  uint64 Offset=Raw.GetV();
  size_t HeaderSize=(size_t)Raw.GetV();
  if (HeaderSize>MAX_HEADER_SIZE_RAR5)
    return false;
  LastReadHeader.Alloc(HeaderSize);
  Raw.GetB(&LastReadHeader[0],HeaderSize);
  // Calculate the absolute position as offset from quick open service header.
  LastReadHeaderPos=QOHeaderPos-Offset;
  return true;
}
