#include "rar.hpp"

static void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare,bool DisableNames);
static void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize);
static void ListOldSubHeader(Archive &Arc);
static void ListNewSubHeader(CommandData *Cmd,Archive &Arc);

void ListArchive(CommandData *Cmd)
{
  int64 SumPackSize=0,SumUnpSize=0;
  uint ArcCount=0,SumFileCount=0;
  bool Technical=(Cmd->Command[1]=='T');
  bool ShowService=Technical && Cmd->Command[2]=='A';
  bool Bare=(Cmd->Command[1]=='B');
  bool Verbose=(Cmd->Command[0]=='V');

  std::wstring ArcName;
  while (Cmd->GetArcName(ArcName))
  {
    if (Cmd->ManualPassword)
      Cmd->Password.Clean(); // Clean user entered password before processing next archive.

    Archive Arc(Cmd);
    if (!Arc.WOpen(ArcName))
      continue;
    bool FileMatched=true;
    while (true)
    {
      int64 TotalPackSize=0,TotalUnpSize=0;
      uint FileCount=0;
      if (Arc.IsArchive(true))
      {
        bool TitleShown=false;
        if (!Bare)
        {
          Arc.ViewComment();
          mprintf(L"\n%s: %s",St(MListArchive),Arc.FileName.c_str());

          mprintf(L"\n%s: ",St(MListDetails));
          const wchar *Fmt=Arc.Format==RARFMT14 ? L"RAR 1.4":(Arc.Format==RARFMT15 ? L"RAR 4":L"RAR 5");
          mprintf(L"%s", Fmt);
          if (Arc.Solid)
            mprintf(L", %s", St(MListSolid));
          if (Arc.SFXSize>0)
            mprintf(L", %s", St(MListSFX));
          if (Arc.Volume)
            if (Arc.Format==RARFMT50)
            {
              // RAR 5.0 archives store the volume number in main header,
              // so it is already available now.
              mprintf(L", ");
              mprintf(St(MVolumeNumber),Arc.VolNumber+1);
            }
            else
              mprintf(L", %s", St(MListVolume));
          if (Arc.Protected)
            mprintf(L", %s", St(MListRR));
          if (Arc.Locked)
            mprintf(L", %s", St(MListLock));
          if (Arc.Encrypted)
            mprintf(L", %s", St(MListEncHead));

          if (!Arc.MainHead.OrigName.empty())
            mprintf(L"\n%s: %s",St(MOrigName),Arc.MainHead.OrigName.c_str());
          if (Arc.MainHead.OrigTime.IsSet())
          {
            wchar DateStr[50];
            Arc.MainHead.OrigTime.GetText(DateStr,ASIZE(DateStr),Technical);
            mprintf(L"\n%s: %s",St(MOriginalTime),DateStr);
          }

          mprintf(L"\n");
        }

        wchar VolNumText[50];
        *VolNumText=0;
        while (Arc.ReadHeader()>0)
        {
          Wait(); // Allow quit listing with Ctrl+C.
          HEADER_TYPE HeaderType=Arc.GetHeaderType();
          if (HeaderType==HEAD_ENDARC)
          {
#ifndef SFX_MODULE
            // Only RAR 1.5 archives store the volume number in end record.
            if (Arc.EndArcHead.StoreVolNumber && Arc.Format==RARFMT15)
              swprintf(VolNumText,ASIZE(VolNumText),L"%.10ls %u",St(MListVolume),Arc.VolNumber+1);
#endif
            if (Technical && ShowService)
            {
              mprintf(L"\n%12ls: %ls",St(MListService),L"EOF");
              if (*VolNumText!=0)
                mprintf(L"\n%12ls: %ls",St(MListFlags),VolNumText);
              mprintf(L"\n");
            }
            break;
          }
          switch(HeaderType)
          {
            case HEAD_FILE:
              FileMatched=Cmd->IsProcessFile(Arc.FileHead,NULL,MATCH_WILDSUBPATH,0,NULL)!=0;
              if (FileMatched)
              {
                ListFileHeader(Arc,Arc.FileHead,TitleShown,Verbose,Technical,Bare,Cmd->DisableNames);
                if (!Arc.FileHead.SplitBefore)
                {
                  TotalUnpSize+=Arc.FileHead.UnpSize;
                  FileCount++;
                }
                TotalPackSize+=Arc.FileHead.PackSize;
              }
              break;
            case HEAD_SERVICE:
              if (FileMatched && !Bare)
              {
                if (Technical && ShowService)
                  ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false,Cmd->DisableNames);
              }
              break;
          }
          Arc.SeekToNext();
        }
        if (!Bare && !Technical)
          if (TitleShown)
          {
            wchar UnpSizeText[20];
            itoa(TotalUnpSize,UnpSizeText,ASIZE(UnpSizeText));
        
            wchar PackSizeText[20];
            itoa(TotalPackSize,PackSizeText,ASIZE(PackSizeText));
        
            if (Verbose)
            {
              mprintf(L"\n----------- ---------  -------- ----- ---------- -----  --------  ----");
              mprintf(L"\n%21ls %9ls %3d%%  %-27ls %u",UnpSizeText,
                      PackSizeText,ToPercentUnlim(TotalPackSize,TotalUnpSize),
                      VolNumText,FileCount);
            }
            else
            {
              mprintf(L"\n----------- ---------  ---------- -----  ----");
              mprintf(L"\n%21ls  %-16ls  %u",UnpSizeText,VolNumText,FileCount);
            }

            SumFileCount+=FileCount;
            SumUnpSize+=TotalUnpSize;
            SumPackSize+=TotalPackSize;
            mprintf(L"\n");
          }
          else
            mprintf(St(MListNoFiles));

        ArcCount++;

#ifndef NOVOLUME
        if (Cmd->VolSize==VOLSIZE_AUTO && (Arc.FileHead.SplitAfter ||
            Arc.GetHeaderType()==HEAD_ENDARC && Arc.EndArcHead.NextVolume) &&
            MergeArchive(Arc,NULL,false,Cmd->Command[0]))
          Arc.Seek(0,SEEK_SET);
        else
#endif
          break;
      }
      else
      {
        if (Cmd->ArcNames.ItemsCount()<2 && !Bare)
          mprintf(St(MNotRAR),Arc.FileName.c_str());
        break;
      }
    }
  }

  // Clean user entered password. Not really required, just for extra safety.
  if (Cmd->ManualPassword)
    Cmd->Password.Clean();

  if (ArcCount>1 && !Bare && !Technical)
  {
    wchar UnpSizeText[20],PackSizeText[20];
    itoa(SumUnpSize,UnpSizeText,ASIZE(UnpSizeText));
    itoa(SumPackSize,PackSizeText,ASIZE(PackSizeText));

    if (Verbose)
      mprintf(L"%21ls %9ls %3d%% %28ls %u",UnpSizeText,PackSizeText,
              ToPercentUnlim(SumPackSize,SumUnpSize),L"",SumFileCount);
    else
      mprintf(L"%21ls %18s %lu",UnpSizeText,L"",SumFileCount);
  }
}


enum LISTCOL_TYPE {
  LCOL_NAME,LCOL_ATTR,LCOL_SIZE,LCOL_PACKED,LCOL_RATIO,LCOL_CSUM,LCOL_ENCR
};


void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare,bool DisableNames)
{
  if (!TitleShown && !Technical && !Bare)
  {
    if (Verbose)
    {
      mprintf(L"\n%ls",St(MListTitleV));
      if (!DisableNames)
        mprintf(L"\n----------- ---------  -------- ----- ---------- -----  --------  ----");
    }
    else
    {
      mprintf(L"\n%ls",St(MListTitleL));
      if (!DisableNames)
        mprintf(L"\n----------- ---------  ---------- -----  ----");
    }
    // Must be set even in DisableNames mode to suppress "0 files" output
    // unless no files are matched.
    TitleShown=true;
  }
  if (DisableNames)
    return;

  const wchar *Name=hd.FileName.c_str();
  RARFORMAT Format=Arc.Format;

  if (Bare)
  {
    mprintf(L"%s\n",Name);
    return;
  }

  wchar UnpSizeText[30],PackSizeText[30];
  if (hd.UnpSize==INT64NDF)
    wcsncpyz(UnpSizeText,L"?",ASIZE(UnpSizeText));
  else
    itoa(hd.UnpSize,UnpSizeText,ASIZE(UnpSizeText));
  itoa(hd.PackSize,PackSizeText,ASIZE(PackSizeText));

  wchar AttrStr[30];
  if (hd.HeaderType==HEAD_SERVICE)
    swprintf(AttrStr,ASIZE(AttrStr),L"%cB",hd.Inherited ? 'I' : '.');
  else
    ListFileAttr(hd.FileAttr,hd.HSType,AttrStr,ASIZE(AttrStr));

  wchar RatioStr[10];

  if (hd.SplitBefore && hd.SplitAfter)
    wcsncpyz(RatioStr,L"<->",ASIZE(RatioStr));
  else
    if (hd.SplitBefore)
      wcsncpyz(RatioStr,L"<--",ASIZE(RatioStr));
    else
      if (hd.SplitAfter)
        wcsncpyz(RatioStr,L"-->",ASIZE(RatioStr));
      else
        swprintf(RatioStr,ASIZE(RatioStr),L"%d%%",ToPercentUnlim(hd.PackSize,hd.UnpSize));

  wchar DateStr[50];
  hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical);

  if (Technical)
  {
    mprintf(L"\n%12s: %s",St(MListName),Name);

    bool FileBlock=hd.HeaderType==HEAD_FILE;

    if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM))
    {
      mprintf(L"\n%12ls: %ls",St(MListType),St(MListStream));
      std::wstring StreamName=GetStreamNameNTFS(Arc);
      mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName.c_str());
    }
    else
    {
      const wchar *Type=St(FileBlock ? (hd.Dir ? MListDir:MListFile):MListService);
    
      if (hd.RedirType!=FSREDIR_NONE)
        switch(hd.RedirType)
        {
          case FSREDIR_UNIXSYMLINK:
            Type=St(MListUSymlink); break;
          case FSREDIR_WINSYMLINK:
            Type=St(MListWSymlink); break;
          case FSREDIR_JUNCTION:
            Type=St(MListJunction); break;
          case FSREDIR_HARDLINK:
            Type=St(MListHardlink); break;
          case FSREDIR_FILECOPY:
            Type=St(MListCopy);     break;
        }
      mprintf(L"\n%12ls: %ls",St(MListType),Type);
      if (hd.RedirType!=FSREDIR_NONE)
        if (Format==RARFMT15)
        {
          std::string LinkTargetA;
          if (Arc.FileHead.Encrypted)
          {
            // Link data are encrypted. We would need to ask for password
            // and initialize decryption routine to display the link target.
            LinkTargetA="*<-?->";
          }
          else
          {
            size_t DataSize=(size_t)Min(hd.PackSize,MAXPATHSIZE);
            std::vector<char> Buf(DataSize+1);
            Arc.Read(Buf.data(),DataSize);
            Buf[DataSize] = 0;
            LinkTargetA=Buf.data();
          }
          std::wstring LinkTarget;
          CharToWide(LinkTargetA,LinkTarget);
          mprintf(L"\n%12ls: %ls",St(MListTarget),LinkTarget.c_str());
        }
        else
          mprintf(L"\n%12ls: %ls",St(MListTarget),hd.RedirName.c_str());
    }
    if (!hd.Dir)
    {
      mprintf(L"\n%12ls: %ls",St(MListSize),UnpSizeText);
      mprintf(L"\n%12ls: %ls",St(MListPacked),PackSizeText);
      mprintf(L"\n%12ls: %ls",St(MListRatio),RatioStr);
    }
    bool WinTitles=false;
#ifdef _WIN_ALL
    WinTitles=true;
#endif
    if (hd.mtime.IsSet())
      mprintf(L"\n%12ls: %ls",St(WinTitles ? MListModified:MListMtime),DateStr);
    if (hd.ctime.IsSet())
    {
      hd.ctime.GetText(DateStr,ASIZE(DateStr),true);
      mprintf(L"\n%12ls: %ls",St(WinTitles ? MListCreated:MListCtime),DateStr);
    }
    if (hd.atime.IsSet())
    {
      hd.atime.GetText(DateStr,ASIZE(DateStr),true);
      mprintf(L"\n%12ls: %ls",St(WinTitles ? MListAccessed:MListAtime),DateStr);
    }
    mprintf(L"\n%12ls: %ls",St(MListAttr),AttrStr);
    if (hd.FileHash.Type==HASH_CRC32)
      mprintf(L"\n%12ls: %8.8X",
        hd.UseHashKey ? L"CRC32 MAC":hd.SplitAfter ? L"Pack-CRC32":L"CRC32",
        hd.FileHash.CRC32);
    if (hd.FileHash.Type==HASH_BLAKE2)
    {
      std::wstring BlakeStr;
      BinToHex(hd.FileHash.Digest,BLAKE2_DIGEST_SIZE,BlakeStr);
      mprintf(L"\n%12ls: %ls",
        hd.UseHashKey ? L"BLAKE2 MAC":hd.SplitAfter ? L"Pack-BLAKE2":L"BLAKE2",
        BlakeStr.c_str());
    }

    const wchar *HostOS=L"";
    if (Format==RARFMT50 && hd.HSType!=HSYS_UNKNOWN)
      HostOS=hd.HSType==HSYS_WINDOWS ? L"Windows":L"Unix";
    if (Format==RARFMT15)
    {
      static const wchar *RarOS[]={
        L"DOS",L"OS/2",L"Windows",L"Unix",L"Mac OS",L"BeOS",L"WinCE",L"",L"",L""
      };
      if (hd.HostOS<ASIZE(RarOS))
        HostOS=RarOS[hd.HostOS];
    }
    if (*HostOS!=0)
      mprintf(L"\n%12ls: %ls",St(MListHostOS),HostOS);

    std::wstring WinSize;
    if (!hd.Dir)
      if (hd.WinSize%1073741824==0)
        WinSize=L" -md=" + std::to_wstring(hd.WinSize/1073741824) + L"g";
      else
        if (hd.WinSize%1048576==0)
          WinSize=L" -md=" + std::to_wstring(hd.WinSize/1048576) + L"m";
        else
          if (hd.WinSize>=1024)
            WinSize=L" -md=" + std::to_wstring(hd.WinSize/1024) + L"k";
          else
            WinSize=L" -md=?";

    mprintf(L"\n%12ls: RAR %ls(v%d) -m%d%s",St(MListCompInfo),
            Format==RARFMT15 ? L"1.5":L"5.0",
            hd.UnpVer==VER_UNKNOWN ? 0 : hd.UnpVer,hd.Method,WinSize.c_str());

    if (hd.Solid || hd.Encrypted)
    {
      mprintf(L"\n%12ls: ",St(MListFlags));
      if (hd.Solid)
        mprintf(L"%ls ",St(MListSolid));
      if (hd.Encrypted)
        mprintf(L"%ls ",St(MListEnc));
    }

    if (hd.Version)
    {
      uint Version=ParseVersionFileName(hd.FileName,false);
      if (Version!=0)
        mprintf(L"\n%12ls: %u",St(MListFileVer),Version);
    }

    if (hd.UnixOwnerSet)
    {
      mprintf(L"\n%12ls: ",L"Unix owner");
      if (*hd.UnixOwnerName!=0)
        mprintf(L"%ls",GetWide(hd.UnixOwnerName).c_str());
      else
        if (hd.UnixOwnerNumeric)
          mprintf(L"#%d",hd.UnixOwnerID);
      mprintf(L":");
      if (*hd.UnixGroupName!=0)
        mprintf(L"%ls",GetWide(hd.UnixGroupName).c_str());
      else
        if (hd.UnixGroupNumeric)
          mprintf(L"#%d",hd.UnixGroupID);
    }

    mprintf(L"\n");
    return;
  }

  mprintf(L"\n%c%10ls %9ls ",hd.Encrypted ? '*' : ' ',AttrStr,UnpSizeText);

  if (Verbose)
    mprintf(L"%9ls %4ls ",PackSizeText,RatioStr);

  mprintf(L" %ls  ",DateStr);

  if (Verbose)
  {
    if (hd.FileHash.Type==HASH_CRC32)
      mprintf(L"%8.8X  ",hd.FileHash.CRC32);
    else
      if (hd.FileHash.Type==HASH_BLAKE2)
      {
        byte *S=hd.FileHash.Digest;
        mprintf(L"%02x%02x..%02x  ",S[0],S[1],S[31]);
      }
      else
        mprintf(hd.Dir ? L"          ":L"????????  "); // Missing checksum is ok for folder, not for file.
  }
  mprintf(L"%ls",Name);
}


void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize)
{
  switch(HostType)
  {
    case HSYS_WINDOWS:
      swprintf(AttrStr,AttrSize,L"%c%c%c%c%c%c%c",
              (A & 0x2000)!=0 ? 'I' : '.',  // Not content indexed.
              (A & 0x0800)!=0 ? 'C' : '.',  // Compressed.
              (A & 0x0020)!=0 ? 'A' : '.',  // Archive.
              (A & 0x0010)!=0 ? 'D' : '.',  // Directory.
              (A & 0x0004)!=0 ? 'S' : '.',  // System.
              (A & 0x0002)!=0 ? 'H' : '.',  // Hidden.
              (A & 0x0001)!=0 ? 'R' : '.'); // Read-only.
      break;
    case HSYS_UNIX:
      switch (A & 0xF000)
      {
        case 0x4000:
          AttrStr[0]='d';
          break;
        case 0xA000:
          AttrStr[0]='l';
          break;
        default:
          AttrStr[0]='-';
          break;
      }
      swprintf(AttrStr+1,AttrSize-1,L"%c%c%c%c%c%c%c%c%c",
              (A & 0x0100) ? 'r' : '-',
              (A & 0x0080) ? 'w' : '-',
              (A & 0x0040) ? ((A & 0x0800)!=0 ? 's':'x'):((A & 0x0800)!=0 ? 'S':'-'),
              (A & 0x0020) ? 'r' : '-',
              (A & 0x0010) ? 'w' : '-',
              (A & 0x0008) ? ((A & 0x0400)!=0 ? 's':'x'):((A & 0x0400)!=0 ? 'S':'-'),
              (A & 0x0004) ? 'r' : '-',
              (A & 0x0002) ? 'w' : '-',
              (A & 0x0001) ? ((A & 0x200)!=0 ? 't' : 'x') : '-');
      break;
    case HSYS_UNKNOWN:
      wcsncpyz(AttrStr,L"?",AttrSize);
      break;
  }
}
