// Main.cpp

#include "StdAfx.h"

#include "Common/MyInitGuid.h"

#include "Common/CommandLineParser.h"
#include "Common/MyException.h"
#include "Common/IntToString.h"
#include "Common/ListFileUtils.h"
#include "Common/StdInStream.h"
#include "Common/StdOutStream.h"
#include "Common/StringConvert.h"
#include "Common/StringToInt.h"
#include "Common/Wildcard.h"

#include "Windows/FileDir.h"
#include "Windows/FileName.h"
#include "Windows/Defs.h"
#include "Windows/Error.h"
#ifdef _WIN32
#include "Windows/MemoryLock.h"
#endif

#include "../../IPassword.h"
#include "../../ICoder.h"
#include "../Common/UpdateAction.h"
#include "../Common/Update.h"
#include "../Common/Extract.h"
#include "../Common/ArchiveCommandLine.h"
#include "../Common/ExitCode.h"
#ifdef EXTERNAL_CODECS
#include "../Common/LoadCodecs.h"
#endif

#include "../../Compress/LZMA_Alone/LzmaBenchCon.h"

#include "List.h"
#include "OpenCallbackConsole.h"
#include "ExtractCallbackConsole.h"
#include "UpdateCallbackConsole.h"

#include "../../MyVersion.h"

#if defined( _WIN32) && defined( _7ZIP_LARGE_PAGES)
extern "C" 
{ 
#include "../../../../C/Alloc.h"
}
#endif

#include "myPrivate.h"
#include "Windows/System.h"

using namespace NWindows;
using namespace NFile;
using namespace NCommandLineParser;

HINSTANCE g_hInstance = 0;
extern CStdOutStream *g_StdStream;

static const char *kCopyrightString = "\n7-Zip"
#ifndef EXTERNAL_CODECS
" (A)"
#endif

#ifdef _WIN64
" [64]"
#endif

" " MY_VERSION_COPYRIGHT_DATE "\n"
"p7zip Version " P7ZIP_VERSION ;

static const char *kHelpString = 
    "\nUsage: 7z"
#ifdef _NO_CRYPTO
    "r"
#else
#ifndef EXTERNAL_CODECS
    "a"
#endif
#endif
    " <command> [<switches>...] <archive_name> [<file_names>...]\n"
    "       [<@listfiles...>]\n"
    "\n"
    "<Commands>\n"
    "  a: Add files to archive\n"
    "  b: Benchmark\n"
    "  d: Delete files from archive\n"
    "  e: Extract files from archive (without using directory names)\n"
    "  l: List contents of archive\n"
//    "  l[a|t][f]: List contents of archive\n"
//    "    a - with Additional fields\n"
//    "    t - with all fields\n"
//    "    f - with Full pathnames\n"
    "  t: Test integrity of archive\n"
    "  u: Update files to archive\n"
    "  x: eXtract files with full paths\n"
    "<Switches>\n"
    "  -ai[r[-|0]]{@listfile|!wildcard}: Include archives\n"
    "  -ax[r[-|0]]{@listfile|!wildcard}: eXclude archives\n"
    "  -bd: Disable percentage indicator\n"
    "  -i[r[-|0]]{@listfile|!wildcard}: Include filenames\n"
    "  -m{Parameters}: set compression Method (see the manual)\n"
#ifdef HAVE_LSTAT
    "  -l: don't store symlinks; store the files/directories they point to\n"
    "  CAUTION : the scanning stage can never end because of symlinks like '..'\n"
    "            (ex:  ln -s .. ldir)\n"
#endif
    "  -o{Directory}: set Output directory\n"
    "  -p{Password}: set Password\n"
    "  -r[-|0]: Recurse subdirectories\n"
    "  (CAUTION: this flag does not do what you think, avoid using it)\n"
    "  -sfx[{name}]: Create SFX archive\n"
    "  -si[{name}]: read data from stdin\n"
    "  -slt: show technical information for l (List) command\n"
    "  -so: write data to stdout (eg: "
        "7z"
#ifdef _NO_CRYPTO
        "r"
#elif EXCLUDE_COM
        "a"
#endif
        " a dummy -tgzip -so Doc.txt > archive.gz)\n"
    "  -ssc[-]: set sensitive case mode\n"
    "  -t{Type}: Set type of archive\n"
    "  -v{Size}[b|k|m|g]: Create volumes\n"
    "  -u[-][p#][q#][r#][x#][y#][z#][!newArchiveName]: Update options\n"
    "  -w[path]: assign Work directory. Empty path means a temporary directory\n"
    "  -x[r[-|0]]]{@listfile|!wildcard}: eXclude filenames\n"
    "  -y: assume Yes on all queries\n";

// ---------------------------
// exception messages

static const char *kEverythingIsOk = "Everything is Ok";
static const char *kUserErrorMessage  = "Incorrect command line"; // NExitCode::kUserError

static const wchar_t *kDefaultSfxModule = L"7zCon.sfx";

static void ShowMessageAndThrowException(CStdOutStream &s, LPCSTR message, NExitCode::EEnum code)
{
  s << message << endl;
  throw code;
}

static void PrintHelpAndExit(CStdOutStream &s) // yyy
{
  s << kHelpString;
  ShowMessageAndThrowException(s, kUserErrorMessage, NExitCode::kUserError);
}

#ifndef _WIN32
static void GetArguments(int numArguments, const char *arguments[], UStringVector &parts)
{
  parts.Clear();
  for(int i = 0; i < numArguments; i++)
  {
    UString s = MultiByteToUnicodeString(arguments[i]);
    parts.Add(s);
  }
}
#endif

static void ShowCopyrightAndHelp(CStdOutStream &s, bool needHelp)
{
    s << kCopyrightString << " (locale=" << my_getlocale() <<",Utf16=";
    if (global_use_utf16_conversion) s << "on";
    else                             s << "off";
    s << ",HugeFiles=";
    if (sizeof(off_t) >= 8) s << "on,";
    else                    s << "off,";
    int nbcpu = NWindows::NSystem::GetNumberOfProcessors();
    if (nbcpu > 1) s << nbcpu << " CPUs)\n";
    else           s << nbcpu << " CPU)\n";

  if (needHelp) 
    s << kHelpString;
}

#ifdef EXTERNAL_CODECS
static void PrintString(CStdOutStream &stdStream, const AString &s, int size)
{
  int len = s.Length();
  stdStream << s;
  for (int i = len; i < size; i++)
    stdStream << ' ';
}
#endif

static void PrintString(CStdOutStream &stdStream, const UString &s, int size)
{
  int len = s.Length();
  stdStream << s;
  for (int i = len; i < size; i++)
    stdStream << ' ';
}

static inline char GetHex(Byte value)
{
  return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10)));
}

int Main2(
  #ifndef _WIN32  
  int numArguments, const char *arguments[]
  #endif
)
{
  #ifdef _WIN32  
  SetFileApisToOEM();
  #endif
  
  UStringVector commandStrings;
  #ifdef _WIN32  
  NCommandLineParser::SplitCommandLine(GetCommandLineW(), commandStrings);
  #else
  // GetArguments(numArguments, arguments, commandStrings);
  extern void mySplitCommandLine(int numArguments,const char *arguments[],UStringVector &parts);
  mySplitCommandLine(numArguments,arguments,commandStrings);

  #endif

  if(commandStrings.Size() == 1)
  {
    ShowCopyrightAndHelp(g_StdOut, true);
    return 0;
  }
  commandStrings.Delete(0);

  CArchiveCommandLineOptions options;

  CArchiveCommandLineParser parser;

  parser.Parse1(commandStrings, options);

  if(options.HelpMode)
  {
    ShowCopyrightAndHelp(g_StdOut, true);
    return 0;
  }

  #if defined(_WIN32) && defined(_7ZIP_LARGE_PAGES)
  if (options.LargePages)
  {
    SetLargePageSize();
    NSecurity::EnableLockMemoryPrivilege();
  }
  #endif

  CStdOutStream &stdStream = options.StdOutMode ? g_StdErr : g_StdOut;
  g_StdStream = &stdStream;

  if (options.EnableHeaders)
    ShowCopyrightAndHelp(stdStream, false);

  parser.Parse2(options);

  CCodecs *codecs = new CCodecs;
  CMyComPtr<
    #ifdef EXTERNAL_CODECS
    ICompressCodecsInfo
    #else
    IUnknown
    #endif
    > compressCodecsInfo = codecs;
  HRESULT result = codecs->Load();
  if (result != S_OK)
    throw CSystemException(result);

  bool isExtractGroupCommand = options.Command.IsFromExtractGroup();
  if (options.Command.CommandType == NCommandType::kInfo)
  {
    stdStream << endl << "Formats:" << endl;
    int i;
    for (i = 0; i < codecs->Formats.Size(); i++)
    {
      const CArcInfoEx &arc = codecs->Formats[i];
      #ifdef EXTERNAL_CODECS
      if (arc.LibIndex >= 0)
      {
        char s[32];
        ConvertUInt64ToString(arc.LibIndex, s);
        PrintString(stdStream, s, 2);
      }
      else
      #endif
        stdStream << "  ";
      stdStream << ' ';
      stdStream << (char)(arc.UpdateEnabled ? 'C' : ' ');
      stdStream << (char)(arc.KeepName ? 'K' : ' ');
      stdStream << "  ";
      PrintString(stdStream, arc.Name, 6);
      stdStream << "  ";
      UString s;
      for (int t = 0; t < arc.Exts.Size(); t++)
      {
        const CArcExtInfo &ext = arc.Exts[t];
        s += ext.Ext;
        if (!ext.AddExt.IsEmpty())
        {
          s += L" (";
          s += ext.AddExt;
          s += L')';
        }
        s += L' ';
      }
      PrintString(stdStream, s, 14);
      stdStream << "  ";
      const CByteBuffer &sig = arc.StartSignature;
      for (size_t j = 0; j < sig.GetCapacity(); j++)
      {
        Byte b = sig[j];
        if (b > 0x20 && b < 0x80)
        {
          stdStream << (char)b;
        }
        else
        {
          stdStream << GetHex((Byte)((b >> 4) & 0xF));
          stdStream << GetHex((Byte)(b & 0xF));
        }
        stdStream << ' ';
      }
      stdStream << endl;
    }
    stdStream << endl << "Codecs:" << endl;

    #ifdef EXTERNAL_CODECS
    UINT32 numMethods;
    if (codecs->GetNumberOfMethods(&numMethods) == S_OK)
    for (UInt32 j = 0; j < numMethods; j++)
    {
      int libIndex = codecs->GetCodecLibIndex(j);
      if (libIndex >= 0)
      {
        char s[32];
        ConvertUInt64ToString(libIndex, s);
        PrintString(stdStream, s, 2);
      }
      else
        stdStream << "  ";
      stdStream << ' ';
      stdStream << (char)(codecs->GetCodecEncoderIsAssigned(j) ? 'C' : ' ');
      UInt64 id;
      stdStream << "  ";
      HRESULT res = codecs->GetCodecId(j, id);
      if (res != S_OK)
        id = (UInt64)(Int64)-1;
      char s[32];
      ConvertUInt64ToString(id, s, 16);
      PrintString(stdStream, s, 8);
      stdStream << "  ";
      PrintString(stdStream, codecs->GetCodecName(j), 11);
      stdStream << endl;
      /*
      if (res != S_OK)
        throw "incorrect Codec ID";
      */
    }
    #endif
    return S_OK;
  }
  else if (options.Command.CommandType == NCommandType::kBenchmark)
  {
    if (options.Method.CompareNoCase(L"CRC") == 0)
    {
      HRESULT res = CrcBenchCon((FILE *)stdStream, options.NumIterations, options.NumThreads, options.DictionarySize);
      if (res != S_OK)
      {
        if (res == S_FALSE)
        {
          stdStream << "\nCRC Error\n";
          return NExitCode::kFatalError;
        }
        throw CSystemException(res);
      }
    }
    else
    {
      HRESULT res = LzmaBenchCon(
        #ifdef EXTERNAL_LZMA
        codecs,
        #endif
        (FILE *)stdStream, options.NumIterations, options.NumThreads, options.DictionarySize);
      if (res != S_OK)
      {
        if (res == S_FALSE)
        {
          stdStream << "\nDecoding Error\n";
          return NExitCode::kFatalError;
        }
        throw CSystemException(res);
      }
    }
  }
  else if (isExtractGroupCommand || options.Command.CommandType == NCommandType::kList)
  {
    if(isExtractGroupCommand)
    {
      CExtractCallbackConsole *ecs = new CExtractCallbackConsole;
      CMyComPtr<IFolderArchiveExtractCallback> extractCallback = ecs;

      ecs->OutStream = &stdStream;
      ecs->PasswordIsDefined = options.PasswordEnabled;
      ecs->Password = options.Password;
      ecs->Init();

      COpenCallbackConsole openCallback;
      openCallback.OutStream = &stdStream;
      openCallback.PasswordIsDefined = options.PasswordEnabled;
      openCallback.Password = options.Password;

      CExtractOptions eo;
      eo.StdOutMode = options.StdOutMode;
      eo.PathMode = options.Command.GetPathMode();
      eo.TestMode = options.Command.IsTestMode();
      eo.OverwriteMode = options.OverwriteMode;
      eo.OutputDir = options.OutputDir;
      eo.YesToAll = options.YesToAll;
      #ifdef COMPRESS_MT
      eo.Properties = options.ExtractProperties;
      #endif
      UString errorMessage;
      CDecompressStat stat;
      HRESULT result = DecompressArchives(
          codecs,
          options.ArchivePathsSorted, 
          options.ArchivePathsFullSorted,
          options.WildcardCensor.Pairs.Front().Head, 
          eo, &openCallback, ecs, errorMessage, stat);
      if (!errorMessage.IsEmpty())
      {
        stdStream << endl << "Error: " << errorMessage;
        if (result == S_OK)
          result = E_FAIL;
      }

      stdStream << endl << endl << "Total:" << endl;
      if (ecs->NumArchives > 1)
        stdStream << "Archives: " << ecs->NumArchives << endl;
      {
        stdStream << "Folders: " << stat.NumFolders << endl;
        stdStream << "Files: " << stat.NumFiles << endl;
        stdStream << "Size: " << stat.UnpackSize << endl;
        stdStream << "Compressed: " << stat.PackSize << endl;
      }
      if (ecs->NumArchiveErrors != 0 || ecs->NumFileErrors != 0)
      {
        if (ecs->NumArchives > 1)
        {
          stdStream << endl;
          if (ecs->NumArchiveErrors != 0)
            stdStream << "Archive Errors: " << ecs->NumArchiveErrors << endl;
          if (ecs->NumFileErrors != 0)
            stdStream << "Sub items Errors: " << ecs->NumFileErrors << endl;
        }
        if (result != S_OK)
          throw CSystemException(result);
        return NExitCode::kFatalError;
      }
      if (result != S_OK)
        throw CSystemException(result);
    }
    else
    {
      UInt64 numErrors = 0;
      HRESULT result = ListArchives(
          codecs,
          options.ArchivePathsSorted, 
          options.ArchivePathsFullSorted,
          options.WildcardCensor.Pairs.Front().Head, 
          options.EnableHeaders, 
          options.TechMode,
          options.PasswordEnabled, 
          options.Password, numErrors);
      if (numErrors > 0)
      {
        g_StdOut << endl << "Errors: " << numErrors;
        return NExitCode::kFatalError;
      }
      if (result != S_OK)
        throw CSystemException(result);
    }
  }
  else if(options.Command.IsFromUpdateGroup())
  {
    UString workingDir;

    CUpdateOptions &uo = options.UpdateOptions;
    if (uo.SfxMode && uo.SfxModule.IsEmpty())
      uo.SfxModule = kDefaultSfxModule;

    bool passwordIsDefined = 
        options.PasswordEnabled && !options.Password.IsEmpty();

    COpenCallbackConsole openCallback;
    openCallback.OutStream = &stdStream;
    openCallback.PasswordIsDefined = passwordIsDefined;
    openCallback.Password = options.Password;

    CUpdateCallbackConsole callback;
    callback.EnablePercents = options.EnablePercents;
    callback.PasswordIsDefined = passwordIsDefined;
    callback.AskPassword = options.PasswordEnabled && options.Password.IsEmpty();
    callback.Password = options.Password;
    callback.StdOutMode = uo.StdOutMode;
    callback.Init(&stdStream);

    CUpdateErrorInfo errorInfo;

    if (!uo.Init(codecs, options.ArchiveName, options.ArcType))
      throw "Unsupported archive type";
    HRESULT result = UpdateArchive(codecs, 
        options.WildcardCensor, uo, 
        errorInfo, &openCallback, &callback);

#ifdef ENV_UNIX
    if (uo.SfxMode)
    {
        void myAddExeFlag(const UString &name);
        for(int i = 0; i < uo.Commands.Size(); i++)
        {
            CUpdateArchiveCommand &command = uo.Commands[i];
            if (!uo.StdOutMode)
            {
                myAddExeFlag(command.ArchivePath.GetFinalPath());
            }
        }
    }
#endif

    int exitCode = NExitCode::kSuccess;
    if (callback.CantFindFiles.Size() > 0)
    {
      stdStream << endl;
      stdStream << "WARNINGS for files:" << endl << endl;
      int numErrors = callback.CantFindFiles.Size();
      for (int i = 0; i < numErrors; i++)
      {
        stdStream << callback.CantFindFiles[i] << " : ";
        stdStream << NError::MyFormatMessageW(callback.CantFindCodes[i]) << endl;
      }
      stdStream << "----------------" << endl;
      stdStream << "WARNING: Cannot find " << numErrors << " file";
      if (numErrors > 1)
        stdStream << "s";
      stdStream << endl;
      exitCode = NExitCode::kWarning;
    }

    if (result != S_OK)
    {
      UString message;
      if (!errorInfo.Message.IsEmpty())
      {
        message += errorInfo.Message;
        message += L"\n";
      }
      if (!errorInfo.FileName.IsEmpty())
      {
        message += errorInfo.FileName;
        message += L"\n";
      }
      if (!errorInfo.FileName2.IsEmpty())
      {
        message += errorInfo.FileName2;
        message += L"\n";
      }
      if (errorInfo.SystemError != 0)
      {
        message += NError::MyFormatMessageW(errorInfo.SystemError);
        message += L"\n";
      }
      if (!message.IsEmpty())
        stdStream << L"\nError:\n" << message;
      throw CSystemException(result);
    }
    int numErrors = callback.FailedFiles.Size();
    if (numErrors == 0)
    {
      if (callback.CantFindFiles.Size() == 0)
        stdStream << kEverythingIsOk << endl;
    }
    else
    {
      stdStream << endl;
      stdStream << "WARNINGS for files:" << endl << endl;
      for (int i = 0; i < numErrors; i++)
      {
        stdStream << callback.FailedFiles[i] << " : ";
        stdStream << NError::MyFormatMessageW(callback.FailedCodes[i]) << endl;
      }
      stdStream << "----------------" << endl;
      stdStream << "WARNING: Cannot open " << numErrors << " file";
      if (numErrors > 1)
        stdStream << "s";
      stdStream << endl;
      exitCode = NExitCode::kWarning;
    }
    return exitCode;
  }
  else 
    PrintHelpAndExit(stdStream);
  return 0;
}
