// CompressDialog.cpp

#include "StdAfx.h"

#include "../../../../C/CpuArch.h"

#include "../../../Common/IntToString.h"
#include "../../../Common/StringConvert.h"

#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileName.h"
#include "../../../Windows/System.h"

#include "../../Common/MethodProps.h"

#include "../FileManager/BrowseDialog.h"
#include "../FileManager/FormatUtils.h"
#include "../FileManager/HelpUtils.h"
#include "../FileManager/PropertyName.h"
#include "../FileManager/SplitUtils.h"
#include "../FileManager/resourceGui.h"

#include "../Explorer/MyMessages.h"

#include "../Common/ZipRegistry.h"

#include "CompressDialog.h"

#ifndef _UNICODE
extern bool g_IsNT;
#endif

#include "../FileManager/LangUtils.h"

#include "CompressDialogRes.h"
#include "ExtractRes.h"
#include "resource2.h"

// #define PRINT_PARAMS

#ifdef Z7_LANG

// #define IDS_OPTIONS 2100

static const UInt32 kLangIDs[] =
{
  IDT_COMPRESS_ARCHIVE,
  IDT_COMPRESS_UPDATE_MODE,
  IDT_COMPRESS_FORMAT,
  IDT_COMPRESS_LEVEL,
  IDT_COMPRESS_METHOD,
  IDT_COMPRESS_DICTIONARY,
  IDT_COMPRESS_ORDER,
  IDT_COMPRESS_SOLID,
  IDT_COMPRESS_THREADS,
  IDT_COMPRESS_PARAMETERS,
  
  IDB_COMPRESS_OPTIONS, // IDS_OPTIONS

  IDG_COMPRESS_OPTIONS,
  IDX_COMPRESS_SFX,
  IDX_COMPRESS_SHARED,
  IDX_COMPRESS_DEL,

  IDT_COMPRESS_MEMORY,
  IDT_COMPRESS_MEMORY_DE,

  IDG_COMPRESS_ENCRYPTION,
  IDT_COMPRESS_ENCRYPTION_METHOD,
  IDX_COMPRESS_ENCRYPT_FILE_NAMES,

  IDT_PASSWORD_ENTER,
  IDT_PASSWORD_REENTER,
  IDX_PASSWORD_SHOW,

  IDT_SPLIT_TO_VOLUMES,
  IDT_COMPRESS_PATH_MODE,
};
#endif

using namespace NWindows;
using namespace NFile;
using namespace NName;
using namespace NDir;

static const unsigned kHistorySize = 20;

static const UInt32 kSolidLog_NoSolid = 0;
static const UInt32 kSolidLog_FullSolid = 64;

static const UInt32 kLzmaMaxDictSize = (UInt32)15 << 28;

static const UINT k_Message_ArcChanged = WM_APP + 1;

/*
static const UInt32 kZstd_MAX_DictSize = (UInt32)1 << MY_ZSTD_WINDOWLOG_MAX;
*/

/* The top value for windowLog_Chain:
   (MY_ZSTD_CHAINLOG_MAX - 1): in BT mode
   (MY_ZSTD_CHAINLOG_MAX)    : in non-BT mode. But such big value is useless in most cases.
   So we always reduce top value to (MY_ZSTD_CHAINLOG_MAX - 1) */
/*
static const unsigned kMaxDictChain = MY_ZSTD_CHAINLOG_MAX - 1;
static const UInt32 kZstd_MAX_DictSize_Chain = (UInt32)1 << kMaxDictChain;
*/

static LPCSTR const kExeExt = ".exe";

static const UInt32 g_Levels[] =
{
  IDS_METHOD_STORE,
  IDS_METHOD_FASTEST,
  0,
  IDS_METHOD_FAST,
  0,
  IDS_METHOD_NORMAL,
  0,
  IDS_METHOD_MAXIMUM,
  0,
  IDS_METHOD_ULTRA
};

enum EMethodID
{
  kCopy,
  kLZMA,
  kLZMA2,
  kPPMd,
  kBZip2,
  kDeflate,
  kDeflate64,
  kPPMdZip,
  // kZSTD,
  kSha256,
  kSha1,
  kCrc32,
  kCrc64,
  kGnu,
  kPosix
};

static LPCSTR const kMethodsNames[] =
{
    "Copy"
  , "LZMA"
  , "LZMA2"
  , "PPMd"
  , "BZip2"
  , "Deflate"
  , "Deflate64"
  , "PPMd"
  // , "ZSTD"
  , "SHA256"
  , "SHA1"
  , "CRC32"
  , "CRC64"
  , "GNU"
  , "POSIX"
};

static const EMethodID g_7zMethods[] =
{
  kLZMA2,
  kLZMA,
  kPPMd,
  kBZip2
  , kDeflate
  , kDeflate64
  // , kZSTD
  , kCopy
};

static const EMethodID g_7zSfxMethods[] =
{
  kCopy,
  kLZMA,
  kLZMA2,
  kPPMd
};

static const EMethodID g_ZipMethods[] =
{
  kDeflate,
  kDeflate64,
  kBZip2,
  kLZMA,
  kPPMdZip
  // , kZSTD
};

static const EMethodID g_GZipMethods[] =
{
  kDeflate
};

static const EMethodID g_BZip2Methods[] =
{
  kBZip2
};

static const EMethodID g_XzMethods[] =
{
  kLZMA2
};

/*
static const EMethodID g_ZstdMethods[] =
{
  kZSTD
};
*/

/*
static const EMethodID g_SwfcMethods[] =
{
  kDeflate
  // kLZMA
};
*/

static const EMethodID g_TarMethods[] =
{
  kGnu,
  kPosix
};

static const EMethodID g_HashMethods[] =
{
    kSha256
  , kSha1
  // , kCrc32
  // , kCrc64
};

static const UInt32 kFF_Filter      = 1 << 0;
static const UInt32 kFF_Solid       = 1 << 1;
static const UInt32 kFF_MultiThread = 1 << 2;
static const UInt32 kFF_Encrypt     = 1 << 3;
static const UInt32 kFF_EncryptFileNames  = 1 << 4;
static const UInt32 kFF_MemUse      = 1 << 5;
static const UInt32 kFF_SFX         = 1 << 6;

/*
static const UInt32 kFF_Time_Win  = 1 << 10;
static const UInt32 kFF_Time_Unix = 1 << 11;
static const UInt32 kFF_Time_DOS  = 1 << 12;
static const UInt32 kFF_Time_1ns  = 1 << 13;
*/

struct CFormatInfo
{
  LPCSTR Name;
  UInt32 LevelsMask;
  unsigned NumMethods;
  const EMethodID *MethodIDs;

  UInt32 Flags;

  bool Filter_() const { return (Flags & kFF_Filter) != 0; }
  bool Solid_() const { return (Flags & kFF_Solid) != 0; }
  bool MultiThread_() const { return (Flags & kFF_MultiThread) != 0; }
  bool Encrypt_() const { return (Flags & kFF_Encrypt) != 0; }
  bool EncryptFileNames_() const { return (Flags & kFF_EncryptFileNames) != 0; }
  bool MemUse_() const { return (Flags & kFF_MemUse) != 0; }
  bool SFX_() const { return (Flags & kFF_SFX) != 0; }
};

#define METHODS_PAIR(x) Z7_ARRAY_SIZE(x), x

static const CFormatInfo g_Formats[] =
{
  {
    "",
    // (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    ((UInt32)1 << 10) - 1,
    // (UInt32)(Int32)-1,
    0, NULL,
    kFF_MultiThread | kFF_MemUse
  },
  {
    "7z",
    // (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    (1 << 10) - 1,
    METHODS_PAIR(g_7zMethods),
    kFF_Filter | kFF_Solid | kFF_MultiThread | kFF_Encrypt |
    kFF_EncryptFileNames | kFF_MemUse | kFF_SFX
    // | kFF_Time_Win
  },
  {
    "Zip",
    (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_ZipMethods),
    kFF_MultiThread | kFF_Encrypt | kFF_MemUse
    // | kFF_Time_Win | kFF_Time_Unix | kFF_Time_DOS
  },
  {
    "GZip",
    (1 << 1) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_GZipMethods),
    kFF_MemUse
    // | kFF_Time_Unix
  },
  {
    "BZip2",
    (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_BZip2Methods),
    kFF_MultiThread | kFF_MemUse
  },
  {
    "xz",
    // (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    (1 << 10) - 1 - (1 << 0), // store (1 << 0) is not supported
    METHODS_PAIR(g_XzMethods),
    kFF_Solid | kFF_MultiThread | kFF_MemUse
  },
  /*
  {
    "zstd",
    // (1 << (MY_ZSTD_LEVEL_MAX + 1)) - 1,
    (1 << (9 + 1)) - 1,
    METHODS_PAIR(g_ZstdMethods),
    // kFF_Solid |
    kFF_MultiThread
    | kFF_MemUse
  },
  */
/*
  {
    "Swfc",
    (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9),
    METHODS_PAIR(g_SwfcMethods),
    0
  },
*/
  {
    "Tar",
    (1 << 0),
    METHODS_PAIR(g_TarMethods),
    0
    // kFF_Time_Unix | kFF_Time_Win // | kFF_Time_1ns
  },
  {
    "wim",
    (1 << 0),
    0, NULL,
    0
    // | kFF_Time_Win
  },
  {
    "Hash",
    (0 << 0),
    METHODS_PAIR(g_HashMethods),
    0
  }
};

static bool IsMethodSupportedBySfx(int methodID)
{
  for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_7zSfxMethods); i++)
    if (methodID == g_7zSfxMethods[i])
      return true;
  return false;
}


static const
  // NCompressDialog::NUpdateMode::EEnum
  int
  k_UpdateMode_Vals[] =
{
  NCompressDialog::NUpdateMode::kAdd,
  NCompressDialog::NUpdateMode::kUpdate,
  NCompressDialog::NUpdateMode::kFresh,
  NCompressDialog::NUpdateMode::kSync
};
  
static const UInt32 k_UpdateMode_IDs[] =
{
  IDS_COMPRESS_UPDATE_MODE_ADD,
  IDS_COMPRESS_UPDATE_MODE_UPDATE,
  IDS_COMPRESS_UPDATE_MODE_FRESH,
  IDS_COMPRESS_UPDATE_MODE_SYNC
};

static const
  // NWildcard::ECensorPathMode
  int
  k_PathMode_Vals[] =
{
  NWildcard::k_RelatPath,
  NWildcard::k_FullPath,
  NWildcard::k_AbsPath,
};

static const UInt32 k_PathMode_IDs[] =
{
  IDS_PATH_MODE_RELAT,
  IDS_EXTRACT_PATHS_FULL,
  IDS_EXTRACT_PATHS_ABS
};

void AddComboItems(NControl::CComboBox &combo, const UInt32 *langIDs, unsigned numItems, const int *values, int curVal);

void CCompressDialog::SetMethods(const CObjectVector<CCodecInfoUser> &userCodecs)
{
  ExternalMethods.Clear();
  {
    FOR_VECTOR (i, userCodecs)
    {
      const CCodecInfoUser &c = userCodecs[i];
      if (!c.EncoderIsAssigned
          || !c.IsFilter_Assigned
          || c.IsFilter
          || c.NumStreams != 1)
        continue;
      unsigned k;
      for (k = 0; k < Z7_ARRAY_SIZE(g_7zMethods); k++)
        if (c.Name.IsEqualTo_Ascii_NoCase(kMethodsNames[g_7zMethods[k]]))
          break;
      if (k != Z7_ARRAY_SIZE(g_7zMethods))
        continue;
      ExternalMethods.Add(c.Name);
    }
  }
}
                                         

bool CCompressDialog::OnInit()
{
  #ifdef Z7_LANG
  LangSetWindowText(*this, IDD_COMPRESS);
  LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs));
  // LangSetDlgItemText(*this, IDB_COMPRESS_OPTIONS, IDS_OPTIONS); // IDG_COMPRESS_OPTIONS
  #endif

  {
    size_t size = (size_t)sizeof(size_t) << 29;
    _ramSize_Defined = NSystem::GetRamSize(size);
    // size = (UInt64)3 << 62; // for debug only;
    {
      // we use reduced limit for 32-bit version:
      unsigned bits = sizeof(size_t) * 8;
      if (bits == 32)
      {
        const UInt32 limit2 = (UInt32)7 << 28;
        if (size > limit2)
            size = limit2;
      }
    }
    _ramSize = size;
    const size_t kMinUseSize = 1 << 26;
    if (size < kMinUseSize)
        size = kMinUseSize;
    _ramSize_Reduced = size;

    // 80% - is auto usage limit in handlers
    _ramUsage_Auto = Calc_From_Val_Percents(size, 80);
  }

  _password1Control.Attach(GetItem(IDE_COMPRESS_PASSWORD1));
  _password2Control.Attach(GetItem(IDE_COMPRESS_PASSWORD2));
  _password1Control.SetText(Info.Password);
  _password2Control.SetText(Info.Password);
  _encryptionMethod.Attach(GetItem(IDC_COMPRESS_ENCRYPTION_METHOD));
  _default_encryptionMethod_Index = -1;

  m_ArchivePath.Attach(GetItem(IDC_COMPRESS_ARCHIVE));
  m_Format.Attach(GetItem(IDC_COMPRESS_FORMAT)); // that combo has CBS_SORT style in resources
  m_Level.Attach(GetItem(IDC_COMPRESS_LEVEL));
  m_Method.Attach(GetItem(IDC_COMPRESS_METHOD));
  m_Dictionary.Attach(GetItem(IDC_COMPRESS_DICTIONARY));

  /*
  {
    RECT r;
    GetClientRectOfItem(IDC_COMPRESS_DICTIONARY, r);
    _dictionaryCombo_left = r.left;
  }
  */
  _dictionaryCombo_left = 0; // 230;

  // m_Dictionary_Chain.Attach(GetItem(IDC_COMPRESS_DICTIONARY2));
  m_Order.Attach(GetItem(IDC_COMPRESS_ORDER));
  m_Solid.Attach(GetItem(IDC_COMPRESS_SOLID));
  m_NumThreads.Attach(GetItem(IDC_COMPRESS_THREADS));
  m_MemUse.Attach(GetItem(IDC_COMPRESS_MEM_USE));
  
  m_UpdateMode.Attach(GetItem(IDC_COMPRESS_UPDATE_MODE));
  m_PathMode.Attach(GetItem(IDC_COMPRESS_PATH_MODE));

  m_Volume.Attach(GetItem(IDC_COMPRESS_VOLUME));
  m_Params.Attach(GetItem(IDE_COMPRESS_PARAMETERS));

  AddVolumeItems(m_Volume);

  m_RegistryInfo.Load();
  CheckButton(IDX_PASSWORD_SHOW, m_RegistryInfo.ShowPassword);
  CheckButton(IDX_COMPRESS_ENCRYPT_FILE_NAMES, m_RegistryInfo.EncryptHeaders);
  
  UpdatePasswordControl();

  {
    const bool needSetMain = (Info.FormatIndex < 0);
    FOR_VECTOR(i, ArcIndices)
    {
      const unsigned arcIndex = ArcIndices[i];
      const CArcInfoEx &ai = (*ArcFormats)[arcIndex];
      const int index = (int)m_Format.AddString(ai.Name);
      m_Format.SetItemData(index, (LPARAM)arcIndex);
      if (!needSetMain)
      {
        if (Info.FormatIndex == (int)arcIndex)
          m_Format.SetCurSel(index);
        continue;
      }
      if (i == 0 || ai.Name.IsEqualTo_NoCase(m_RegistryInfo.ArcType))
      {
        m_Format.SetCurSel(index);
        Info.FormatIndex = (int)arcIndex;
      }
    }
  }

  CheckButton(IDX_COMPRESS_SFX, Info.SFXMode);

  {
    UString fileName;
    SetArcPathFields(Info.ArcPath, fileName, true);
    StartDirPrefix = DirPrefix;
    SetArchiveName(fileName);
  }
  
  for (unsigned i = 0; i < m_RegistryInfo.ArcPaths.Size() && i < kHistorySize; i++)
    m_ArchivePath.AddString(m_RegistryInfo.ArcPaths[i]);

  AddComboItems(m_UpdateMode, k_UpdateMode_IDs, Z7_ARRAY_SIZE(k_UpdateMode_IDs),
      k_UpdateMode_Vals, Info.UpdateMode);

  AddComboItems(m_PathMode, k_PathMode_IDs, Z7_ARRAY_SIZE(k_PathMode_IDs),
      k_PathMode_Vals, Info.PathMode);


  TCHAR s[32] = { TEXT('/'), TEXT(' '), 0 };
  ConvertUInt32ToString(NSystem::GetNumberOfProcessors(), s + 2);
  SetItemText(IDT_COMPRESS_HARDWARE_THREADS, s);

  CheckButton(IDX_COMPRESS_SHARED, Info.OpenShareForWrite);
  CheckButton(IDX_COMPRESS_DEL, Info.DeleteAfterCompressing);

  FormatChanged(false); // isChanged

  // OnButtonSFX();

  NormalizePosition();

  return CModalDialog::OnInit();
}

/*
namespace NCompressDialog
{
  bool CInfo::GetFullPathName(UString &result) const
  {
    #ifndef UNDER_CE
    // NDirectory::MySetCurrentDirectory(CurrentDirPrefix);
    #endif
    FString resultF;
    bool res = MyGetFullPathName(us2fs(ArchiveName), resultF);
    result = fs2us(resultF);
    return res;
  }
}
*/

void CCompressDialog::UpdatePasswordControl()
{
  const bool showPassword = IsShowPasswordChecked();
  const TCHAR c = showPassword ? (TCHAR)0: TEXT('*');
  _password1Control.SetPasswordChar((WPARAM)c);
  _password2Control.SetPasswordChar((WPARAM)c);
  UString password;
  _password1Control.GetText(password);
  _password1Control.SetText(password);
  _password2Control.GetText(password);
  _password2Control.SetText(password);

  ShowItem_Bool(IDT_PASSWORD_REENTER, !showPassword);
  _password2Control.Show_Bool(!showPassword);
}

bool CCompressDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND)
{
  switch (buttonID)
  {
    case IDB_COMPRESS_SET_ARCHIVE:
    {
      OnButtonSetArchive();
      return true;
    }
    case IDX_COMPRESS_SFX:
    {
      SetMethod(GetMethodID());
      OnButtonSFX();
      SetMemoryUsage();
      return true;
    }
    case IDX_PASSWORD_SHOW:
    {
      UpdatePasswordControl();
      return true;
    }
    case IDB_COMPRESS_OPTIONS:
    {
      COptionsDialog dialog(this);
      if (dialog.Create(*this) == IDOK)
        ShowOptionsString();
      return true;
    }
  }
  return CModalDialog::OnButtonClicked(buttonID, buttonHWND);
}

void CCompressDialog::CheckSFXControlsEnable()
{
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  bool enable = fi.SFX_();
  if (enable)
  {
    const int methodID = GetMethodID();
    enable = (methodID == -1 || IsMethodSupportedBySfx(methodID));
  }
  if (!enable)
    CheckButton(IDX_COMPRESS_SFX, false);
  EnableItem(IDX_COMPRESS_SFX, enable);
}

/*
void CCompressDialog::CheckVolumeEnable()
{
  bool isSFX = IsSFX();
  m_Volume.Enable(!isSFX);
  if (isSFX)
    m_Volume.SetText(TEXT(""));
}
*/

void CCompressDialog::EnableMultiCombo(unsigned id)
{
  NWindows::NControl::CComboBox combo;
  combo.Attach(GetItem(id));
  const bool enable = (combo.GetCount() > 1);
  EnableItem(id, enable);
}

static LRESULT ComboBox_AddStringAscii(NControl::CComboBox &cb, const char *s);

static void Combine_Two_BoolPairs(const CBoolPair &b1, const CBoolPair &b2, CBool1 &res)
{
  if (!b1.Def && b2.Def)
    res.Val = b2.Val;
  else
    res.Val = b1.Val;
}

#define SET_GUI_BOOL(name) \
      Combine_Two_BoolPairs(Info. name, m_RegistryInfo. name, name)


static void Set_Final_BoolPairs(
    const CBool1 &gui,
    CBoolPair &cmd,
    CBoolPair &reg)
{
  if (!cmd.Def)
  {
    reg.Val = gui.Val;
    reg.Def = gui.Val;
  }
  if (gui.Supported)
  {
    cmd.Val = gui.Val;
    cmd.Def = gui.Val;
  }
  else
    cmd.Init();
}

#define SET_FINAL_BOOL_PAIRS(name) \
    Set_Final_BoolPairs(name, Info. name, m_RegistryInfo. name)

void CCompressDialog::FormatChanged(bool isChanged)
{
  SetLevel();
  SetSolidBlockSize();
  SetParams();
  SetMemUseCombo();
  SetNumThreads();

  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  Info.SolidIsSpecified = fi.Solid_();
  Info.EncryptHeadersIsAllowed = fi.EncryptFileNames_();
  
  /*
  const bool multiThreadEnable = fi.MultiThread;
  Info.MultiThreadIsAllowed = multiThreadEnable;
  EnableItem(IDC_COMPRESS_SOLID, fi.Solid);
  EnableItem(IDC_COMPRESS_THREADS, multiThreadEnable);
  const bool methodEnable = (fi.MethodIDs != NULL);
  EnableItem(IDC_COMPRESS_METHOD, methodEnable);
  EnableMultiCombo(IDC_COMPRESS_DICTIONARY, methodEnable);
  EnableItem(IDC_COMPRESS_ORDER, methodEnable);
  */

  CheckSFXControlsEnable();

  {
    if (!isChanged)
    {
      SET_GUI_BOOL (SymLinks);
      SET_GUI_BOOL (HardLinks);
      SET_GUI_BOOL (AltStreams);
      SET_GUI_BOOL (NtSecurity);
      SET_GUI_BOOL (PreserveATime);
    }

    PreserveATime.Supported = true;

    {
      const CArcInfoEx &ai = Get_ArcInfoEx();
      SymLinks.Supported   = ai.Flags_SymLinks();
      HardLinks.Supported  = ai.Flags_HardLinks();
      AltStreams.Supported = ai.Flags_AltStreams();
      NtSecurity.Supported = ai.Flags_NtSecurity();
    }

    ShowOptionsString();
  }
  // CheckVolumeEnable();

  const bool encrypt = fi.Encrypt_();
  EnableItem(IDG_COMPRESS_ENCRYPTION, encrypt);

  EnableItem(IDT_PASSWORD_ENTER, encrypt);
  EnableItem(IDT_PASSWORD_REENTER, encrypt);
  EnableItem(IDE_COMPRESS_PASSWORD1, encrypt);
  EnableItem(IDE_COMPRESS_PASSWORD2, encrypt);
  EnableItem(IDX_PASSWORD_SHOW, encrypt);

  EnableItem(IDT_COMPRESS_ENCRYPTION_METHOD, encrypt);
  EnableItem(IDC_COMPRESS_ENCRYPTION_METHOD, encrypt);
  EnableItem(IDX_COMPRESS_ENCRYPT_FILE_NAMES, fi.EncryptFileNames_());

  ShowItem_Bool(IDX_COMPRESS_ENCRYPT_FILE_NAMES, fi.EncryptFileNames_());

  SetEncryptionMethod();
  SetMemoryUsage();
}


bool CCompressDialog::IsSFX()
{
  return IsWindowEnabled(GetItem(IDX_COMPRESS_SFX))
      && IsButtonCheckedBool(IDX_COMPRESS_SFX);
}

static int GetExtDotPos(const UString &s)
{
  const int dotPos = s.ReverseFind_Dot();
  if (dotPos > s.ReverseFind_PathSepar() + 1)
    return dotPos;
  return -1;
}

void CCompressDialog::OnButtonSFX()
{
  UString fileName;
  m_ArchivePath.GetText(fileName);
  const int dotPos = GetExtDotPos(fileName);
  if (IsSFX())
  {
    if (dotPos >= 0)
      fileName.DeleteFrom(dotPos);
    fileName += kExeExt;
    m_ArchivePath.SetText(fileName);
  }
  else
  {
    if (dotPos >= 0)
    {
      const UString ext = fileName.Ptr(dotPos);
      if (ext.IsEqualTo_Ascii_NoCase(kExeExt))
      {
        fileName.DeleteFrom(dotPos);
        m_ArchivePath.SetText(fileName);
      }
    }
    SetArchiveName2(false); // it's for OnInit
  }

  // CheckVolumeEnable();
}


bool CCompressDialog::GetFinalPath_Smart(UString &resPath) const
{
  resPath.Empty();
  UString name;
  m_ArchivePath.GetText(name);
  name.Trim();
  FString fullPath;
  UString dirPrefx = DirPrefix;
  if (dirPrefx.IsEmpty())
    dirPrefx = StartDirPrefix;
  const bool res = !dirPrefx.IsEmpty() ?
      NName::GetFullPath(us2fs(dirPrefx), us2fs(name), fullPath):
      NName::GetFullPath(                 us2fs(name), fullPath);
  if (res)
    resPath = fs2us(fullPath);
  return res;
}


bool CCompressDialog::SetArcPathFields(const UString &path)
{
  UString name;
  return SetArcPathFields(path, name, true); // always
}


bool CCompressDialog::SetArcPathFields(const UString &path, UString &name, bool always)
{
  FString resDirPrefix;
  FString resFileName;
  const bool res = GetFullPathAndSplit(us2fs(path), resDirPrefix, resFileName);
  if (res)
  {
    DirPrefix = fs2us(resDirPrefix);
    name = fs2us(resFileName);
  }
  else
  {
    if (!always)
      return false;
    DirPrefix.Empty();
    name = path;
  }
  SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, DirPrefix);
  m_ArchivePath.SetText(name);
  return res;
}


static const wchar_t * const k_IncorrectPathMessage = L"Incorrect archive path";

static void AddFilter(CObjectVector<CBrowseFilterInfo> &filters,
    const UString &description, const UString &ext)
{
  CBrowseFilterInfo &f = filters.AddNew();
  UString mask ("*.");
  mask += ext;
  f.Masks.Add(mask);
  f.Description = description;
  f.Description += " (";
  f.Description += mask;
  f.Description += ")";
}


static const char * const k_DontSave_Exts =
  "xpi odt ods docx xlsx ";

void CCompressDialog::OnButtonSetArchive()
{
  UString path;
  if (!GetFinalPath_Smart(path))
  {
    ShowErrorMessage(*this, k_IncorrectPathMessage);
    return;
  }

  int filterIndex;
  CObjectVector<CBrowseFilterInfo> filters;
  unsigned numFormats = 0;

  const bool isSFX = IsSFX();
  if (isSFX)
  {
    filterIndex = 0;
    const UString ext ("exe");
    AddFilter(filters, ext, ext);
  }
  else
  {
    filterIndex = m_Format.GetCurSel();
    numFormats = (unsigned)m_Format.GetCount();

    // filters [0, ... numFormats - 1] corresponds to items in m_Format combo
    UString desc;
    UStringVector masks;
    CStringFinder finder;

    for (unsigned i = 0; i < numFormats; i++)
    {
      const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData(i)];
      CBrowseFilterInfo &f = filters.AddNew();
      f.Description = ai.Name;
      f.Description += " (";
      bool needSpace_desc = false;

      FOR_VECTOR (k, ai.Exts)
      {
        const UString &ext = ai.Exts[k].Ext;
        UString mask ("*.");
        mask += ext;

        if (finder.FindWord_In_LowCaseAsciiList_NoCase(k_DontSave_Exts, ext))
          continue;

        f.Masks.Add(mask);
        masks.Add(mask);
        if (needSpace_desc)
          f.Description.Add_Space();
        needSpace_desc = true;
        f.Description += ext;
      }
      f.Description += ")";
      // we use only main ext in desc to reduce the size of list
      if (i != 0)
        desc.Add_Space();
      desc += ai.GetMainExt();
    }

    CBrowseFilterInfo &f = filters.AddNew();
    f.Description = LangString(IDT_COMPRESS_ARCHIVE); // IDS_ARCHIVES_COLON;
    if (f.Description.IsEmpty())
      GetItemText(IDT_COMPRESS_ARCHIVE, f.Description);
    f.Description.RemoveChar(L'&');
    // f.Description = "archive";
    f.Description += " (";
    f.Description += desc;
    f.Description += ")";
    f.Masks = masks;
  }

  AddFilter(filters, LangString(IDS_OPEN_TYPE_ALL_FILES), UString("*"));
  if (filterIndex < 0)
    filterIndex = (int)filters.Size() - 1;

  const UString title = LangString(IDS_COMPRESS_SET_ARCHIVE_BROWSE);
  CBrowseInfo bi;
  bi.lpstrTitle = title;
  bi.SaveMode = true;
  bi.FilterIndex = filterIndex;
  bi.hwndOwner = *this;
  bi.FilePath = path;

  if (!bi.BrowseForFile(filters))
    return;
  
  path = bi.FilePath;

  if (isSFX)
  {
    const int dotPos = GetExtDotPos(path);
    if (dotPos >= 0)
      path.DeleteFrom(dotPos);
    path += kExeExt;
  }
  else
  // if (bi.FilterIndex >= 0)
  // if (bi.FilterIndex != filterIndex)
  if ((unsigned)bi.FilterIndex < numFormats)
  {
    // archive format was confirmed. So we try to set format extension
    bool needAddExt = true;
    const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData((unsigned)bi.FilterIndex)];
    const int dotPos = GetExtDotPos(path);
    if (dotPos >= 0)
    {
      const UString ext = path.Ptr(dotPos + 1);
      if (ai.FindExtension(ext) >= 0)
        needAddExt = false;
    }
    if (needAddExt)
    {
      if (path.IsEmpty() || path.Back() != '.')
        path.Add_Dot();
      path += ai.GetMainExt();
    }
  }

  SetArcPathFields(path);

  if (!isSFX)
  if ((unsigned)bi.FilterIndex < numFormats)
  if (bi.FilterIndex != m_Format.GetCurSel())
  {
    m_Format.SetCurSel(bi.FilterIndex);
    SaveOptionsInMem();
    FormatChanged(true); // isChanged
    return;
  }
  
  ArcPath_WasChanged(path);
}


// in ExtractDialog.cpp
extern void AddUniqueString(UStringVector &strings, const UString &srcString);

static bool IsAsciiString(const UString &s)
{
  for (unsigned i = 0; i < s.Len(); i++)
  {
    const wchar_t c = s[i];
    if (c < 0x20 || c > 0x7F)
      return false;
  }
  return true;
}


static void AddSize_MB(UString &s, UInt64 size)
{
  s.Add_LF();
  const UInt64 v2 = size + ((UInt32)1 << 20) - 1;
  if (size < v2)
      size = v2;
  s.Add_UInt64(size >> 20);
  s += " MB : ";
}

static void AddSize_MB_id(UString &s, UInt64 size, UInt32 id)
{
  AddSize_MB(s, size);
  AddLangString(s, id);
}

void SetErrorMessage_MemUsage(UString &s, UInt64 reqSize, UInt64 ramSize, UInt64 ramLimit, const UString &usageString);
void SetErrorMessage_MemUsage(UString &s, UInt64 reqSize, UInt64 ramSize, UInt64 ramLimit, const UString &usageString)
{
  AddLangString(s, IDS_MEM_OPERATION_BLOCKED);
  s.Add_LF();
  AddLangString(s, IDS_MEM_REQUIRES_BIG_MEM);
  s.Add_LF();
  AddSize_MB(s, reqSize);
  s += usageString;
  AddSize_MB_id(s, ramSize, IDS_MEM_RAM_SIZE);
  // if (ramLimit != 0)
  {
    AddSize_MB_id(s, ramLimit, IDS_MEM_USAGE_LIMIT_SET_BY_7ZIP);
  }
  s.Add_LF();
  s.Add_LF();
  AddLangString(s, IDS_MEM_ERROR);
}


void CCompressDialog::OnOK()
{
  _password1Control.GetText(Info.Password);
  if (IsZipFormat())
  {
    if (!IsAsciiString(Info.Password))
    {
      ShowErrorMessageHwndRes(*this, IDS_PASSWORD_USE_ASCII);
      return;
    }
    UString method = GetEncryptionMethodSpec();
    if (method.IsPrefixedBy_Ascii_NoCase("aes"))
    {
      if (Info.Password.Len() > 99)
      {
        ShowErrorMessageHwndRes(*this, IDS_PASSWORD_TOO_LONG);
        return;
      }
    }
  }
  if (!IsShowPasswordChecked())
  {
    UString password2;
    _password2Control.GetText(password2);
    if (password2 != Info.Password)
    {
      ShowErrorMessageHwndRes(*this, IDS_PASSWORD_NOT_MATCH);
      return;
    }
  }

  {
    UInt64 decompressMem;
    const UInt64 memUsage = GetMemoryUsage_DecompMem(decompressMem);
    if (memUsage != (UInt64)(Int64)-1)
    {
      const UInt64 limit = Get_MemUse_Bytes();
      if (memUsage > limit)
      {
        UString s2;
        LangString_OnlyFromLangFile(IDS_MEM_REQUIRED_MEM_SIZE, s2);
        if (s2.IsEmpty())
        {
          s2 = LangString(IDT_COMPRESS_MEMORY);
          if (s2.IsEmpty())
            GetItemText(IDT_COMPRESS_MEMORY, s2);
          s2.RemoveChar(L':');
        }
        UString s;
        SetErrorMessage_MemUsage(s, memUsage, _ramSize, limit, s2);
        MessageBoxError(s);
        return;
      }
    }
  }

  SaveOptionsInMem();

  UStringVector arcPaths;
  {
    UString s;
    if (!GetFinalPath_Smart(s))
    {
      ShowErrorMessage(*this, k_IncorrectPathMessage);
      return;
    }
    Info.ArcPath = s;
    AddUniqueString(arcPaths, s);
  }
  
  Info.UpdateMode = (NCompressDialog::NUpdateMode::EEnum)k_UpdateMode_Vals[m_UpdateMode.GetCurSel()];
  Info.PathMode = (NWildcard::ECensorPathMode)k_PathMode_Vals[m_PathMode.GetCurSel()];

  Info.Level = GetLevelSpec();
  Info.Dict64 = GetDictSpec();
  // Info.Dict64_Chain = GetDictChainSpec();
  Info.Order = GetOrderSpec();
  Info.OrderMode = GetOrderMode();
  Info.NumThreads = GetNumThreadsSpec();

  Info.MemUsage.Clear();
  {
    const UString mus = Get_MemUse_Spec();
    if (!mus.IsEmpty())
    {
      NCompression::CMemUse mu;
      mu.Parse(mus);
      if (mu.IsDefined)
        Info.MemUsage = mu;
    }
  }

  {
    // Info.SolidIsSpecified = g_Formats[GetStaticFormatIndex()].Solid;
    const UInt32 solidLogSize = GetBlockSizeSpec();
    Info.SolidBlockSize = 0;
    if (solidLogSize == (UInt32)(Int32)-1)
      Info.SolidIsSpecified = false;
    else if (solidLogSize > 0)
      Info.SolidBlockSize = (solidLogSize >= 64) ?
          (UInt64)(Int64)-1 :
          ((UInt64)1 << solidLogSize);
  }

  Info.Method = GetMethodSpec();
  Info.EncryptionMethod = GetEncryptionMethodSpec();
  Info.FormatIndex = (int)GetFormatIndex();
  Info.SFXMode = IsSFX();
  Info.OpenShareForWrite = IsButtonCheckedBool(IDX_COMPRESS_SHARED);
  Info.DeleteAfterCompressing = IsButtonCheckedBool(IDX_COMPRESS_DEL);

  m_RegistryInfo.EncryptHeaders =
    Info.EncryptHeaders = IsButtonCheckedBool(IDX_COMPRESS_ENCRYPT_FILE_NAMES);


  /* (Info) is for saving to registry:
     (CBoolPair::Val) will be set as (false), if it was (false)
       in registry at dialog creation, and user didn't click checkbox.
     in another case (CBoolPair::Val) will be set as (true) */

  {
    /* Info properties could be for another archive types.
       so we disable unsupported properties in Info */
    // const CArcInfoEx &ai = Get_ArcInfoEx();

    SET_FINAL_BOOL_PAIRS (SymLinks);
    SET_FINAL_BOOL_PAIRS (HardLinks);
    SET_FINAL_BOOL_PAIRS (AltStreams);
    SET_FINAL_BOOL_PAIRS (NtSecurity);

    SET_FINAL_BOOL_PAIRS (PreserveATime);
  }

  {
    const NCompression::CFormatOptions &fo = Get_FormatOptions();

    Info.TimePrec = fo.TimePrec;
    Info.MTime = fo.MTime;
    Info.CTime = fo.CTime;
    Info.ATime = fo.ATime;
    Info.SetArcMTime = fo.SetArcMTime;
  }

  m_Params.GetText(Info.Options);
  
  UString volumeString;
  m_Volume.GetText(volumeString);
  volumeString.Trim();
  Info.VolumeSizes.Clear();
  
  if (!volumeString.IsEmpty())
  {
    if (!ParseVolumeSizes(volumeString, Info.VolumeSizes))
    {
      ShowErrorMessageHwndRes(*this, IDS_INCORRECT_VOLUME_SIZE);
      return;
    }
    if (!Info.VolumeSizes.IsEmpty())
    {
      const UInt64 volumeSize = Info.VolumeSizes.Back();
      if (volumeSize < (100 << 10))
      {
        wchar_t s[32];
        ConvertUInt64ToString(volumeSize, s);
        if (::MessageBoxW(*this, MyFormatNew(IDS_SPLIT_CONFIRM, s),
            L"7-Zip", MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES)
          return;
      }
    }
  }

  if (Info.FormatIndex >= 0)
    m_RegistryInfo.ArcType = (*ArcFormats)[Info.FormatIndex].Name;
  m_RegistryInfo.ShowPassword = IsShowPasswordChecked();

  FOR_VECTOR (i, m_RegistryInfo.ArcPaths)
  {
    if (arcPaths.Size() >= kHistorySize)
      break;
    AddUniqueString(arcPaths, m_RegistryInfo.ArcPaths[i]);
  }
  m_RegistryInfo.ArcPaths = arcPaths;

  m_RegistryInfo.Save();
  
  CModalDialog::OnOK();
}

#define kHelpTopic         "fm/plugins/7-zip/add.htm"
#define kHelpTopic_Options "fm/plugins/7-zip/add.htm#options"

void CCompressDialog::OnHelp()
{
  ShowHelpWindow(kHelpTopic);
}


void CCompressDialog::ArcPath_WasChanged(const UString &path)
{
  const int dotPos = GetExtDotPos(path);
  if (dotPos < 0)
    return;
  const UString ext = path.Ptr(dotPos + 1);
  {
    const CArcInfoEx &ai = Get_ArcInfoEx();
    if (ai.FindExtension(ext) >= 0)
      return;
  }

  const unsigned count = (unsigned)m_Format.GetCount();
  for (unsigned i = 0; i < count; i++)
  {
    const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData(i)];
    if (ai.FindExtension(ext) >= 0)
    {
      m_Format.SetCurSel(i);
      SaveOptionsInMem();
      FormatChanged(true); // isChanged
      return;
    }
  }
}


bool CCompressDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
    case k_Message_ArcChanged:
    {
      // UString path;
      // m_ArchivePath.GetText(path);
      const int select = m_ArchivePath.GetCurSel();
      if ((unsigned)select < m_RegistryInfo.ArcPaths.Size())
      // if (path == m_RegistryInfo.ArcPaths[select])
      {
        const UString &path = m_RegistryInfo.ArcPaths[select];
        SetArcPathFields(path);
        // ArcPath_WasChanged(path);
      }
      return 0;
    }
  }
  return CModalDialog::OnMessage(message, wParam, lParam);
}


bool CCompressDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam)
{
  if (code == CBN_SELCHANGE)
  {
    switch (itemID)
    {
      case IDC_COMPRESS_ARCHIVE:
      {
        /* CBN_SELCHANGE is called before actual value of combo text will be changed.
           So GetText() here returns old value (before change) of combo text.
           So here we can change all controls except of m_ArchivePath.
        */
        const int select = m_ArchivePath.GetCurSel();
        if ((unsigned)select < m_RegistryInfo.ArcPaths.Size())
        {
          // DirPrefix.Empty();
          // SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, DirPrefix);
          const UString &path = m_RegistryInfo.ArcPaths[select];
          // SetArcPathFields(path);
          ArcPath_WasChanged(path);
          // we use PostMessage(k_Message_ArcChanged) here that later will change m_ArchivePath control
          PostMsg(k_Message_ArcChanged);
        }
        return true;
      }
      
      case IDC_COMPRESS_FORMAT:
      {
        const bool isSFX = IsSFX();
        SaveOptionsInMem();
        FormatChanged(true); // isChanged
        SetArchiveName2(isSFX);
        return true;
      }
      
      case IDC_COMPRESS_LEVEL:
      {
        Get_FormatOptions().ResetForLevelChange();

        SetMethod(); // call it if level changes method

        // call the following if level change keeps old method
        /*
        {
          // try to keep old method
          SetMethod(GetMethodID());
          MethodChanged();
        }
        */

        SetSolidBlockSize();
        SetNumThreads();
        CheckSFXNameChange();
        SetMemoryUsage();
        return true;
      }
      
      case IDC_COMPRESS_METHOD:
      {
        MethodChanged();
        SetSolidBlockSize();
        SetNumThreads();
        CheckSFXNameChange();
        SetMemoryUsage();
        if (Get_ArcInfoEx().Flags_HashHandler())
          SetArchiveName2(false);

        return true;
      }
      
      case IDC_COMPRESS_DICTIONARY:
      // case IDC_COMPRESS_DICTIONARY2:
      {
        /* we want to change the reported threads for Auto line
           and keep selected NumThreads option
           So we save selected NumThreads option in memory */
        SaveOptionsInMem();
        const UInt32 blockSizeLog = GetBlockSizeSpec();
        if (// blockSizeLog != (UInt32)(Int32)-1 &&
               blockSizeLog != kSolidLog_NoSolid
            && blockSizeLog != kSolidLog_FullSolid)
        {
          Get_FormatOptions().Reset_BlockLogSize();
          // SetSolidBlockSize(true);
        }

        SetDictionary2();
        SetSolidBlockSize();
        SetNumThreads(); // we want to change the reported threads for Auto line only
        SetMemoryUsage();
        return true;
      }
      
      case IDC_COMPRESS_ORDER:
      {
       #ifdef PRINT_PARAMS
        Print_Params();
       #endif
        return true;
      }
      
      case IDC_COMPRESS_SOLID:
      {
        SetMemoryUsage();
        return true;
      }

      case IDC_COMPRESS_THREADS:
      {
        SetMemoryUsage();
        return true;
      }
      
      case IDC_COMPRESS_MEM_USE:
      {
        /* we want to change the reported threads for Auto line
           and keep selected NumThreads option
           So we save selected NumThreads option in memory */
        SaveOptionsInMem();

        SetNumThreads(); // we want to change the reported threads for Auto line only
        SetMemoryUsage();
        return true;
      }
    }
  }
  return CModalDialog::OnCommand(code, itemID, lParam);
}

void CCompressDialog::CheckSFXNameChange()
{
  const bool isSFX = IsSFX();
  CheckSFXControlsEnable();
  if (isSFX != IsSFX())
    SetArchiveName2(isSFX);
}

void CCompressDialog::SetArchiveName2(bool prevWasSFX)
{
  UString fileName;
  m_ArchivePath.GetText(fileName);
  const CArcInfoEx &prevArchiverInfo = (*ArcFormats)[m_PrevFormat];
  if (prevArchiverInfo.Flags_KeepName() || Info.KeepName)
  {
    UString prevExtension;
    if (prevWasSFX)
      prevExtension = kExeExt;
    else
    {
      prevExtension.Add_Dot();
      prevExtension += prevArchiverInfo.GetMainExt();
    }
    const unsigned prevExtensionLen = prevExtension.Len();
    if (fileName.Len() >= prevExtensionLen)
      if (StringsAreEqualNoCase(fileName.RightPtr(prevExtensionLen), prevExtension))
        fileName.DeleteFrom(fileName.Len() - prevExtensionLen);
  }
  SetArchiveName(fileName);
}

// if type.KeepName then use OriginalFileName
// else if !KeepName remove extension
// add new extension

void CCompressDialog::SetArchiveName(const UString &name)
{
  UString fileName = name;
  Info.FormatIndex = (int)GetFormatIndex();
  const CArcInfoEx &ai = (*ArcFormats)[Info.FormatIndex];
  m_PrevFormat = Info.FormatIndex;
  if (ai.Flags_KeepName())
  {
    fileName = OriginalFileName;
  }
  else
  {
    if (!Info.KeepName)
    {
      int dotPos = GetExtDotPos(fileName);
      if (dotPos >= 0)
        fileName.DeleteFrom(dotPos);
    }
  }

  if (IsSFX())
    fileName += kExeExt;
  else
  {
    fileName.Add_Dot();
    UString ext = ai.GetMainExt();
    if (ai.Flags_HashHandler())
    {
      UString estimatedName;
      GetMethodSpec(estimatedName);
      if (!estimatedName.IsEmpty())
      {
        ext = estimatedName;
        ext.MakeLower_Ascii();
      }
    }
    fileName += ext;
  }
  m_ArchivePath.SetText(fileName);
}


int CCompressDialog::FindRegistryFormat(const UString &name)
{
  FOR_VECTOR (i, m_RegistryInfo.Formats)
  {
    const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[i];
    if (name.IsEqualTo_NoCase(GetUnicodeString(fo.FormatID)))
      return (int)i;
  }
  return -1;
}


unsigned CCompressDialog::FindRegistryFormat_Always(const UString &name)
{
  const int index = FindRegistryFormat(name);
  if (index >= 0)
    return (unsigned)index;
  {
    NCompression::CFormatOptions fo;
    fo.FormatID = GetSystemString(name);
    return m_RegistryInfo.Formats.Add(fo);
  }
}


NCompression::CFormatOptions &CCompressDialog::Get_FormatOptions()
{
  const CArcInfoEx &ai = Get_ArcInfoEx();
  return m_RegistryInfo.Formats[FindRegistryFormat_Always(ai.Name)];
}


unsigned CCompressDialog::GetStaticFormatIndex()
{
  const CArcInfoEx &ai = Get_ArcInfoEx();
  for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Formats); i++)
    if (ai.Name.IsEqualTo_Ascii_NoCase(g_Formats[i].Name))
      return i;
  return 0; // -1;
}

void CCompressDialog::SetNearestSelectComboBox(NControl::CComboBox &comboBox, UInt32 value)
{
  for (int i = comboBox.GetCount() - 1; i >= 0; i--)
    if ((UInt32)comboBox.GetItemData(i) <= value)
    {
      comboBox.SetCurSel(i);
      return;
    }
  if (comboBox.GetCount() > 0)
    comboBox.SetCurSel(0);
}

void CCompressDialog::SetLevel2()
{
  m_Level.ResetContent();
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  const CArcInfoEx &ai = Get_ArcInfoEx();
  UInt32 level = 5;
  {
    int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (fo.Level <= 9)
        level = fo.Level;
      else if (fo.Level == (UInt32)(Int32)-1)
        level = 5;
      else
        level = 9;
    }
  }
  
  const bool isZstd = ai.Is_Zstd();

  for (unsigned i = 0; i < sizeof(UInt32) * 8; i++)
  {
    const UInt32 mask = fi.LevelsMask >> i;
    // if (mask == 0) break;
    if (mask & 1)
    {
      UString s;
      s.Add_UInt32(i);
      if (i < Z7_ARRAY_SIZE(g_Levels))
      {
        const UInt32 langID = g_Levels[i];
        // if (fi.LevelsMask < (1 << (MY_ZSTD_LEVEL_MAX + 1)) - 1)
        if (langID)
          if (i != 0 || !isZstd)
          {
            s += " - ";
            AddLangString(s, langID);
          }
      }
      const int index = (int)m_Level.AddString(s);
      m_Level.SetItemData(index, (LPARAM)i);
    }
  }
  SetNearestSelectComboBox(m_Level, level);
}


static LRESULT ComboBox_AddStringAscii(NControl::CComboBox &cb, const char *s)
{
  return cb.AddString((CSysString)s);
}

static const char *k_Auto_Prefix = "*  ";

static void Modify_Auto(AString &s)
{
  s.Insert(0, k_Auto_Prefix);
}

void CCompressDialog::SetMethod2(int keepMethodId)
{
  m_Method.ResetContent();
  _auto_MethodId = -1;
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  const CArcInfoEx &ai = Get_ArcInfoEx();
  if (GetLevel() == 0 && !ai.Flags_HashHandler())
  {
    if (!ai.Is_Tar() &&
        !ai.Is_Zstd())
    {
      MethodChanged();
      return;
    }
  }
  UString defaultMethod;
  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      defaultMethod = fo.Method;
    }
  }
  const bool isSfx = IsSFX();
  bool weUseSameMethod = false;

  const bool is7z = ai.Is_7z();
  
  for (unsigned m = 0;; m++)
  {
    int methodID;
    const char *method;
    if (m < fi.NumMethods)
    {
      methodID = fi.MethodIDs[m];
      method = kMethodsNames[methodID];
      if (is7z)
      if (methodID == kCopy
          || methodID == kDeflate
          || methodID == kDeflate64
          )
        continue;
    }
    else
    {
      if (!is7z)
        break;
      const unsigned extIndex = m - fi.NumMethods;
      if (extIndex >= ExternalMethods.Size())
        break;
      methodID = (int)(Z7_ARRAY_SIZE(kMethodsNames) + extIndex);
      method = ExternalMethods[extIndex].Ptr();
    }
    if (isSfx)
      if (!IsMethodSupportedBySfx(methodID))
        continue;

    AString s (method);
    int writtenMethodId = methodID;
    if (m == 0)
    {
      _auto_MethodId = methodID;
      writtenMethodId = -1;
      Modify_Auto(s);
    }
    const int itemIndex = (int)ComboBox_AddStringAscii(m_Method, s);
    m_Method.SetItemData(itemIndex, writtenMethodId);
    if (keepMethodId == methodID)
    {
      m_Method.SetCurSel(itemIndex);
      weUseSameMethod = true;
      continue;
    }
    if ((defaultMethod.IsEqualTo_Ascii_NoCase(method) || m == 0) && !weUseSameMethod)
      m_Method.SetCurSel(itemIndex);
  }
  
  if (!weUseSameMethod)
    MethodChanged();
}



bool CCompressDialog::IsZipFormat()
{
  return Get_ArcInfoEx().Is_Zip();
}

bool CCompressDialog::IsXzFormat()
{
  return Get_ArcInfoEx().Is_Xz();
}

void CCompressDialog::SetEncryptionMethod()
{
  _encryptionMethod.ResetContent();
  _default_encryptionMethod_Index = -1;
  const CArcInfoEx &ai = Get_ArcInfoEx();
  if (ai.Is_7z())
  {
    ComboBox_AddStringAscii(_encryptionMethod, "AES-256");
    _encryptionMethod.SetCurSel(0);
    _default_encryptionMethod_Index = 0;
  }
  else if (ai.Is_Zip())
  {
    int index = FindRegistryFormat(ai.Name);
    UString encryptionMethod;
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      encryptionMethod = fo.EncryptionMethod;
    }
    int sel = 0;
    // if (ZipCryptoIsAllowed)
    {
      ComboBox_AddStringAscii(_encryptionMethod, "ZipCrypto");
      sel = (encryptionMethod.IsPrefixedBy_Ascii_NoCase("aes") ? 1 : 0);
      _default_encryptionMethod_Index = 0;
    }
    ComboBox_AddStringAscii(_encryptionMethod, "AES-256");
    _encryptionMethod.SetCurSel(sel);
  }
}


int CCompressDialog::GetMethodID_RAW()
{
  if (m_Method.GetCount() <= 0)
    return -1;
  return (int)(Int32)(UInt32)m_Method.GetItemData_of_CurSel();
}

int CCompressDialog::GetMethodID()
{
  int raw = GetMethodID_RAW();
  if (raw < 0)
    return _auto_MethodId;
  return raw;
}


UString CCompressDialog::GetMethodSpec(UString &estimatedName)
{
  estimatedName.Empty();
  if (m_Method.GetCount() < 1)
    return estimatedName;
  const int methodIdRaw = GetMethodID_RAW();
  int methodId = methodIdRaw;
  if (methodIdRaw < 0)
    methodId = _auto_MethodId;
  UString s;
  if (methodId >= 0)
  {
    if ((unsigned)methodId < Z7_ARRAY_SIZE(kMethodsNames))
      estimatedName = kMethodsNames[methodId];
    else
      estimatedName = ExternalMethods[(unsigned)methodId - (unsigned)Z7_ARRAY_SIZE(kMethodsNames)];
    if (methodIdRaw >= 0)
      s = estimatedName;
  }
  return s;
}


UString CCompressDialog::GetMethodSpec()
{
  UString estimatedName;
  UString s = GetMethodSpec(estimatedName);
  return s;
}

bool CCompressDialog::IsMethodEqualTo(const UString &s)
{
  UString estimatedName;
  const UString shortName = GetMethodSpec(estimatedName);
  if (s.IsEmpty())
    return shortName.IsEmpty();
  return s.IsEqualTo_NoCase(estimatedName);
}


UString CCompressDialog::GetEncryptionMethodSpec()
{
  UString s;
  if (_encryptionMethod.GetCount() > 0
      && _encryptionMethod.GetCurSel() != _default_encryptionMethod_Index)
  {
    _encryptionMethod.GetText(s);
    s.RemoveChar(L'-');
  }
  return s;
}


static const size_t k_Auto_Dict = (size_t)0 - 1;

static int Combo_AddDict2(NWindows::NControl::CComboBox &cb, size_t sizeReal, size_t sizeShow)
{
  char c = 0;
  unsigned moveBits = 0;
       if ((sizeShow & 0xFFFFF) == 0) { moveBits = 20; c = 'M'; }
  else if ((sizeShow &   0x3FF) == 0) { moveBits = 10; c = 'K'; }
  AString s;
  s.Add_UInt64(sizeShow >> moveBits);
  s.Add_Space();
  if (c != 0)
    s.Add_Char(c);
  s.Add_Char('B');
  if (sizeReal == k_Auto_Dict)
    Modify_Auto(s);
  const int index = (int)ComboBox_AddStringAscii(cb, s);
  cb.SetItemData(index, (LPARAM)sizeReal);
  return index;
}

int CCompressDialog::AddDict2(size_t sizeReal, size_t sizeShow)
{
  return Combo_AddDict2(m_Dictionary, sizeReal, sizeShow);
}

int CCompressDialog::AddDict(size_t size)
{
  return AddDict2(size, size);
}

/*
int CCompressDialog::AddDict_Chain(size_t size)
{
  return Combo_AddDict2(m_Dictionary_Chain, size, size);
}
*/

void CCompressDialog::SetDictionary2()
{
  m_Dictionary.ResetContent();
  // m_Dictionary_Chain.ResetContent();
  
  // _auto_Dict = (UInt32)1 << 24; // we can use this dictSize to calculate _auto_Solid for unknown method for 7z
  _auto_Dict = (UInt32)(Int32)-1; // for debug
  // _auto_Dict_Chain = (UInt32)(Int32)-1; // for debug

  const CArcInfoEx &ai = Get_ArcInfoEx();
  UInt32 defaultDict = (UInt32)(Int32)-1;
  // UInt32 defaultDict_Chain = (UInt32)(Int32)-1;
  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method))
      {
        defaultDict = fo.Dictionary;
        // defaultDict_Chain = fo.DictionaryChain;
      }
    }
  }
  
  const int methodID = GetMethodID();
  const UInt32 level = GetLevel2();

  {
    RECT r, rLabel;
    GetClientRectOfItem(IDT_COMPRESS_DICTIONARY, rLabel);
    GetClientRectOfItem(IDC_COMPRESS_DICTIONARY, r);
    if (_dictionaryCombo_left == 0)
      _dictionaryCombo_left = r.left;

    // bool showDict2;
    int newLableRight;
    int newDictLeft;

    /*
    if (methodID == kZSTD)
    {
      showDict2 = true;
      newDictLeft = _dictionaryCombo_left;
      RECT r2;
      GetClientRectOfItem(IDC_COMPRESS_DICTIONARY2, r2);
      newLableRight = r2.left;
    }
    else
    */
    {
      // showDict2 = false;
      RECT rBig;
      GetClientRectOfItem(IDC_COMPRESS_METHOD, rBig);
      newDictLeft= rBig.left;
      newLableRight = newDictLeft;
    }
    
    if (newLableRight != rLabel.right)
    {
      rLabel.right = newLableRight;
      MoveItem_RECT(IDT_COMPRESS_DICTIONARY, rLabel);
      InvalidateRect(&rLabel);
    }
    if (newDictLeft != r.left)
    {
      r.left = newDictLeft;
      MoveItem_RECT(IDC_COMPRESS_DICTIONARY, r);
      // InvalidateRect(&r);
    }
    // ShowItem_Bool(IDC_COMPRESS_DICTIONARY2, showDict2);
  }

  if (methodID < 0)
    return;
  
  switch (methodID)
  {
    case kLZMA:
    case kLZMA2:
    {
      {
        _auto_Dict = level <= 4 ?
            (UInt32)1 << (level * 2 + 16) :
            level <= sizeof(size_t) / 2 + 4 ?
              (UInt32)1 << (level + 20) :
              (UInt32)1 << (sizeof(size_t) / 2 + 24);
      }

      // we use threshold 3.75 GiB to switch to kLzmaMaxDictSize.
      if (defaultDict != (UInt32)(Int32)-1
          && defaultDict >= ((UInt32)15 << 28))
        defaultDict = kLzmaMaxDictSize;
      
      const size_t kLzmaMaxDictSize_Up = (size_t)1 << (20 + sizeof(size_t) / 4 * 6);
      
      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);

      for (unsigned i = (16 - 1) * 2; i <= (32 - 1) * 2; i++)
      {
        if (i < (20 - 1) * 2
            && i != (16 - 1) * 2
            && i != (18 - 1) * 2)
          continue;
        if (i == (20 - 1) * 2 + 1)
          continue;
        const size_t dict_up = (size_t)(2 + (i & 1)) << (i / 2);
        size_t dict = dict_up;
        if (dict_up >= kLzmaMaxDictSize)
          dict = kLzmaMaxDictSize; // we reduce dictionary
        
        const int index = AddDict(dict);
        // AddDict2(dict, dict_up); // for debug : we show 4 GB

        // const UInt32 numThreads = 2;
        // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(numThreads, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (dict <= defaultDict || curSel <= 0)
          // if (!maxRamSize_Defined || memUsage <= maxRamSize)
            curSel = index;
        if (dict_up >= kLzmaMaxDictSize_Up)
          break;
      }
      
      m_Dictionary.SetCurSel(curSel);
      break;
    }

    /*
    case kZSTD:
    {
      if (defaultDict != (UInt32)(Int32)-1 &&
          defaultDict > kZstd_MAX_DictSize)
        defaultDict = kZstd_MAX_DictSize;

      if (defaultDict_Chain != (UInt32)(Int32)-1 &&
          defaultDict_Chain > kZstd_MAX_DictSize_Chain)
        defaultDict_Chain = kZstd_MAX_DictSize_Chain;

      {
        CZstdEncProps props;
        ZstdEncProps_Init(&props);
        // props.level_zstd = level;
        props.level_7z = level;
        ZstdEncProps_Set_WindowSize(&props, defaultDict != (UInt32)(Int32)-1 ? defaultDict: 0);
        ZstdEncProps_NormalizeFull(&props);
        _auto_Dict_Chain = (UInt32)1 << props.windowLog_Chain;
      }
      {
        CZstdEncProps props;
        ZstdEncProps_Init(&props);
        // props.level_zstd = level;
        props.level_7z = level;
        ZstdEncProps_Set_WindowChainSize(&props, defaultDict_Chain != (UInt32)(Int32)-1 ? defaultDict_Chain: 0);
        ZstdEncProps_NormalizeFull(&props);
        _auto_Dict = (UInt32)1 << props.windowLog;
      }

      // if there is collision of two window sizes, we reduce dict_Chain
      if (defaultDict != (UInt32)(Int32)-1 &&
          defaultDict_Chain != (UInt32)(Int32)-1 &&
          defaultDict < defaultDict_Chain)
        defaultDict_Chain = defaultDict;

      {
        int curSel = AddDict2(k_Auto_Dict, _auto_Dict);

        // defaultDict = 12 << 10; // for debug
        const UInt32 kWinStart = 18;
        if (defaultDict != 0 && defaultDict < ((UInt32)1 << kWinStart))
          curSel = AddDict(defaultDict);
        
        for (unsigned i = kWinStart; i <= MY_ZSTD_WINDOWLOG_MAX; i++)
        {
          const size_t dict = (size_t)1 << i;
          const int index = AddDict(dict);
          if (defaultDict != (UInt32)(Int32)-1)
            if (dict <= defaultDict || curSel <= 0)
              curSel = index;
        }
        m_Dictionary.SetCurSel(curSel);
      }

      {
        int curSel = Combo_AddDict2(m_Dictionary_Chain, k_Auto_Dict, _auto_Dict_Chain);
        
        // defaultDict_Chain = 10 << 10; // for debug
        const UInt32 kWinChainStart = 15;
        if (defaultDict_Chain != 0 && defaultDict_Chain < ((UInt32)1 << kWinChainStart))
          curSel = AddDict_Chain(defaultDict_Chain);

        for (unsigned i = kWinChainStart; i <= kMaxDictChain; i++)
        {
          const size_t dict = (size_t)1 << i;
          if (defaultDict != (UInt32)(Int32)-1 && dict > defaultDict)
            break;
          const int index = AddDict_Chain(dict);
          if (defaultDict_Chain != (UInt32)(Int32)-1)
            if (dict <= defaultDict_Chain || curSel <= 0)
              curSel = index;
        }
        m_Dictionary_Chain.SetCurSel(curSel);
      }

      break;
    }
    */
    
    case kPPMd:
    {
      _auto_Dict = (UInt32)1 << (level + 19);

      const UInt32 kPpmd_Default_4g = (UInt32)0 - ((UInt32)1 << 10);
      const size_t kPpmd_MaxDictSize_Up = (size_t)1 << (29 + sizeof(size_t) / 8);

      if (defaultDict != (UInt32)(Int32)-1
          && defaultDict >= ((UInt32)15 << 28)) // threshold
        defaultDict = kPpmd_Default_4g;

      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);

      for (unsigned i = (20 - 1) * 2; i <= (32 - 1) * 2; i++)
      {
        if (i == (20 - 1) * 2 + 1)
          continue;

        const size_t dict_up = (size_t)(2 + (i & 1)) << (i / 2);
        size_t dict = dict_up;
        if (dict_up >= kPpmd_Default_4g)
          dict = kPpmd_Default_4g;

        const int index = AddDict2(dict, dict_up);
        // AddDict2((UInt32)((UInt32)0 - 2), dict_up); // for debug
        // AddDict(dict_up); // for debug
        // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(1, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (dict <= defaultDict || curSel <= 0)
            // if (!maxRamSize_Defined || memUsage <= maxRamSize)
            curSel = index;
        if (dict_up >= kPpmd_MaxDictSize_Up)
          break;
      }
      m_Dictionary.SetCurSel(curSel);
      break;
    }

    case kPPMdZip:
    {
      _auto_Dict = (UInt32)1 << (level + 19);
      
      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);

      for (unsigned i = 20; i <= 28; i++)
      {
        const UInt32 dict = (UInt32)1 << i;
        const int index = AddDict(dict);
        // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(1, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (dict <= defaultDict || curSel <= 0)
            // if (!maxRamSize_Defined || memUsage <= maxRamSize)
            curSel = index;
      }
      m_Dictionary.SetCurSel(curSel);
      break;
    }

    case kDeflate:
    case kDeflate64:
    {
      const UInt32 dict = (methodID == kDeflate ? (UInt32)(1 << 15) : (UInt32)(1 << 16));
      _auto_Dict = dict;
      AddDict2(k_Auto_Dict, _auto_Dict);
      m_Dictionary.SetCurSel(0);
      // EnableItem(IDC_COMPRESS_DICTIONARY, false);
      break;
    }
    
    case kBZip2:
    {
      {
             if (level >= 5) _auto_Dict = (900 << 10);
        else if (level >= 3) _auto_Dict = (500 << 10);
        else                 _auto_Dict = (100 << 10);
      }

      int curSel = AddDict2(k_Auto_Dict, _auto_Dict);
      
      for (unsigned i = 1; i <= 9; i++)
      {
        const UInt32 dict = ((UInt32)i * 100) << 10;
        AddDict(dict);
        // AddDict2(i * 100000, dict);
        if (defaultDict != (UInt32)(Int32)-1)
          if (i <= defaultDict / 100000 || curSel <= 0)
            curSel = m_Dictionary.GetCount() - 1;
      }
      m_Dictionary.SetCurSel(curSel);
      break;
    }

    case kCopy:
    {
      _auto_Dict = 0;
      AddDict(0);
      m_Dictionary.SetCurSel(0);
      break;
    }
  }
}


UInt32 CCompressDialog::GetComboValue(NWindows::NControl::CComboBox &c, int defMax)
{
  if (c.GetCount() <= defMax)
    return (UInt32)(Int32)-1;
  return (UInt32)c.GetItemData_of_CurSel();
}


UInt64 CCompressDialog::GetComboValue_64(NWindows::NControl::CComboBox &c, int defMax)
{
  if (c.GetCount() <= defMax)
    return (UInt64)(Int64)-1;
  // LRESULT is signed. so we cast it to unsigned size_t at first:
  LRESULT val = c.GetItemData_of_CurSel();
  if (val == (LPARAM)(INT_PTR)(-1))
    return (UInt64)(Int64)-1;
  return (UInt64)(size_t)c.GetItemData_of_CurSel();
}

UInt32 CCompressDialog::GetLevel2()
{
  UInt32 level = GetLevel();
  if (level == (UInt32)(Int32)-1)
    level = 5;
  return level;
}


int CCompressDialog::AddOrder(UInt32 size)
{
  char s[32];
  ConvertUInt32ToString(size, s);
  const int index = (int)ComboBox_AddStringAscii(m_Order, s);
  m_Order.SetItemData(index, (LPARAM)size);
  return index;
}

int CCompressDialog::AddOrder_Auto()
{
  AString s;
  s.Add_UInt32(_auto_Order);
  Modify_Auto(s);
  int index = (int)ComboBox_AddStringAscii(m_Order, s);
  m_Order.SetItemData(index, (LPARAM)(INT_PTR)(-1));
  return index;
}

void CCompressDialog::SetOrder2()
{
  m_Order.ResetContent();
  
  _auto_Order = 1;

  const CArcInfoEx &ai = Get_ArcInfoEx();
  UInt32 defaultOrder = (UInt32)(Int32)-1;

  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method))
        defaultOrder = fo.Order;
    }
  }

  const int methodID = GetMethodID();
  const UInt32 level = GetLevel2();
  if (methodID < 0)
    return;
  
  switch (methodID)
  {
    case kLZMA:
    case kLZMA2:
    {
      _auto_Order = (level < 7 ? 32 : 64);
      int curSel = AddOrder_Auto();
      for (unsigned i = 2 * 2; i < 8 * 2; i++)
      {
        UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2));
        if (order > 256)
          order = 273;
        const int index = AddOrder(order);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (order <= defaultOrder || curSel <= 0)
            curSel = index;
      }
      m_Order.SetCurSel(curSel);
      break;
    }

    /*
    case kZSTD:
    {
      {
        CZstdEncProps props;
        ZstdEncProps_Init(&props);
        // props.level_zstd = level;
        props.level_7z = level;
        ZstdEncProps_NormalizeFull(&props);
        _auto_Order = props.targetLength;
        if (props.strategy < ZSTD_strategy_btopt)
        {
          // ZSTD_strategy_fast uses targetLength to change fast level.
          // targetLength probably is used only in ZSTD_strategy_btopt and higher
          break;
        }
      }
      int curSel = AddOrder_Auto();

      for (unsigned i = 6; i <= 9 * 2; i++)
      {
        UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2));
        // if (order > 999) order = 999;
        const int index = AddOrder(order);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (order <= defaultOrder || curSel <= 0)
            curSel = index;
      }
      m_Order.SetCurSel(curSel);
      break;
    }
    */

    case kDeflate:
    case kDeflate64:
    {
      {
             if (level >= 9) _auto_Order = 128;
        else if (level >= 7) _auto_Order = 64;
        else                 _auto_Order = 32;
      }
      int curSel = AddOrder_Auto();
      for (unsigned i = 2 * 2; i < 8 * 2; i++)
      {
        UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2));
        if (order > 256)
          order = (methodID == kDeflate64 ? 257 : 258);
        const int index = AddOrder(order);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (order <= defaultOrder || curSel <= 0)
            curSel = index;
      }
      
      m_Order.SetCurSel(curSel);
      break;
    }
   
    case kPPMd:
    {
      {
             if (level >= 9) _auto_Order = 32;
        else if (level >= 7) _auto_Order = 16;
        else if (level >= 5) _auto_Order = 6;
        else                 _auto_Order = 4;
      }
      
      int curSel = AddOrder_Auto();

      for (unsigned i = 0;; i++)
      {
        UInt32 order = i + 2;
        if (i >= 2)
          order = (4 + ((i - 2) & 3)) << ((i - 2) / 4);
        const int index = AddOrder(order);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (order <= defaultOrder || curSel <= 0)
            curSel = index;
        if (order >= 32)
          break;
      }
      m_Order.SetCurSel(curSel);
      break;
    }

    case kPPMdZip:
    {
      _auto_Order = level + 3;
      int curSel = AddOrder_Auto();
      for (unsigned i = 2; i <= 16; i++)
      {
        const int index = AddOrder(i);
        if (defaultOrder != (UInt32)(Int32)-1)
          if (i <= defaultOrder || curSel <= 0)
            curSel = index;
      }
      m_Order.SetCurSel(curSel);
      break;
    }
    
    // case kBZip2:
    default:
      break;
  }
}

bool CCompressDialog::GetOrderMode()
{
  switch (GetMethodID())
  {
    case kPPMd:
    case kPPMdZip:
      return true;
  }
  return false;
}


static UInt64 Get_Lzma2_ChunkSize(UInt64 dict)
{
  // we use same default chunk sizes as defined in 7z encoder and lzma2 encoder
  UInt64 cs = (UInt64)dict << 2;
  const UInt32 kMinSize = (UInt32)1 << 20;
  const UInt32 kMaxSize = (UInt32)1 << 28;
  if (cs < kMinSize) cs = kMinSize;
  if (cs > kMaxSize) cs = kMaxSize;
  if (cs < dict) cs = dict;
  cs += (kMinSize - 1);
  cs &= ~(UInt64)(kMinSize - 1);
  return cs;
}


static void Add_Size(AString &s, UInt64 val)
{
  unsigned moveBits = 0;
  char c = 0;
       if ((val & 0x3FFFFFFF) == 0) { moveBits = 30; c = 'G'; }
  else if ((val &    0xFFFFF) == 0) { moveBits = 20; c = 'M'; }
  else if ((val &      0x3FF) == 0) { moveBits = 10; c = 'K'; }
  s.Add_UInt64(val >> moveBits);
  s.Add_Space();
  if (moveBits != 0)
    s.Add_Char(c);
  s.Add_Char('B');
}


void CCompressDialog::SetSolidBlockSize2()
{
  m_Solid.ResetContent();
  _auto_Solid = 1 << 20;

  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  if (!fi.Solid_())
    return;

  const UInt32 level = GetLevel2();
  if (level == 0)
    return;

  UInt64 dict = GetDict2();
  if (dict == (UInt64)(Int64)-1)
  {
    dict = 1 << 25; // default dict for unknown methods
    // return;
  }


  UInt32 defaultBlockSize = (UInt32)(Int32)-1;

  const CArcInfoEx &ai = Get_ArcInfoEx();

  /*
  if (usePrevDictionary)
    defaultBlockSize = GetBlockSizeSpec();
  else
  */
  {
    const int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method))
        defaultBlockSize = fo.BlockLogSize;
    }
  }

  const bool is7z = ai.Is_7z();

  const UInt64 cs = Get_Lzma2_ChunkSize(dict);

  // Solid Block Size
  UInt64 blockSize = cs; // for xz

  if (is7z)
  {
    // we use same default block sizes as defined in 7z encoder
    UInt64 kMaxSize = (UInt64)1 << 32;
    const int methodId = GetMethodID();
    if (methodId == kLZMA2)
    {
      blockSize = cs << 6;
      kMaxSize = (UInt64)1 << 34;
    }
    else
    {
      UInt64 dict2 = dict;
      if (methodId == kBZip2)
      {
        dict2 /= 100000;
        if (dict2 < 1)
          dict2 = 1;
        dict2 *= 100000;
      }
      blockSize = dict2 << 7;
    }

    const UInt32 kMinSize = (UInt32)1 << 24;
    if (blockSize < kMinSize) blockSize = kMinSize;
    if (blockSize > kMaxSize) blockSize = kMaxSize;
  }

  _auto_Solid = blockSize;

  int curSel;
  {
    AString s;
    Add_Size(s, _auto_Solid);
    Modify_Auto(s);
    const int index = (int)ComboBox_AddStringAscii(m_Solid, s);
    m_Solid.SetItemData(index, (LPARAM)(UInt32)(Int32)-1);
    curSel = index;
  }

  if (is7z)
  {
    UString s ('-');
    // kSolidLog_NoSolid = 0 for xz means default blockSize
    if (is7z)
      LangString(IDS_COMPRESS_NON_SOLID, s);
    const int index = (int)m_Solid.AddString(s);
    m_Solid.SetItemData(index, (LPARAM)(UInt32)kSolidLog_NoSolid);
    if (defaultBlockSize == kSolidLog_NoSolid)
      curSel = index;
  }
  
  for (unsigned i = 20; i <= 36; i++)
  {
    AString s;
    Add_Size(s, (UInt64)1 << i);
    const int index = (int)ComboBox_AddStringAscii(m_Solid, s);
    m_Solid.SetItemData(index, (LPARAM)(UInt32)i);
    if (defaultBlockSize != (UInt32)(Int32)-1)
      if (i <= defaultBlockSize || index <= 1)
        curSel = index;
  }
  
  {
    const int index = (int)m_Solid.AddString(LangString(IDS_COMPRESS_SOLID));
    m_Solid.SetItemData(index, (LPARAM)kSolidLog_FullSolid);
    if (defaultBlockSize == kSolidLog_FullSolid)
      curSel = index;
  }

  m_Solid.SetCurSel(curSel);
}


/*
static void ZstdEncProps_SetDictProps_From_CompressDialog(CZstdEncProps *props, CCompressDialog &cd)
{
  {
    const UInt64 d64 = cd.GetDictSpec();
    UInt32 d32 = 0; // 0 is default for ZstdEncProps::windowLog
    if (d64 != (UInt64)(Int64)-1)
    {
      d32 = (UInt32)d64;
      if (d32 != d64)
        d32 = (UInt32)(Int32)-2;
    }
    ZstdEncProps_Set_WindowSize(props, d32);
  }
  {
    const UInt64 d64 = cd.GetDictChainSpec();
    UInt32 d32 = 0; // 0 is default for ZstdEncProps::windowLog_Chain
    if (d64 != (UInt64)(Int64)-1)
    {
      d32 = (UInt32)d64;
      if (d32 != d64)
        d32 = (UInt32)(Int32)-2;
    }
    ZstdEncProps_Set_WindowChainSize(props, d32);
  }
}

static bool Is_Zstd_Mt_Supported()
{
  if (!GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "InitializeConditionVariable"))
    return false;
  return true;
}
*/

static const char *k_ST_Threads = " (ST)";

void CCompressDialog::SetNumThreads2()
{
  _auto_NumThreads = 1;

  m_NumThreads.ResetContent();
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  if (!fi.MultiThread_())
    return;

  const UInt32 numHardwareThreads = NSystem::GetNumberOfProcessors();
    // 64; // for debug:

  UInt32 defaultValue = numHardwareThreads;
  bool useAutoThreads = true;

  {
    const CArcInfoEx &ai = Get_ArcInfoEx();
    int index = FindRegistryFormat(ai.Name);
    if (index >= 0)
    {
      const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
      if (IsMethodEqualTo(fo.Method) && fo.NumThreads != (UInt32)(Int32)-1)
      {
        defaultValue = fo.NumThreads;
        useAutoThreads = false;
      }
    }
  }

  // const UInt32 num_ZSTD_threads_MAX = Is_Zstd_Mt_Supported() ? MY_ZSTDMT_NBWORKERS_MAX : 0;

  UInt32 numAlgoThreadsMax = numHardwareThreads * 2;
  const int methodID = GetMethodID();

  const bool isZip = IsZipFormat();
  if (isZip)
    numAlgoThreadsMax =
        8 << (sizeof(size_t) / 2); // 32 threads for 32-bit : 128 threads for 64-bit
  else if (IsXzFormat())
    numAlgoThreadsMax = 256 * 2;
  else switch (methodID)
  {
    case kLZMA: numAlgoThreadsMax = 2; break;
    case kLZMA2: numAlgoThreadsMax = 256; break;
    case kBZip2: numAlgoThreadsMax = 64; break;
    // case kZSTD: numAlgoThreadsMax = num_ZSTD_threads_MAX; break;
    case kCopy:
    case kPPMd:
    case kDeflate:
    case kDeflate64:
    case kPPMdZip:
      numAlgoThreadsMax = 1;
  }
  UInt32 autoThreads = numHardwareThreads;
  if (autoThreads > numAlgoThreadsMax)
    autoThreads = numAlgoThreadsMax;

  const UInt64 memUse_Limit = Get_MemUse_Bytes();

  if (_ramSize_Defined)
  if (autoThreads > 1
      // || (autoThreads == 0 && methodID == kZSTD)
      )
  {
    if (isZip)
    {
      for (; autoThreads > 1; autoThreads--)
      {
        const UInt64 dict64 = GetDict2();
        UInt64 decompressMemory;
        const UInt64 usage = GetMemoryUsage_Threads_Dict_DecompMem(autoThreads, dict64, decompressMemory);
        if (usage <= memUse_Limit)
          break;
      }
    }
    else if (methodID == kLZMA2)
    {
      const UInt64 dict64 = GetDict2();
      const UInt32 numThreads1 = (GetLevel2() >= 5 ? 2 : 1);
      UInt32 numBlockThreads = autoThreads / numThreads1;
      for (; numBlockThreads > 1; numBlockThreads--)
      {
        autoThreads = numBlockThreads * numThreads1;
        UInt64 decompressMemory;
        const UInt64 usage = GetMemoryUsage_Threads_Dict_DecompMem(autoThreads, dict64, decompressMemory);
        if (usage <= memUse_Limit)
          break;
      }
      autoThreads = numBlockThreads * numThreads1;
    }
    /*
    else if (methodID == kZSTD)
    {
      if (num_ZSTD_threads_MAX != 0)
      {
        CZstdEncProps props;
        ZstdEncProps_Init(&props);
        // props.level_zstd = level;
        props.level_7z = GetLevel2();
        ZstdEncProps_SetDictProps_From_CompressDialog(&props, *this);
        autoThreads = ZstdEncProps_GetNumThreads_for_MemUsageLimit(&props, memUse_Limit, autoThreads);
      }
    }
    */
  }

  _auto_NumThreads = autoThreads;

  int curSel = -1;
  {
    AString s;
    s.Add_UInt32(autoThreads);
    if (autoThreads == 0) s += k_ST_Threads;
    Modify_Auto(s);
    const int index = (int)ComboBox_AddStringAscii(m_NumThreads, s);
    m_NumThreads.SetItemData(index, (LPARAM)(INT_PTR)(-1));
    // m_NumThreads.SetItemData(index, autoThreads);
    if (useAutoThreads)
      curSel = index;
  }

  if (numAlgoThreadsMax != autoThreads || autoThreads != 1)
  for (UInt32 i =
      // (methodID == kZSTD) ? 0 :
      1;
      i <= numHardwareThreads * 2 && i <= numAlgoThreadsMax; i++)
  {
    AString s;
    s.Add_UInt32(i);
    if (i == 0) s += k_ST_Threads;
    const int index = (int)ComboBox_AddStringAscii(m_NumThreads, s);
    m_NumThreads.SetItemData(index, (LPARAM)(UInt32)i);
    if (!useAutoThreads && i == defaultValue)
      curSel = index;
  }
 
  m_NumThreads.SetCurSel(curSel);
}


static void AddMemSize(UString &res, UInt64 size)
{
  char c;
  unsigned moveBits = 0;
  if (size >= ((UInt64)1 << 31) && (size & 0x3FFFFFFF) == 0)
    { moveBits = 30; c = 'G'; }
  else // if (size >= ((UInt32)1 << 21) && (size & 0xFFFFF) == 0)
    { moveBits = 20; c = 'M'; }
  // else { moveBits = 10; c = 'K'; }
  res.Add_UInt64(size >> moveBits);
  res.Add_Space();
  if (moveBits != 0)
    res.Add_Char(c);
  res.Add_Char('B');
}


int CCompressDialog::AddMemComboItem(UInt64 val, bool isPercent, bool isDefault)
{
  UString sUser;
  UString sRegistry;
  if (isPercent)
  {
    UString s;
    s.Add_UInt64(val);
    s.Add_Char('%');
    if (isDefault)
      sUser = k_Auto_Prefix;
    else
      sRegistry = s;
    sUser += s;
  }
  else
  {
    AddMemSize(sUser, val);
    sRegistry = sUser;
    for (;;)
    {
      const int pos = sRegistry.Find(L' ');
      if (pos < 0)
        break;
      sRegistry.Delete(pos);
    }
    if (!sRegistry.IsEmpty())
      if (sRegistry.Back() == 'B')
        sRegistry.DeleteBack();
  }
  const unsigned dataIndex = _memUse_Strings.Add(sRegistry);
  const int index = (int)m_MemUse.AddString(sUser);
  m_MemUse.SetItemData(index, (LPARAM)dataIndex);
  return index;
}



void CCompressDialog::SetMemUseCombo()
{
  _memUse_Strings.Clear();
  m_MemUse.ResetContent();
  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];

  {
    const bool enable = fi.MemUse_();
    ShowItem_Bool(IDT_COMPRESS_MEMORY, enable);
    ShowItem_Bool(IDT_COMPRESS_MEMORY_VALUE, enable);
    ShowItem_Bool(IDT_COMPRESS_MEMORY_DE, enable);
    ShowItem_Bool(IDT_COMPRESS_MEMORY_DE_VALUE, enable);
    ShowItem_Bool(IDC_COMPRESS_MEM_USE, enable);
    EnableItem(IDC_COMPRESS_MEM_USE, enable);
    if (!enable)
      return;
  }

  UInt64 curMem_Bytes = 0;
  UInt64 curMem_Percents = 0;
  bool needSetCur_Bytes = false;
  bool needSetCur_Percents = false;
  {
    const NCompression::CFormatOptions &fo = Get_FormatOptions();
    if (!fo.MemUse.IsEmpty())
    {
      NCompression::CMemUse mu;
      mu.Parse(fo.MemUse);
      if (mu.IsDefined)
      {
        if (mu.IsPercent)
        {
          curMem_Percents = mu.Val;
          needSetCur_Percents = true;
        }
        else
        {
          curMem_Bytes = mu.GetBytes(_ramSize_Reduced);
          needSetCur_Bytes = true;
        }
      }
    }
  }

  
  // 80% - is auto usage limit in handlers
  AddMemComboItem(80, true, true);
  m_MemUse.SetCurSel(0);

  {
    for (unsigned i = 10;; i += 10)
    {
      UInt64 size = i;
      if (i > 100)
        size = (UInt64)(Int64)-1;
      if (needSetCur_Percents && size >= curMem_Percents)
      {
        const int index = AddMemComboItem(curMem_Percents, true);
        m_MemUse.SetCurSel(index);
        needSetCur_Percents = false;
        if (size == curMem_Percents)
          continue;
      }
      if (size == (UInt64)(Int64)-1)
        break;
      AddMemComboItem(size, true);
    }
  }
  {
    for (unsigned i = (27) * 2;; i++)
    {
      UInt64 size = (UInt64)(2 + (i & 1)) << (i / 2);
      if (i > (20 + sizeof(size_t) * 3 - 1) * 2)
        size = (UInt64)(Int64)-1;
      if (needSetCur_Bytes && size >= curMem_Bytes)
      {
        const int index = AddMemComboItem(curMem_Bytes);
        m_MemUse.SetCurSel(index);
        needSetCur_Bytes = false;
        if (size == curMem_Bytes)
          continue;
      }
      if (size == (UInt64)(Int64)-1)
        break;
      AddMemComboItem(size);
    }
  }
}


UString CCompressDialog::Get_MemUse_Spec()
{
  if (m_MemUse.GetCount() < 1)
    return UString();
  return _memUse_Strings[(unsigned)m_MemUse.GetItemData_of_CurSel()];
}


UInt64 CCompressDialog::Get_MemUse_Bytes()
{
  const UString mus = Get_MemUse_Spec();
  NCompression::CMemUse mu;
  if (!mus.IsEmpty())
  {
    mu.Parse(mus);
    if (mu.IsDefined)
      return mu.GetBytes(_ramSize_Reduced);
  }
  return _ramUsage_Auto; // _ramSize_Reduced; // _ramSize;;
}



UInt64 CCompressDialog::GetMemoryUsage_DecompMem(UInt64 &decompressMemory)
{
  return GetMemoryUsage_Dict_DecompMem(GetDict2(), decompressMemory);
}


/*
we could use that function to reduce the dictionary if small RAM
UInt64 CCompressDialog::GetMemoryUsageComp_Threads_Dict(UInt32 numThreads, UInt64 dict64)
{
  UInt64 decompressMemory;
  return GetMemoryUsage_Threads_Dict_DecompMem(numThreads, dict64, decompressMemory);
}
*/


UInt64 CCompressDialog::GetMemoryUsage_Dict_DecompMem(UInt64 dict64, UInt64 &decompressMemory)
{
  return GetMemoryUsage_Threads_Dict_DecompMem(GetNumThreads2(), dict64, decompressMemory);
}

UInt64 CCompressDialog::GetMemoryUsage_Threads_Dict_DecompMem(UInt32 numThreads, UInt64 dict64, UInt64 &decompressMemory)
{
  decompressMemory = (UInt64)(Int64)-1;

  const UInt32 level = GetLevel2();
  if (level == 0 && !Get_ArcInfoEx().Is_Zstd())
  {
    decompressMemory = (1 << 20);
    return decompressMemory;
  }
  UInt64 size = 0;

  const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()];
  if (fi.Filter_() && level >= 9)
    size += (12 << 20) * 2 + (5 << 20);
  // UInt32 numThreads = GetNumThreads2();
  
  UInt32 numMainZipThreads = 1;

  if (IsZipFormat())
  {
    UInt32 numSubThreads = 1;
    if (GetMethodID() == kLZMA && numThreads > 1 && level >= 5)
      numSubThreads = 2;
    numMainZipThreads = numThreads / numSubThreads;
    if (numMainZipThreads > 1)
      size += (UInt64)numMainZipThreads * ((size_t)sizeof(size_t) << 23);
    else
      numMainZipThreads = 1;
  }
  
  const int methodId = GetMethodID();

  if (dict64 == (UInt64)(Int64)-1
      // && methodId != kZSTD
      )
    return (UInt64)(Int64)-1;

  
  switch (methodId)
  {
    case kLZMA:
    case kLZMA2:
    {
      const UInt32 dict = (dict64 >= kLzmaMaxDictSize ? kLzmaMaxDictSize : (UInt32)dict64);
      UInt32 hs = dict - 1;
      hs |= (hs >> 1);
      hs |= (hs >> 2);
      hs |= (hs >> 4);
      hs |= (hs >> 8);
      hs >>= 1;
      if (hs >= (1 << 24))
        hs >>= 1;
      hs |= (1 << 16) - 1;
      // if (numHashBytes >= 5)
      if (level < 5)
        hs |= (256 << 10) - 1;
      hs++;
      UInt64 size1 = (UInt64)hs * 4;
      size1 += (UInt64)dict * 4;
      if (level >= 5)
        size1 += (UInt64)dict * 4;
      size1 += (2 << 20);

      UInt32 numThreads1 = 1;
      if (numThreads > 1 && level >= 5)
      {
        size1 += (2 << 20) + (4 << 20);
        numThreads1 = 2;
      }
      
      UInt32 numBlockThreads = numThreads / numThreads1;
    
      UInt64 chunkSize = 0; // it's solid chunk

      if (methodId != kLZMA && numBlockThreads != 1)
      {
        chunkSize = Get_Lzma2_ChunkSize(dict);

        if (IsXzFormat())
        {
          UInt32 blockSizeLog = GetBlockSizeSpec();
          if (blockSizeLog != (UInt32)(Int32)-1)
          {
            if (blockSizeLog == kSolidLog_FullSolid)
            {
              numBlockThreads = 1;
              chunkSize = 0;
            }
            else if (blockSizeLog != kSolidLog_NoSolid)
              chunkSize = (UInt64)1 << blockSizeLog;
          }
        }
      }

      if (chunkSize == 0)
      {
        const UInt32 kBlockSizeMax = (UInt32)0 - (UInt32)(1 << 16);
        UInt64 blockSize = (UInt64)dict + (1 << 16)
          + (numThreads1 > 1 ? (1 << 20) : 0);
        blockSize += (blockSize >> (blockSize < ((UInt32)1 << 30) ? 1 : 2));
        if (blockSize >= kBlockSizeMax)
          blockSize = kBlockSizeMax;
        size += numBlockThreads * (size1 + blockSize);
      }
      else
      {
        size += numBlockThreads * (size1 + chunkSize);
        const UInt32 numPackChunks = numBlockThreads + (numBlockThreads / 8) + 1;
        if (chunkSize < ((UInt32)1 << 26)) numBlockThreads++;
        if (chunkSize < ((UInt32)1 << 24)) numBlockThreads++;
        if (chunkSize < ((UInt32)1 << 22)) numBlockThreads++;
        size += numPackChunks * chunkSize;
      }

      decompressMemory = dict + (2 << 20);
      return size;
    }

    /*
    case kZSTD:
    {
      CZstdEncProps props;
      ZstdEncProps_Init(&props);
      // props.level_zstd = level;
      props.level_7z = level;
      props.nbWorkers = numThreads;
      ZstdEncProps_SetDictProps_From_CompressDialog(&props, *this);
      ZstdEncProps_NormalizeFull(&props);
      size = ZstdEncProps_GetMemUsage(&props);
      decompressMemory = (UInt64)1 << props.windowLog;
      return size;
    }
    */
    
    case kPPMd:
    {
      decompressMemory = dict64 + (2 << 20);
      return size + decompressMemory;
    }
    
    case kDeflate:
    case kDeflate64:
    {
      UInt64 size1 = 3 << 20;
      // if (level >= 7)
        size1 += (1 << 20);
      size += size1 * numMainZipThreads;
      decompressMemory = (2 << 20);
      return size;
    }
    
    case kBZip2:
    {
      decompressMemory = (7 << 20);
      UInt64 memForOneThread = (10 << 20);
      return size + memForOneThread * numThreads;
    }
    
    case kPPMdZip:
    {
      decompressMemory = dict64 + (2 << 20);
      return size + (UInt64)decompressMemory * numThreads;
    }
  }
  
  return (UInt64)(Int64)-1;
}



static void AddMemUsage(UString &s, UInt64 v)
{
  const char *post;
  if (v <= ((UInt64)16 << 30))
  {
    v = (v + (1 << 20) - 1) >> 20;
    post = "MB";
  }
  else if (v <= ((UInt64)64 << 40))
  {
    v = (v + (1 << 30) - 1) >> 30;
    post = "GB";
  }
  else
  {
    const UInt64 v2 = v + ((UInt64)1 << 40) - 1;
    if (v <= v2)
      v = v2;
    v >>= 40;
    post = "TB";
  }
  s.Add_UInt64(v);
  s.Add_Space();
  s += post;
}


void CCompressDialog::PrintMemUsage(UINT res, UInt64 value)
{
  if (value == (UInt64)(Int64)-1)
  {
    SetItemText(res, TEXT("?"));
    return;
  }
  UString s;
  AddMemUsage(s, value);
  if (res == IDT_COMPRESS_MEMORY_VALUE)
  {
    const UString mus = Get_MemUse_Spec();
    NCompression::CMemUse mu;
    if (!mus.IsEmpty())
      mu.Parse(mus);
    if (mu.IsDefined)
    {
      s += " / ";
      AddMemUsage(s, mu.GetBytes(_ramSize_Reduced));
    }
    else if (_ramSize_Defined)
    {
      s += " / ";
      AddMemUsage(s, _ramUsage_Auto);
    }

    if (_ramSize_Defined)
    {
      s += " / ";
      AddMemUsage(s, _ramSize);
    }
  }
  SetItemText(res, s);
}

    
void CCompressDialog::SetMemoryUsage()
{
  UInt64 decompressMem;
  const UInt64 memUsage = GetMemoryUsage_DecompMem(decompressMem);
  PrintMemUsage(IDT_COMPRESS_MEMORY_VALUE, memUsage);
  PrintMemUsage(IDT_COMPRESS_MEMORY_DE_VALUE, decompressMem);
 #ifdef PRINT_PARAMS
  Print_Params();
 #endif
}



#ifdef PRINT_PARAMS

static const char kPropDelimeter = ' '; // ':'

static void AddPropName(AString &s, const char *name)
{
  if (!s.IsEmpty())
    s += kPropDelimeter;
  s += name;
}

static void AddProp(AString &s, const char *name, unsigned v)
{
  AddPropName(s, name);
  s.Add_UInt32(v);
}

static void AddProp_switch(AString &s, const char *name, E_ZSTD_paramSwitch_e e)
{
  AddPropName(s, name);
  s += e == k_ZSTD_ps_enable ? "" : "-";
}

static void PrintPropAsLog(AString &s, const char *name, size_t v)
{
  AddPropName(s, name);
  for (unsigned i = 0; i < sizeof(size_t) * 8; i++)
  {
    if (((size_t)1 << i) == v)
    {
      s.Add_UInt32(i);
      return;
    }
  }
  char c = 'b';
       if ((v & 0x3FFFFFFF) == 0) { v >>= 30; c = 'G'; }
  else if ((v &    0xFFFFF) == 0) { v >>= 20; c = 'M'; }
  else if ((v &      0x3FF) == 0) { v >>= 10; c = 'K'; }
  s.Add_UInt64(v);
  s += c;
}

static void ZstdEncProps_Print(CZstdEncProps *props, AString &s)
{
  if (props->level_zstd >= 0)
    AddProp(s, "zx", props->level_zstd);
  else
    AddProp(s, "zf", -(props->level_zstd));
  AddProp(s, "a", props->strategy);
  AddProp(s, "d", props->windowLog);
  AddProp(s, "zclog", props->chainLog);
  AddProp(s, "zhb", props->hashLog);
  AddProp(s, "mml", props->minMatch);
  AddProp(s, "mcb", props->searchLog);
  AddProp(s, "fb", props->targetLength);
  AddProp(s, "mt", props->nbWorkers);
  PrintPropAsLog(s, "c", props->jobSize);
  AddProp(s, "zov", props->overlapLog);
  PrintPropAsLog(s, "ztps", props->targetPrefixSize);
  AddProp_switch(s, "zmfr", props->useRowMatchFinder);
  if (props->ldmParams.enableLdm == k_ZSTD_ps_enable)
  {
    AddProp_switch(s, "zle", props->ldmParams.enableLdm);
    AddProp(s, "zlhb", props->ldmParams.hashLog);
    AddProp(s, "zlbb", props->ldmParams.bucketSizeLog);
    AddProp(s, "zlmml", props->ldmParams.minMatchLength);
    AddProp(s, "zlhrb", props->ldmParams.hashRateLog);
  }
}

void CCompressDialog::Print_Params()
{
  {
    CZstdEncProps props;
    ZstdEncProps_Init(&props);
    // props.level_zstd = level;
    props.level_7z = GetLevel2();
    ZstdEncProps_SetDictProps_From_CompressDialog(&props, *this);
    {
      UInt32 order = GetOrderSpec();
      if (order != (UInt32)(Int32)-1)
        props.targetLength = GetOrderSpec();
    }
    props.nbWorkers = GetNumThreads2();
    // props.windowLog = 18; // for debug
    ZstdEncProps_NormalizeFull(&props);
    AString s;
    ZstdEncProps_Print(&props, s);
    SetItemTextA(IDT_COMPRESS_PARAMS_INFO, s);
  }
}

#endif // PRINT_PARAMS



void CCompressDialog::SetParams()
{
  const CArcInfoEx &ai = Get_ArcInfoEx();
  m_Params.SetText(TEXT(""));
  const int index = FindRegistryFormat(ai.Name);
  if (index >= 0)
  {
    const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
    m_Params.SetText(fo.Options);
  }
}

void CCompressDialog::SaveOptionsInMem()
{
  /* these options are for (Info.FormatIndex).
     If it's called just after format changing,
     then it's format that was selected before format changing
     So we store previous format properties */

  m_Params.GetText(Info.Options);
  Info.Options.Trim();

  const CArcInfoEx &ai = (*ArcFormats)[Info.FormatIndex];
  const unsigned index = FindRegistryFormat_Always(ai.Name);
  NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index];
  fo.Options = Info.Options;
  fo.Level = GetLevelSpec();
  {
    const UInt64 dict64 = GetDictSpec();
    UInt32 dict32;
    if (dict64 == (UInt64)(Int64)-1)
      dict32 = (UInt32)(Int32)-1;
    else
    {
      dict32 = (UInt32)dict64;
      if (dict64 != dict32)
      {
        /* here we must write 32-bit value for registry that indicates big_value
           (UInt32)(Int32)-1  : is used as marker for default size
           (UInt32)(Int32)-2  : it can be used to indicate big value (4 GiB)
           the value must be larger than threshold
        */
        dict32 = (UInt32)(Int32)-2;
        // dict32 = kLzmaMaxDictSize; // it must be larger than threshold
      }
    }
    fo.Dictionary = dict32;
  }
  /*
  {
    const UInt64 dict64 = GetDictChainSpec();
    UInt32 dict32;
    if (dict64 == (UInt64)(Int64)-1)
      dict32 = (UInt32)(Int32)-1;
    else
    {
      dict32 = (UInt32)dict64;
      if (dict64 != dict32)
      {
        dict32 = (UInt32)(Int32)-2;
        // dict32 = k_Zstd_MAX_DictSize; // it must be larger than threshold
      }
    }
    fo.DictionaryChain = dict32;
  }
  */

  fo.Order = GetOrderSpec();
  fo.Method = GetMethodSpec();
  fo.EncryptionMethod = GetEncryptionMethodSpec();
  fo.NumThreads = GetNumThreadsSpec();
  fo.BlockLogSize = GetBlockSizeSpec();
  fo.MemUse = Get_MemUse_Spec();
}


unsigned CCompressDialog::GetFormatIndex()
{
  return (unsigned)m_Format.GetItemData_of_CurSel();
}



static void AddText_from_BoolPair(AString &s, const char *name, const CBoolPair &bp)
{
  if (bp.Def)
  {
    s.Add_OptSpaced(name);
    if (!bp.Val)
      s += "-";
  }
  /*
  else if (bp.Val)
  {
    s.Add_OptSpaced("[");
    s += name;
    s += "]";
  }
  */
}


static void AddText_from_Bool1(AString &s, const char *name, const CBool1 &b)
{
  if (b.Supported && b.Val)
    s.Add_OptSpaced(name);
}


void CCompressDialog::ShowOptionsString()
{
  NCompression::CFormatOptions &fo = Get_FormatOptions();

  AString s;
  if (fo.IsSet_TimePrec())
  {
    s.Add_OptSpaced("tp");
    s.Add_UInt32(fo.TimePrec);
  }
  AddText_from_BoolPair(s, "tm", fo.MTime);
  AddText_from_BoolPair(s, "tc", fo.CTime);
  AddText_from_BoolPair(s, "ta", fo.ATime);
  AddText_from_BoolPair(s, "-stl", fo.SetArcMTime);

  // const CArcInfoEx &ai = Get_ArcInfoEx();
  AddText_from_Bool1(s, "SL",  SymLinks);
  AddText_from_Bool1(s, "HL",  HardLinks);
  AddText_from_Bool1(s, "AS",  AltStreams);
  AddText_from_Bool1(s, "Sec", NtSecurity);

  // AddText_from_Bool1(s, "Preserve", PreserveATime);
  
  SetItemText(IDT_COMPRESS_OPTIONS, GetUnicodeString(s));
}





// ---------- OPTIONS ----------


void COptionsDialog::CheckButton_Bool1(UINT id, const CBool1 &b1)
{
  CheckButton(id, b1.Val);
}

void COptionsDialog::GetButton_Bool1(UINT id, CBool1 &b1)
{
  b1.Val = IsButtonCheckedBool(id);
}


void COptionsDialog::CheckButton_BoolBox(
    bool supported, const CBoolPair &b2, CBoolBox &bb)
{
  const bool isSet = b2.Def;
  const bool val = isSet ? b2.Val : bb.DefaultVal;

  bb.IsSupported = supported;

  CheckButton (bb.Set_Id, isSet);
  ShowItem_Bool (bb.Set_Id, supported);
  CheckButton (bb.Id, val);
  EnableItem (bb.Id, isSet);
  ShowItem_Bool (bb.Id, supported);
}

void COptionsDialog::GetButton_BoolBox(CBoolBox &bb)
{
  // we save value for invisible buttons too
  bb.BoolPair.Val = IsButtonCheckedBool (bb.Id);
  bb.BoolPair.Def = IsButtonCheckedBool (bb.Set_Id);
}


void COptionsDialog::Store_TimeBoxes()
{
  TimePrec = GetPrecSpec();
  GetButton_BoolBox (MTime);
  GetButton_BoolBox (CTime);
  GetButton_BoolBox (ATime);
  GetButton_BoolBox (ZTime);
}


UInt32 COptionsDialog::GetComboValue(NWindows::NControl::CComboBox &c, int defMax)
{
  if (c.GetCount() <= defMax)
    return (UInt32)(Int32)-1;
  return (UInt32)c.GetItemData_of_CurSel();
}

static const unsigned kTimePrec_Win  = 0;
static const unsigned kTimePrec_Unix = 1;
static const unsigned kTimePrec_DOS  = 2;
static const unsigned kTimePrec_1ns  = 3;

static void AddTimeOption(UString &s, UInt32 val, const UString &unit, const char *sys = NULL)
{
  // s += " : ";
  {
    AString s2;
    s2.Add_UInt32(val);
    s += s2;
  }
  s.Add_Space();
  s += unit;
  if (sys)
  {
    s += " : ";
    s += sys;
  }
}

int COptionsDialog::AddPrec(unsigned prec, bool isDefault)
{
  UString s;
  UInt32 writePrec = prec;
  if (isDefault)
  {
    // s += "* ";
    // writePrec = (UInt32)(Int32)-1;
  }
       if (prec == kTimePrec_Win)  AddTimeOption(s, 100, NsString, "Windows");
  else if (prec == kTimePrec_Unix) AddTimeOption(s, 1, SecString, "Unix");
  else if (prec == kTimePrec_DOS)  AddTimeOption(s, 2, SecString, "DOS");
  else if (prec == kTimePrec_1ns)  AddTimeOption(s, 1, NsString, "Linux");
  else if (prec == k_PropVar_TimePrec_Base) AddTimeOption(s, 1, SecString);
  else if (prec >= k_PropVar_TimePrec_Base)
  {
    UInt32 d = 1;
    for (unsigned i = prec; i < k_PropVar_TimePrec_Base + 9; i++)
      d *= 10;
    AddTimeOption(s, d, NsString);
  }
  else
    s.Add_UInt32(prec);
  const int index = (int)m_Prec.AddString(s);
  m_Prec.SetItemData(index, (LPARAM)writePrec);
  return index;
}


void COptionsDialog::SetPrec()
{
  // const CFormatInfo &fi = g_Formats[cd->GetStaticFormatIndex()];
  const CArcInfoEx &ai = cd->Get_ArcInfoEx();

  // UInt32 flags = fi.Flags;

  UInt32 flags = ai.Get_TimePrecFlags();
  UInt32 defaultPrec = ai.Get_DefaultTimePrec();
  if (defaultPrec != 0)
    flags |= ((UInt32)1 << defaultPrec);

  // const NCompression::CFormatOptions &fo = cd->Get_FormatOptions();

  // unsigned defaultPrec = kTimePrec_Win;

  if (ai.Is_GZip())
    defaultPrec = kTimePrec_Unix;

  {
    UString s;
    s += GetNameOfProperty(kpidType, L"type");
    s += ": ";
    s += ai.Name;
    if (ai.Is_Tar())
    {
      const int methodID = cd->GetMethodID();
      
      // for debug
      // defaultPrec = kTimePrec_Unix;
      // flags = (UInt32)1 << kTimePrec_Unix;

      s.Add_Colon();
      if (methodID >= 0 && (unsigned)methodID < Z7_ARRAY_SIZE(kMethodsNames))
        s += kMethodsNames[methodID];
      if (methodID == kPosix)
      {
        // for debug
        // flags |= (UInt32)1 << kTimePrec_Win;
        // flags |= (UInt32)1 << kTimePrec_1ns;
      }
    }
    else
    {
      // if (is_for_MethodChanging) return;
    }
    
    SetItemText(IDT_COMPRESS_TIME_INFO, s);
  }

  m_Prec.ResetContent();
  _auto_Prec = defaultPrec;

  unsigned selectedPrec = defaultPrec;
  {
    // if (TimePrec >= kTimePrec_Win && TimePrec <= kTimePrec_DOS)
    if ((Int32)TimePrec >= 0)
      selectedPrec = TimePrec;
  }

  int curSel = -1;
  int defaultPrecIndex = -1;
  for (unsigned prec = 0;
      // prec <= k_PropVar_TimePrec_HighPrec;
      prec <= k_PropVar_TimePrec_1ns;
      prec++)
  {
    if (((flags >> prec) & 1) == 0)
      continue;
    const bool isDefault = (defaultPrec == prec);
    const int index = AddPrec(prec, isDefault);
    if (isDefault)
      defaultPrecIndex = index;
    if (selectedPrec == prec)
      curSel = index;
  }

  if (curSel < 0 && selectedPrec > kTimePrec_DOS)
    curSel = AddPrec(selectedPrec, false); // isDefault
  if (curSel < 0)
    curSel = defaultPrecIndex;
  if (curSel >= 0)
    m_Prec.SetCurSel(curSel);

  {
    const bool isSet = IsSet_TimePrec();
    const int count = m_Prec.GetCount();
    const bool showPrec = (count != 0);
    ShowItem_Bool(IDC_COMPRESS_TIME_PREC, showPrec);
    ShowItem_Bool(IDT_COMPRESS_TIME_PREC, showPrec);
    EnableItem(IDC_COMPRESS_TIME_PREC, isSet && (count > 1));

    CheckButton(IDX_COMPRESS_PREC_SET, isSet);
    const bool setIsSupported = isSet || (count > 1);
    EnableItem(IDX_COMPRESS_PREC_SET, setIsSupported);
    ShowItem_Bool(IDX_COMPRESS_PREC_SET, setIsSupported);
  }

  SetTimeMAC();
}


void COptionsDialog::SetTimeMAC()
{
  const CArcInfoEx &ai = cd->Get_ArcInfoEx();

  const
  bool m_allow = ai.Flags_MTime();
  bool c_allow = ai.Flags_CTime();
  bool a_allow = ai.Flags_ATime();

  if (ai.Is_Tar())
  {
    const int methodID = cd->GetMethodID();
    c_allow = false;
    a_allow = false;
    if (methodID == kPosix)
    {
      // c_allow = true; // do we need it as change time ?
      a_allow = true;
    }
  }

  if (ai.Is_Zip())
  {
    // const int methodID = GetMethodID();
    UInt32 prec = GetPrec();
    if (prec == (UInt32)(Int32)-1)
      prec = _auto_Prec;
    if (prec != kTimePrec_Win)
    {
      c_allow = false;
      a_allow = false;
    }
  }

  
  /*
  MTime.DefaultVal = true;
  CTime.DefaultVal = false;
  ATime.DefaultVal = false;
  */

  MTime.DefaultVal = ai.Flags_MTime_Default();
  CTime.DefaultVal = ai.Flags_CTime_Default();
  ATime.DefaultVal = ai.Flags_ATime_Default();
  
  ZTime.DefaultVal = false;

  const NCompression::CFormatOptions &fo = cd->Get_FormatOptions();
  
  CheckButton_BoolBox (m_allow, fo.MTime, MTime );
  CheckButton_BoolBox (c_allow, fo.CTime, CTime );
  CheckButton_BoolBox (a_allow, fo.ATime, ATime );
  CheckButton_BoolBox (true, fo.SetArcMTime, ZTime);

  if (m_allow && !fo.MTime.Def)
  {
    const bool isSingleFile = ai.Flags_KeepName();
    if (!isSingleFile)
    {
      // we can hide changing checkboxes for MTime here:
      ShowItem_Bool (MTime.Set_Id, false);
      EnableItem (MTime.Id, false);
    }
  }
  // On_CheckBoxSet_Prec_Clicked();
  // const bool isSingleFile = ai.Flags_KeepName();
  // mtime for Gz can be
}



void COptionsDialog::On_CheckBoxSet_Prec_Clicked()
{
  const bool isSet = IsButtonCheckedBool(IDX_COMPRESS_PREC_SET);
  if (!isSet)
  {
    // We save current MAC boxes to memory before SetPrec()
    Store_TimeBoxes();
    Reset_TimePrec();
    SetPrec();
  }
  EnableItem(IDC_COMPRESS_TIME_PREC, isSet);
}

void COptionsDialog::On_CheckBoxSet_Clicked(const CBoolBox &bb)
{
  const bool isSet = IsButtonCheckedBool(bb.Set_Id);
  if (!isSet)
    CheckButton(bb.Id, bb.DefaultVal);
  EnableItem(bb.Id, isSet);
}




#ifdef Z7_LANG
static const UInt32 kLangIDs_Options[] =
{
  IDX_COMPRESS_NT_SYM_LINKS,
  IDX_COMPRESS_NT_HARD_LINKS,
  IDX_COMPRESS_NT_ALT_STREAMS,
  IDX_COMPRESS_NT_SECUR,

  IDG_COMPRESS_TIME,
  IDT_COMPRESS_TIME_PREC,
  IDX_COMPRESS_MTIME,
  IDX_COMPRESS_CTIME,
  IDX_COMPRESS_ATIME,
  IDX_COMPRESS_ZTIME,
  IDX_COMPRESS_PRESERVE_ATIME
};
#endif


bool COptionsDialog::OnInit()
{
  #ifdef Z7_LANG
  LangSetWindowText(*this, IDB_COMPRESS_OPTIONS); // IDS_OPTIONS
  LangSetDlgItems(*this, kLangIDs_Options, Z7_ARRAY_SIZE(kLangIDs_Options));
  // LangSetDlgItemText(*this, IDB_COMPRESS_TIME_DEFAULT, IDB_COMPRESS_TIME_DEFAULT);
  // LangSetDlgItemText(*this, IDX_COMPRESS_TIME_DEFAULT, IDX_COMPRESS_TIME_DEFAULT);
  #endif

  LangString(IDS_COMPRESS_SEC, SecString);
  if (SecString.IsEmpty())
    SecString = "sec";
  LangString(IDS_COMPRESS_NS, NsString);
  if (NsString.IsEmpty())
    NsString = "ns";

  {
    // const CArcInfoEx &ai = cd->Get_ArcInfoEx();
    
    ShowItem_Bool ( IDX_COMPRESS_NT_SYM_LINKS,    cd->SymLinks.Supported);
    ShowItem_Bool ( IDX_COMPRESS_NT_HARD_LINKS,   cd->HardLinks.Supported);
    ShowItem_Bool ( IDX_COMPRESS_NT_ALT_STREAMS,  cd->AltStreams.Supported);
    ShowItem_Bool ( IDX_COMPRESS_NT_SECUR,        cd->NtSecurity.Supported);

    ShowItem_Bool ( IDG_COMPRESS_NTFS,
           cd->SymLinks.Supported
        || cd->HardLinks.Supported
        || cd->AltStreams.Supported
        || cd->NtSecurity.Supported);
  }

   /* we read property from two sources:
       1) command line  : (Info)
       2) registry      : (m_RegistryInfo)
     (Info) has priority, if both are no defined */

  CheckButton_Bool1 ( IDX_COMPRESS_NT_SYM_LINKS,   cd->SymLinks);
  CheckButton_Bool1 ( IDX_COMPRESS_NT_HARD_LINKS,  cd->HardLinks);
  CheckButton_Bool1 ( IDX_COMPRESS_NT_ALT_STREAMS, cd->AltStreams);
  CheckButton_Bool1 ( IDX_COMPRESS_NT_SECUR,       cd->NtSecurity);

  CheckButton_Bool1 (IDX_COMPRESS_PRESERVE_ATIME, cd->PreserveATime);

  m_Prec.Attach (GetItem(IDC_COMPRESS_TIME_PREC));

  MTime.SetIDs ( IDX_COMPRESS_MTIME, IDX_COMPRESS_MTIME_SET);
  CTime.SetIDs ( IDX_COMPRESS_CTIME, IDX_COMPRESS_CTIME_SET);
  ATime.SetIDs ( IDX_COMPRESS_ATIME, IDX_COMPRESS_ATIME_SET);
  ZTime.SetIDs ( IDX_COMPRESS_ZTIME, IDX_COMPRESS_ZTIME_SET);

  {
    const NCompression::CFormatOptions &fo = cd->Get_FormatOptions();
    TimePrec = fo.TimePrec;
    MTime.BoolPair = fo.MTime;
    CTime.BoolPair = fo.CTime;
    ATime.BoolPair = fo.ATime;
    ZTime.BoolPair = fo.SetArcMTime;
  }
  
  SetPrec();

  NormalizePosition();

  return CModalDialog::OnInit();
}


bool COptionsDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam)
{
  if (code == CBN_SELCHANGE)
  {
    switch (itemID)
    {
      case IDC_COMPRESS_TIME_PREC:
      {
        Store_TimeBoxes();
        SetTimeMAC(); // for zip/tar
        return true;
      }
    }
  }
  return CModalDialog::OnCommand(code, itemID, lParam);
}


bool COptionsDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND)
{
  switch (buttonID)
  {
    case IDX_COMPRESS_PREC_SET:  { On_CheckBoxSet_Prec_Clicked(); return true; }
    case IDX_COMPRESS_MTIME_SET: { On_CheckBoxSet_Clicked (MTime); return true; }
    case IDX_COMPRESS_CTIME_SET: { On_CheckBoxSet_Clicked (CTime); return true; }
    case IDX_COMPRESS_ATIME_SET: { On_CheckBoxSet_Clicked (ATime); return true; }
    case IDX_COMPRESS_ZTIME_SET: { On_CheckBoxSet_Clicked (ZTime); return true; }
  }
  return CModalDialog::OnButtonClicked(buttonID, buttonHWND);
}


void COptionsDialog::OnOK()
{
  GetButton_Bool1 (IDX_COMPRESS_NT_SYM_LINKS,   cd->SymLinks);
  GetButton_Bool1 (IDX_COMPRESS_NT_HARD_LINKS,  cd->HardLinks);
  GetButton_Bool1 (IDX_COMPRESS_NT_ALT_STREAMS, cd->AltStreams);
  GetButton_Bool1 (IDX_COMPRESS_NT_SECUR,       cd->NtSecurity);
  GetButton_Bool1 (IDX_COMPRESS_PRESERVE_ATIME, cd->PreserveATime);

  Store_TimeBoxes();
  {
    NCompression::CFormatOptions &fo = cd->Get_FormatOptions();
    fo.TimePrec = TimePrec;
    fo.MTime = MTime.BoolPair;
    fo.CTime = CTime.BoolPair;
    fo.ATime = ATime.BoolPair;
    fo.SetArcMTime = ZTime.BoolPair;
  }

  CModalDialog::OnOK();
}

void COptionsDialog::OnHelp()
{
  ShowHelpWindow(kHelpTopic_Options);
}
