// BenchmarkDialog.cpp

#include "StdAfx.h"

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

#include "../../../Common/Defs.h"
#include "../../../Common/IntToString.h"
#include "../../../Common/MyException.h"
#include "../../../Common/StringConvert.h"
#include "../../../Common/StringToInt.h"

#include "../../../Windows/Synchronization.h"
#include "../../../Windows/System.h"
#include "../../../Windows/Thread.h"
#include "../../../Windows/SystemInfo.h"

#include "../../../Windows/Control/ComboBox.h"
#include "../../../Windows/Control/Edit.h"

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

#include "../FileManager/DialogSize.h"
#include "../FileManager/HelpUtils.h"
#include "../FileManager/LangUtils.h"
#include "../FileManager/resourceGui.h"

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

#include "../Common/Bench.h"

#include "BenchmarkDialogRes.h"
#include "BenchmarkDialog.h"

using namespace NWindows;

#define kHelpTopic "fm/benchmark.htm"

static const UINT_PTR kTimerID = 4;
static const UINT kTimerElapse = 1000; // 1000

// use PRINT_ITER_TIME to show time of each iteration in log box
// #define PRINT_ITER_TIME

static const unsigned kRatingVector_NumBundlesMax = 20;

enum MyBenchMessages
{
  k_Message_Finished = WM_APP + 1
};

enum My_Message_WPARAM
{
  k_Msg_WPARM_Thread_Finished = 0,
  k_Msg_WPARM_Iter_Finished,
  k_Msg_WPARM_Enc1_Finished
};


struct CBenchPassResult
{
  CTotalBenchRes Enc;
  CTotalBenchRes Dec;
#ifdef PRINT_ITER_TIME
  DWORD Ticks;
#endif
  // CBenchInfo EncInfo; // for debug
  // CBenchPassResult() {};
};


struct CTotalBenchRes2: public CTotalBenchRes
{
  UInt64 UnpackSize;

  void Init()
  {
    CTotalBenchRes::Init();
    UnpackSize = 0;
  }

  void SetFrom_BenchInfo(const CBenchInfo &info)
  {
    NumIterations2 = 1;
    Generate_From_BenchInfo(info);
    UnpackSize = info.Get_UnpackSize_Full();
  }

  void Update_With_Res2(const CTotalBenchRes2 &r)
  {
    Update_With_Res(r);
    UnpackSize += r.UnpackSize;
  }
};

  
struct CSyncData
{
  UInt32 NumPasses_Finished;
#ifdef PRINT_ITER_TIME
  DWORD TotalTicks;
#endif
  int RatingVector_DeletedIndex;
  // UInt64 RatingVector_NumDeleted;

  bool BenchWasFinished; // all passes were finished
  bool NeedPrint_Freq;
  bool NeedPrint_RatingVector;
  bool NeedPrint_Enc_1;
  bool NeedPrint_Enc;
  bool NeedPrint_Dec_1;
  bool NeedPrint_Dec;
  bool NeedPrint_Tot; // intermediate Total was updated after current pass

  // UInt64 NumEncProgress; // for debug
  // UInt64 NumDecProgress; // for debug
  // CBenchInfo EncInfo; // for debug

  CTotalBenchRes2 Enc_BenchRes_1;
  CTotalBenchRes2 Enc_BenchRes;

  CTotalBenchRes2 Dec_BenchRes_1;
  CTotalBenchRes2 Dec_BenchRes;

  void Init();
};


void CSyncData::Init()
{
  NumPasses_Finished = 0;
  
  // NumEncProgress = 0;
  // NumDecProgress = 0;
  
  Enc_BenchRes.Init();
  Enc_BenchRes_1.Init();
  Dec_BenchRes.Init();
  Dec_BenchRes_1.Init();
  
  #ifdef PRINT_ITER_TIME
  TotalTicks = 0;
  #endif
  
  RatingVector_DeletedIndex = -1;
  // RatingVector_NumDeleted = 0;
  
  BenchWasFinished =
    NeedPrint_Freq =
    NeedPrint_RatingVector =
    NeedPrint_Enc_1 =
    NeedPrint_Enc   =
    NeedPrint_Dec_1 =
    NeedPrint_Dec   =
    NeedPrint_Tot   = false;
}


struct CBenchProgressSync
{
  bool Exit; // GUI asks BenchThread to Exit, and BenchThread reads that variable
  bool TextWasChanged;

  UInt32 NumThreads;
  UInt64 DictSize;
  UInt32 NumPasses_Limit;
  int Level;

  AString Text;

  /* BenchFinish_Task_HRESULT    - for result from benchmark code
     BenchFinish_Thread_HRESULT  - for Exceptions and service errors
             these arreos must be shown even if user escapes benchmark */
  HRESULT BenchFinish_Task_HRESULT;
  HRESULT BenchFinish_Thread_HRESULT;

  UInt32 NumFreqThreadsPrev;
  UString FreqString_Sync;
  UString FreqString_GUI;

  // must be written by benchmark thread, read by GUI thread */
  CRecordVector<CBenchPassResult> RatingVector;
  CSyncData sd;

  NWindows::NSynchronization::CCriticalSection CS;

  CBenchProgressSync()
  {
    NumPasses_Limit = 1;
  }

  void Init();
  
  void SendExit()
  {
    NWindows::NSynchronization::CCriticalSectionLock lock(CS);
    Exit = true;
  }
};


void CBenchProgressSync::Init()
{
  Exit = false;
  
  BenchFinish_Task_HRESULT = S_OK;
  BenchFinish_Thread_HRESULT = S_OK;
  
  sd.Init();
  RatingVector.Clear();
  
  NumFreqThreadsPrev = 0;
  FreqString_Sync.Empty();
  FreqString_GUI.Empty();
  
  Text.Empty();
  TextWasChanged = true;
}



struct CMyFont
{
  HFONT _font;
  CMyFont(): _font(NULL) {}
  ~CMyFont()
  {
    if (_font)
      DeleteObject(_font);
  }
  void Create(const LOGFONT *lplf)
  {
    _font = CreateFontIndirect(lplf);
  }
};


class CBenchmarkDialog;

struct CThreadBenchmark
{
  CBenchmarkDialog *BenchmarkDialog;
  DECL_EXTERNAL_CODECS_LOC_VARS_DECL
  // HRESULT Result;

  HRESULT Process();
  static THREAD_FUNC_DECL MyThreadFunction(void *param)
  {
    /* ((CThreadBenchmark *)param)->Result = */
    ((CThreadBenchmark *)param)->Process();
    return 0;
  }
};


class CBenchmarkDialog:
  public NWindows::NControl::CModalDialog
{
  bool _finishTime_WasSet;
  
  bool WasStopped_in_GUI;
  bool ExitWasAsked_in_GUI;
  bool NeedRestart;

  bool RamSize_Defined;

public:
  bool TotalMode;

private:

  NWindows::NControl::CComboBox m_Dictionary;
  NWindows::NControl::CComboBox m_NumThreads;
  NWindows::NControl::CComboBox m_NumPasses;
  NWindows::NControl::CEdit _consoleEdit;
  UINT_PTR _timer;

  UInt32 _startTime;
  UInt32 _finishTime;

  CMyFont _font;

  size_t RamSize;
  size_t RamSize_Limit;

  UInt32 NumPasses_Finished_Prev;

  UString ElapsedSec_Prev;

  void InitSyncNew()
  {
    NumPasses_Finished_Prev = (UInt32)(Int32)-1;
    ElapsedSec_Prev.Empty();
    Sync.Init();
  }

  virtual bool OnInit() Z7_override;
  virtual bool OnDestroy() Z7_override;
  virtual bool OnSize(WPARAM /* wParam */, int xSize, int ySize) Z7_override;
  virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override;
  virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam) Z7_override;
  virtual void OnHelp() Z7_override;
  virtual void OnCancel() Z7_override;
  virtual bool OnTimer(WPARAM timerID, LPARAM callback) Z7_override;
  virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override;

  void Disable_Stop_Button();
  void OnStopButton();
  void RestartBenchmark();
  void StartBenchmark();

  void UpdateGui();

  void PrintTime();
  void PrintRating(UInt64 rating, UINT controlID);
  void PrintUsage(UInt64 usage, UINT controlID);
  void PrintBenchRes(const CTotalBenchRes2 &info, const UINT ids[]);

  UInt32 GetNumberOfThreads();
  size_t OnChangeDictionary();

  void SetItemText_Number(unsigned itemID, UInt64 val, LPCTSTR post = NULL);
  void Print_MemUsage(UString &s, UInt64 memUsage) const;
  bool IsMemoryUsageOK(UInt64 memUsage) const
    { return memUsage + (1 << 20) <= RamSize_Limit; }

  void MyKillTimer();

  void SendExit_Status(const wchar_t *message)
  {
    SetItemText(IDT_BENCH_ERROR_MESSAGE, message);
    Sync.SendExit();
  }

public:
  CBenchProgressSync Sync;

  CObjectVector<CProperty> Props;

  CSysString Bench2Text;

  NWindows::CThread _thread;
  CThreadBenchmark _threadBenchmark;

  CBenchmarkDialog():
      WasStopped_in_GUI(false),
      ExitWasAsked_in_GUI(false),
      NeedRestart(false),
      TotalMode(false),
      _timer(0)
      {}

  ~CBenchmarkDialog() Z7_DESTRUCTOR_override;

  bool PostMsg_Finish(WPARAM wparam)
  {
    if ((HWND)*this)
      return PostMsg(k_Message_Finished, wparam);
    // the (HWND)*this is NULL only for some internal code failure
    return true;
  }

  INT_PTR Create(HWND wndParent = NULL)
  {
    BIG_DIALOG_SIZE(332, 228);
    return CModalDialog::Create(TotalMode ? IDD_BENCH_TOTAL : SIZED_DIALOG(IDD_BENCH), wndParent);
  }
  void MessageBoxError(LPCWSTR message)
  {
    MessageBoxW(*this, message, L"7-Zip", MB_ICONERROR);
  }
  void MessageBoxError_Status(LPCWSTR message)
  {
    UString s ("ERROR: ");
    s += message;
    MessageBoxError(s);
    SetItemText(IDT_BENCH_ERROR_MESSAGE, s);
  }
};









UString HResultToMessage(HRESULT errorCode);

#ifdef Z7_LANG
static const UInt32 kLangIDs[] =
{
  IDT_BENCH_DICTIONARY,
  IDT_BENCH_MEMORY,
  IDT_BENCH_NUM_THREADS,
  IDT_BENCH_SIZE,
  IDT_BENCH_RATING_LABEL,
  IDT_BENCH_USAGE_LABEL,
  IDT_BENCH_RPU_LABEL,
  IDG_BENCH_COMPRESSING,
  IDG_BENCH_DECOMPRESSING,
  IDG_BENCH_TOTAL_RATING,
  IDT_BENCH_CURRENT,
  IDT_BENCH_RESULTING,
  IDT_BENCH_ELAPSED,
  IDT_BENCH_PASSES,
  IDB_STOP,
  IDB_RESTART
};

static const UInt32 kLangIDs_RemoveColon[] =
{
  IDT_BENCH_SPEED
};

#endif

static LPCTSTR const kProcessingString = TEXT("...");
static LPCTSTR const kGB = TEXT(" GB");
static LPCTSTR const kMB = TEXT(" MB");
static LPCTSTR const kKB = TEXT(" KB");
// static LPCTSTR const kMIPS = TEXT(" MIPS");
static LPCTSTR const kKBs = TEXT(" KB/s");

static const unsigned kMinDicLogSize = 18;

static const UInt32 kMinDicSize = (UInt32)1 << kMinDicLogSize;
static const size_t kMaxDicSize = (size_t)1 << (22 + sizeof(size_t) / 4 * 5);
// static const size_t kMaxDicSize = (size_t)1 << 16;
    /*
    #ifdef MY_CPU_64BIT
      (UInt32)(Int32)-1; // we can use it, if we want 4 GB buffer
      // (UInt32)15 << 28;
    #else
      (UInt32)1 << 27;
    #endif
    */


static int ComboBox_Add_UInt32(NWindows::NControl::CComboBox &cb, UInt32 v)
{
  TCHAR s[16];
  ConvertUInt32ToString(v, s);
  const int index = (int)cb.AddString(s);
  cb.SetItemData(index, (LPARAM)v);
  return index;
}


bool CBenchmarkDialog::OnInit()
{
  #ifdef Z7_LANG
  LangSetWindowText(*this, IDD_BENCH);
  LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs));
  LangSetDlgItems_RemoveColon(*this, kLangIDs_RemoveColon, Z7_ARRAY_SIZE(kLangIDs_RemoveColon));
  LangSetDlgItemText(*this, IDT_BENCH_CURRENT2, IDT_BENCH_CURRENT);
  LangSetDlgItemText(*this, IDT_BENCH_RESULTING2, IDT_BENCH_RESULTING);
  #endif

  InitSyncNew();

  if (TotalMode)
  {
    _consoleEdit.Attach(GetItem(IDE_BENCH2_EDIT));
    LOGFONT f;
    memset(&f, 0, sizeof(f));
    f.lfHeight = 14;
    f.lfWidth = 0;
    f.lfWeight = FW_DONTCARE;
    f.lfCharSet = DEFAULT_CHARSET;
    f.lfOutPrecision = OUT_DEFAULT_PRECIS;
    f.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    f.lfQuality = DEFAULT_QUALITY;

    f.lfPitchAndFamily = FIXED_PITCH;
    // MyStringCopy(f.lfFaceName, TEXT(""));
    // f.lfFaceName[0] = 0;
    _font.Create(&f);
    if (_font._font)
      _consoleEdit.SendMsg(WM_SETFONT, (WPARAM)_font._font, TRUE);
  }

  UInt32 numCPUs = 1;

  {
    AString s ("/ ");
  
    NSystem::CProcessAffinity threadsInfo;
    threadsInfo.InitST();

    #ifndef Z7_ST
    if (threadsInfo.Get() && threadsInfo.processAffinityMask != 0)
      numCPUs = threadsInfo.GetNumProcessThreads();
    else
      numCPUs = NSystem::GetNumberOfProcessors();
    #endif

    s.Add_UInt32(numCPUs);
    s += GetProcessThreadsInfo(threadsInfo);
    SetItemTextA(IDT_BENCH_HARDWARE_THREADS, s);
  
    {
      AString s2;
      GetSysInfo(s, s2);
      SetItemTextA(IDT_BENCH_SYS1, s);
      if (s != s2 && !s2.IsEmpty())
        SetItemTextA(IDT_BENCH_SYS2, s2);
    }
    {
      AString registers;
      GetCpuName_MultiLine(s, registers);
      SetItemTextA(IDT_BENCH_CPU, s);
    }
    {
      GetOsInfoText(s);
      s += " : ";
      AddCpuFeatures(s);
      SetItemTextA(IDT_BENCH_CPU_FEATURE, s);
    }

    s = "7-Zip " MY_VERSION_CPU;
    SetItemTextA(IDT_BENCH_VER, s);
  }


  // ----- Num Threads ----------

  if (numCPUs < 1)
    numCPUs = 1;
  numCPUs = MyMin(numCPUs, (UInt32)(1 << 6)); // it's WIN32 limit

  UInt32 numThreads = Sync.NumThreads;

  if (numThreads == (UInt32)(Int32)-1)
    numThreads = numCPUs;
  if (numThreads > 1)
    numThreads &= ~(UInt32)1;
  const UInt32 kNumThreadsMax = (1 << 12);
  if (numThreads > kNumThreadsMax)
    numThreads = kNumThreadsMax;

  m_NumThreads.Attach(GetItem(IDC_BENCH_NUM_THREADS));
  const UInt32 numTheads_Combo = numCPUs * 2;
  UInt32 v = 1;
  int cur = 0;
  for (; v <= numTheads_Combo;)
  {
    int index = ComboBox_Add_UInt32(m_NumThreads, v);
    const UInt32 vNext = v + (v < 2 ? 1 : 2);
    if (v <= numThreads)
    if (numThreads < vNext || vNext > numTheads_Combo)
    {
      if (v != numThreads)
        index = ComboBox_Add_UInt32(m_NumThreads, numThreads);
      cur = index;
    }
    v = vNext;
  }
  m_NumThreads.SetCurSel(cur);
  Sync.NumThreads = GetNumberOfThreads();


  // ----- Dictionary ----------

  m_Dictionary.Attach(GetItem(IDC_BENCH_DICTIONARY));
  
  RamSize = (UInt64)(sizeof(size_t)) << 29;
  RamSize_Defined = NSystem::GetRamSize(RamSize);

  
  #ifdef UNDER_CE
  const UInt32 kNormalizedCeSize = (16 << 20);
  if (RamSize > kNormalizedCeSize && RamSize < (33 << 20))
    RamSize = kNormalizedCeSize;
  #endif
  RamSize_Limit = RamSize / 16 * 15;

  if (Sync.DictSize == (UInt64)(Int64)-1)
  {
    unsigned dicSizeLog = 25;
    #ifdef UNDER_CE
    dicSizeLog = 20;
    #endif
    if (RamSize_Defined)
    for (; dicSizeLog > kBenchMinDicLogSize; dicSizeLog--)
      if (IsMemoryUsageOK(GetBenchMemoryUsage(
          Sync.NumThreads, Sync.Level, (UInt64)1 << dicSizeLog, TotalMode)))
        break;
    Sync.DictSize = (UInt64)1 << dicSizeLog;
  }
  
  if (Sync.DictSize < kMinDicSize) Sync.DictSize = kMinDicSize;
  if (Sync.DictSize > kMaxDicSize) Sync.DictSize = kMaxDicSize;

  cur = 0;
  for (unsigned i = (kMinDicLogSize - 1) * 2; i <= (32 - 1) * 2; i++)
   {
      const size_t dict = (size_t)(2 + (i & 1)) << (i / 2);
      // if (i == (32 - 1) * 2) dict = kMaxDicSize;
      TCHAR s[32];
      const TCHAR *post;
      UInt32 d;
           if (dict >= ((UInt32)1 << 31)) { d = (UInt32)(dict >> 30); post = kGB; }
      else if (dict >= ((UInt32)1 << 21)) { d = (UInt32)(dict >> 20); post = kMB; }
      else                                { d = (UInt32)(dict >> 10); post = kKB; }
      ConvertUInt32ToString(d, s);
      lstrcat(s, post);
      const int index = (int)m_Dictionary.AddString(s);
      m_Dictionary.SetItemData(index, (LPARAM)dict);
      if (dict <= Sync.DictSize)
        cur = index;
      if (dict >= kMaxDicSize)
        break;
    }
  m_Dictionary.SetCurSel(cur);


  // ----- Num Passes ----------

  m_NumPasses.Attach(GetItem(IDC_BENCH_NUM_PASSES));
  cur = 0;
  v = 1;
  for (;;)
  {
    int index = ComboBox_Add_UInt32(m_NumPasses, v);
    const bool isLast = (v >= 10000000);
    UInt32 vNext = v * 10;
         if (v < 2) vNext = 2;
    else if (v < 5) vNext = 5;
    else if (v < 10) vNext = 10;

    if (v <= Sync.NumPasses_Limit)
    if (isLast || Sync.NumPasses_Limit < vNext)
    {
      if (v != Sync.NumPasses_Limit)
        index = ComboBox_Add_UInt32(m_NumPasses, Sync.NumPasses_Limit);
      cur = index;
    }
    v = vNext;
    if (isLast)
      break;
  }
  m_NumPasses.SetCurSel(cur);

  if (TotalMode)
    NormalizeSize(true);
  else
    NormalizePosition();

  RestartBenchmark();

  return CModalDialog::OnInit();
}


bool CBenchmarkDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize)
{
  int mx, my;
  GetMargins(8, mx, my);

  if (!TotalMode)
  {
    RECT rect;
    GetClientRectOfItem(IDT_BENCH_LOG, rect);
    int x = xSize - rect.left - mx;
    int y = ySize - rect.top - my;
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    MoveItem(IDT_BENCH_LOG, rect.left, rect.top, x, y, true);
    return false;
  }

  int bx1, bx2, by;

  GetItemSizes(IDCANCEL, bx1, by);
  GetItemSizes(IDHELP, bx2, by);

  {
    int y = ySize - my - by;
    int x = xSize - mx - bx1;
    
    InvalidateRect(NULL);
    
    MoveItem(IDCANCEL, x, y, bx1, by);
    MoveItem(IDHELP, x - mx - bx2, y, bx2, by);
  }

  if (_consoleEdit)
  {
    int yPos = ySize - my - by;
    RECT rect;
    GetClientRectOfItem(IDE_BENCH2_EDIT, rect);
    int y = rect.top;
    int ySize2 = yPos - my - y;
    const int kMinYSize = 20;
    int xx = xSize - mx * 2;
    if (ySize2 < kMinYSize)
    {
      ySize2 = kMinYSize;
    }
    _consoleEdit.Move(mx, y, xx, ySize2);
  }
  return false;
}


UInt32 CBenchmarkDialog::GetNumberOfThreads()
{
  return (UInt32)m_NumThreads.GetItemData_of_CurSel();
}


#define UINT_TO_STR_3(s, val) { \
  s[0] = (wchar_t)('0' + (val) / 100); \
  s[1] = (wchar_t)('0' + (val) % 100 / 10); \
  s[2] = (wchar_t)('0' + (val) % 10); \
  s += 3; s[0] = 0; }

static WCHAR *NumberToDot3(UInt64 val, WCHAR *s)
{
  s = ConvertUInt64ToString(val / 1000, s);
  const UInt32 rem = (UInt32)(val % 1000);
  *s++ = '.';
  UINT_TO_STR_3(s, rem)
  return s;
}

void CBenchmarkDialog::SetItemText_Number(unsigned itemID, UInt64 val, LPCTSTR post)
{
  TCHAR s[64];
  ConvertUInt64ToString(val, s);
  if (post)
    lstrcat(s, post);
  SetItemText(itemID, s);
}

static void AddSize_MB(UString &s, UInt64 size)
{
  s.Add_UInt64((size + (1 << 20) - 1) >> 20);
  s += kMB;
}

void CBenchmarkDialog::Print_MemUsage(UString &s, UInt64 memUsage) const
{
  AddSize_MB(s, memUsage);
  if (RamSize_Defined)
  {
    s += " / ";
    AddSize_MB(s, RamSize);
  }
}

size_t CBenchmarkDialog::OnChangeDictionary()
{
  const size_t dict = (size_t)m_Dictionary.GetItemData_of_CurSel();
  const UInt64 memUsage = GetBenchMemoryUsage(GetNumberOfThreads(),
      Sync.Level,
      dict,
      false); // totalBench mode

  UString s;
  Print_MemUsage(s, memUsage);

  #ifdef Z7_LARGE_PAGES
  {
    AString s2;
    Add_LargePages_String(s2);
    if (!s2.IsEmpty())
    {
      s.Add_Space();
      s += s2;
    }
  }
  #endif

  SetItemText(IDT_BENCH_MEMORY_VAL, s);

  return dict;
}


static const UInt32 g_IDs[] =
{
  IDT_BENCH_COMPRESS_SIZE1,
  IDT_BENCH_COMPRESS_SIZE2,
  IDT_BENCH_COMPRESS_USAGE1,
  IDT_BENCH_COMPRESS_USAGE2,
  IDT_BENCH_COMPRESS_SPEED1,
  IDT_BENCH_COMPRESS_SPEED2,
  IDT_BENCH_COMPRESS_RATING1,
  IDT_BENCH_COMPRESS_RATING2,
  IDT_BENCH_COMPRESS_RPU1,
  IDT_BENCH_COMPRESS_RPU2,
  
  IDT_BENCH_DECOMPR_SIZE1,
  IDT_BENCH_DECOMPR_SIZE2,
  IDT_BENCH_DECOMPR_SPEED1,
  IDT_BENCH_DECOMPR_SPEED2,
  IDT_BENCH_DECOMPR_RATING1,
  IDT_BENCH_DECOMPR_RATING2,
  IDT_BENCH_DECOMPR_USAGE1,
  IDT_BENCH_DECOMPR_USAGE2,
  IDT_BENCH_DECOMPR_RPU1,
  IDT_BENCH_DECOMPR_RPU2,
  
  IDT_BENCH_TOTAL_USAGE_VAL,
  IDT_BENCH_TOTAL_RATING_VAL,
  IDT_BENCH_TOTAL_RPU_VAL
};
  

static const unsigned k_Ids_Enc_1[] = {
  IDT_BENCH_COMPRESS_USAGE1,
  IDT_BENCH_COMPRESS_SPEED1,
  IDT_BENCH_COMPRESS_RPU1,
  IDT_BENCH_COMPRESS_RATING1,
  IDT_BENCH_COMPRESS_SIZE1 };

static const unsigned k_Ids_Enc[] = {
  IDT_BENCH_COMPRESS_USAGE2,
  IDT_BENCH_COMPRESS_SPEED2,
  IDT_BENCH_COMPRESS_RPU2,
  IDT_BENCH_COMPRESS_RATING2,
  IDT_BENCH_COMPRESS_SIZE2 };

static const unsigned k_Ids_Dec_1[] = {
  IDT_BENCH_DECOMPR_USAGE1,
  IDT_BENCH_DECOMPR_SPEED1,
  IDT_BENCH_DECOMPR_RPU1,
  IDT_BENCH_DECOMPR_RATING1,
  IDT_BENCH_DECOMPR_SIZE1 };

static const unsigned k_Ids_Dec[] = {
  IDT_BENCH_DECOMPR_USAGE2,
  IDT_BENCH_DECOMPR_SPEED2,
  IDT_BENCH_DECOMPR_RPU2,
  IDT_BENCH_DECOMPR_RATING2,
  IDT_BENCH_DECOMPR_SIZE2 };

static const unsigned k_Ids_Tot[] = {
  IDT_BENCH_TOTAL_USAGE_VAL,
  0,
  IDT_BENCH_TOTAL_RPU_VAL,
  IDT_BENCH_TOTAL_RATING_VAL,
  0 };


void CBenchmarkDialog::MyKillTimer()
{
  if (_timer != 0)
  {
    KillTimer(kTimerID);
    _timer = 0;
  }
}


bool CBenchmarkDialog::OnDestroy()
{
  /* actually timer was removed before.
     also the timer must be removed by Windows, when window  will be removed. */
  MyKillTimer(); // it's optional code
  return false; // we return (false) to perform default dialog operation
}

void SetErrorMessage_MemUsage(UString &s, UInt64 reqSize, UInt64 ramSize, UInt64 ramLimit, const UString &usageString);

void CBenchmarkDialog::StartBenchmark()
{
  NeedRestart = false;
  WasStopped_in_GUI = false;

  SetItemText_Empty(IDT_BENCH_ERROR_MESSAGE);
  
  MyKillTimer(); // optional code. timer was killed before

  const size_t dict = OnChangeDictionary();
  const UInt32 numThreads = GetNumberOfThreads();
  const UInt32 numPasses = (UInt32)m_NumPasses.GetItemData_of_CurSel();

  for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_IDs); i++)
    SetItemText(g_IDs[i], kProcessingString);

  SetItemText_Empty(IDT_BENCH_LOG);
  SetItemText_Empty(IDT_BENCH_ELAPSED_VAL);
  SetItemText_Empty(IDT_BENCH_ERROR_MESSAGE);
  
  const UInt64 memUsage = GetBenchMemoryUsage(numThreads, Sync.Level, dict,
      false); // totalBench
  if (!IsMemoryUsageOK(memUsage))
  {
    UString s2;
    LangString_OnlyFromLangFile(IDS_MEM_REQUIRED_MEM_SIZE, s2);
    if (s2.IsEmpty())
    {
      s2 = LangString(IDT_BENCH_MEMORY);
      if (s2.IsEmpty())
        GetItemText(IDT_BENCH_MEMORY, s2);
      s2.RemoveChar(L':');
    }
    UString s;
    SetErrorMessage_MemUsage(s, memUsage, RamSize, RamSize_Limit, s2);
    MessageBoxError_Status(s);
    return;
  }

  EnableItem(IDB_STOP, true);

  _startTime = GetTickCount();
  _finishTime = _startTime;
  _finishTime_WasSet = false;

  {
    NWindows::NSynchronization::CCriticalSectionLock lock(Sync.CS);
    InitSyncNew();
    Sync.DictSize = dict;
    Sync.NumThreads = numThreads;
    Sync.NumPasses_Limit = numPasses;
  }

  PrintTime();

  _timer = SetTimer(kTimerID, kTimerElapse);
  if (_thread.Create(CThreadBenchmark::MyThreadFunction, &_threadBenchmark) != 0)
  {
    MyKillTimer();
    MessageBoxError_Status(L"Can't create thread");
  }
  return;
}


void CBenchmarkDialog::RestartBenchmark()
{
  if (ExitWasAsked_in_GUI)
    return;

  if (_thread.IsCreated())
  {
    NeedRestart = true;
    SendExit_Status(L"Stop for restart ...");
  }
  else
    StartBenchmark();
}


void CBenchmarkDialog::Disable_Stop_Button()
{
  // if we disable focused button, then focus will be lost
  if (GetFocus() == GetItem(IDB_STOP))
  {
    // SendMsg_NextDlgCtl_Prev();
    SendMsg_NextDlgCtl_CtlId(IDB_RESTART);
  }
  EnableItem(IDB_STOP, false);
}


void CBenchmarkDialog::OnStopButton()
{
  if (ExitWasAsked_in_GUI)
    return;

  Disable_Stop_Button();

  WasStopped_in_GUI = true;
  if (_thread.IsCreated())
  {
    SendExit_Status(L"Stop ...");
  }
}



void CBenchmarkDialog::OnCancel()
{
  ExitWasAsked_in_GUI = true;
  
  /*
  SendMsg_NextDlgCtl_Prev();
  EnableItem(IDCANCEL, false);
  */

  if (_thread.IsCreated())
    SendExit_Status(L"Cancel ...");
  else
    CModalDialog::OnCancel();
}


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



// void GetTimeString(UInt64 timeValue, wchar_t *s);

void CBenchmarkDialog::PrintTime()
{
  const UInt32 curTime =
    _finishTime_WasSet ?
      _finishTime :
      ::GetTickCount();

  const UInt32 elapsedTime = (curTime - _startTime);

  WCHAR s[64];

  WCHAR *p = ConvertUInt32ToString(elapsedTime / 1000, s);

  if (_finishTime_WasSet)
  {
    *p++ = '.';
    UINT_TO_STR_3(p, elapsedTime % 1000)
  }

  // p = NumberToDot3((UInt64)elapsedTime, s);

  MyStringCopy(p, L" s");

  // if (WasStopped_in_GUI) wcscat(s, L" X"); // for debug

  if (s == ElapsedSec_Prev)
    return;

  ElapsedSec_Prev = s;

  // static cnt = 0; cnt++; wcscat(s, L" ");
  // UString s2; s2.Add_UInt32(cnt); wcscat(s, s2.Ptr());

  SetItemText(IDT_BENCH_ELAPSED_VAL, s);
}


static UInt64 GetMips(UInt64 ips)
{
  return (ips + 500000) / 1000000;
}


static UInt64 GetUsagePercents(UInt64 usage)
{
  return Benchmark_GetUsage_Percents(usage);
}


static UInt32 GetRating(const CTotalBenchRes &info)
{
  UInt64 numIter = info.NumIterations2;
  if (numIter == 0)
    numIter = 1000000;
  const UInt64 rating64 = GetMips(info.Rating / numIter);
  // return rating64;
  UInt32 rating32 = (UInt32)rating64;
  if (rating32 != rating64)
    rating32 = (UInt32)(Int32)-1;
  return rating32;
}


static void AddUsageString(UString &s, const CTotalBenchRes &info)
{
  UInt64 numIter = info.NumIterations2;
  if (numIter == 0)
    numIter = 1000000;
  UInt64 usage = GetUsagePercents(info.Usage / numIter);

  wchar_t w[64];
  ConvertUInt64ToString(usage, w);
  unsigned len = MyStringLen(w);
  while (len < 5)
  {
    s.Add_Space();
    len++;
  }
  s += w;
  s += "%";
}


static void Add_Dot3String(UString &s, UInt64 val)
{
  WCHAR temp[32];
  NumberToDot3(val, temp);
  s += temp;
}


static void AddRatingString(UString &s, const CTotalBenchRes &info)
{
  // AddUsageString(s, info);
  // s.Add_Space();
  // s.Add_UInt32(GetRating(info));
  Add_Dot3String(s, GetRating(info));
}


static void AddRatingsLine(UString &s, const CTotalBenchRes &enc, const CTotalBenchRes &dec
    #ifdef PRINT_ITER_TIME
    , DWORD ticks
    #endif
    )
{
  // AddUsageString(s, enc); s.Add_Space();

  AddRatingString(s, enc);
  s += "  ";
  AddRatingString(s, dec);
  
  CTotalBenchRes tot_BenchRes;
  tot_BenchRes.SetSum(enc, dec);
  
  s += "  ";
  AddRatingString(s, tot_BenchRes);
  
  s.Add_Space();  AddUsageString(s, tot_BenchRes);

  
  #ifdef PRINT_ITER_TIME
  s.Add_Space();
  {
    Add_Dot3String(s, ticks;
    s += " s";
    // s.Add_UInt32(ticks); s += " ms";
  }
  #endif
}


void CBenchmarkDialog::PrintRating(UInt64 rating, UINT controlID)
{
  // SetItemText_Number(controlID, GetMips(rating), kMIPS);
  WCHAR s[64];
  MyStringCopy(NumberToDot3(GetMips(rating), s), L" GIPS");
  SetItemText(controlID, s);
}

void CBenchmarkDialog::PrintUsage(UInt64 usage, UINT controlID)
{
  SetItemText_Number(controlID, GetUsagePercents(usage), TEXT("%"));
}


// void SetItemText_Number

void CBenchmarkDialog::PrintBenchRes(
    const CTotalBenchRes2 &info,
    const UINT ids[])
{
  if (info.NumIterations2 == 0)
    return;
  if (ids[1] != 0)
    SetItemText_Number(ids[1], (info.Speed >> 10) / info.NumIterations2, kKBs);
  PrintRating(info.Rating / info.NumIterations2, ids[3]);
  PrintRating(info.RPU / info.NumIterations2, ids[2]);
  PrintUsage(info.Usage / info.NumIterations2, ids[0]);
  if (ids[4] != 0)
  {
    UInt64 val = info.UnpackSize;
    LPCTSTR kPostfix;
    if (val >= ((UInt64)1 << 40))
    {
      kPostfix = kGB;
      val >>= 30;
    }
    else
    {
      kPostfix = kMB;
      val >>= 20;
    }
    SetItemText_Number(ids[4], val, kPostfix);
  }
}


// static UInt32 k_Message_Finished_cnt = 0;
// static UInt32 k_OnTimer_cnt = 0;

bool CBenchmarkDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
  if (message != k_Message_Finished)
    return CModalDialog::OnMessage(message, wParam, lParam);

  {
    if (wParam == k_Msg_WPARM_Thread_Finished)
    {
      _finishTime = GetTickCount();
      _finishTime_WasSet = true;
      MyKillTimer();

      if (_thread.Wait_Close() != 0)
      {
        MessageBoxError_Status(L"Thread Wait Error");
      }

      if (!WasStopped_in_GUI)
      {
        WasStopped_in_GUI = true;
        Disable_Stop_Button();
      }

      HRESULT res = Sync.BenchFinish_Thread_HRESULT;
      if (res != S_OK)
      // if (!ExitWasAsked_in_GUI || res != E_ABORT)
        MessageBoxError_Status(HResultToMessage(res));

      if (ExitWasAsked_in_GUI)
      {
        // SetItemText(IDT_BENCH_ERROR_MESSAGE, "before CModalDialog::OnCancel()");
        // Sleep (2000);
        // MessageBoxError(L"test");
        CModalDialog::OnCancel();
        return true;
      }
    
      SetItemText_Empty(IDT_BENCH_ERROR_MESSAGE);

      res = Sync.BenchFinish_Task_HRESULT;
      if (res != S_OK)
      {
        if (!WasStopped_in_GUI || res != E_ABORT)
        {
          UString m;
          if (res == S_FALSE)
            m = "Decoding error";
          else if (res == CLASS_E_CLASSNOTAVAILABLE)
            m = "Can't find 7z.dll";
          else
            m = HResultToMessage(res);
          MessageBoxError_Status(m);
        }
      }

      if (NeedRestart)
      {
        StartBenchmark();
        return true;
      }
    }
    // k_Message_Finished_cnt++;
    UpdateGui();
    return true;
  }
}


bool CBenchmarkDialog::OnTimer(WPARAM timerID, LPARAM /* callback */)
{
  // k_OnTimer_cnt++;
  if (timerID == kTimerID)
    UpdateGui();
  return true;
}


void CBenchmarkDialog::UpdateGui()
{
  PrintTime();

  if (TotalMode)
  {
    bool wasChanged = false;
    {
      NWindows::NSynchronization::CCriticalSectionLock lock(Sync.CS);
      
      if (Sync.TextWasChanged)
      {
        wasChanged = true;
        Bench2Text += Sync.Text;
        Sync.Text.Empty();
        Sync.TextWasChanged = false;
      }
    }
    if (wasChanged)
      _consoleEdit.SetText(Bench2Text);
    return;
  }

  CSyncData sd;
  CRecordVector<CBenchPassResult> RatingVector;

  {
    NWindows::NSynchronization::CCriticalSectionLock lock(Sync.CS);
    sd = Sync.sd;

    if (sd.NeedPrint_RatingVector)
      RatingVector = Sync.RatingVector;
    
    if (sd.NeedPrint_Freq)
    {
      Sync.FreqString_GUI = Sync.FreqString_Sync;
      sd.NeedPrint_RatingVector = true;
    }

    Sync.sd.NeedPrint_RatingVector = false;
    Sync.sd.NeedPrint_Enc_1 = false;
    Sync.sd.NeedPrint_Enc   = false;
    Sync.sd.NeedPrint_Dec_1 = false;
    Sync.sd.NeedPrint_Dec   = false;
    Sync.sd.NeedPrint_Tot   = false;
    Sync.sd.NeedPrint_Freq = false;
  }

  if (sd.NumPasses_Finished != NumPasses_Finished_Prev)
  {
    SetItemText_Number(IDT_BENCH_PASSES_VAL, sd.NumPasses_Finished, TEXT(" /"));
    NumPasses_Finished_Prev = sd.NumPasses_Finished;
  }

  if (sd.NeedPrint_Enc_1) PrintBenchRes(sd.Enc_BenchRes_1, k_Ids_Enc_1);
  if (sd.NeedPrint_Enc)   PrintBenchRes(sd.Enc_BenchRes,   k_Ids_Enc);
  if (sd.NeedPrint_Dec_1) PrintBenchRes(sd.Dec_BenchRes_1, k_Ids_Dec_1);
  if (sd.NeedPrint_Dec)   PrintBenchRes(sd.Dec_BenchRes,   k_Ids_Dec);
 
  if (sd.BenchWasFinished && sd.NeedPrint_Tot)
  {
    CTotalBenchRes2 tot_BenchRes = sd.Enc_BenchRes;
    tot_BenchRes.Update_With_Res2(sd.Dec_BenchRes);
    PrintBenchRes(tot_BenchRes, k_Ids_Tot);
  }


  if (sd.NeedPrint_RatingVector)
  // for (unsigned k = 0; k < 1; k++)
  {
    UString s;
    s += Sync.FreqString_GUI;
    if (!RatingVector.IsEmpty())
    {
      if (!s.IsEmpty())
        s.Add_LF();
      s += "Compr Decompr Total   CPU"
          #ifdef PRINT_ITER_TIME
          "   Time"
          #endif
          ;
      s.Add_LF();
    }
    // s += "GIPS    GIPS   GIPS    %   s"; s.Add_LF();
    for (unsigned i = 0; i < RatingVector.Size(); i++)
    {
      if (i != 0)
        s.Add_LF();
      if ((int)i == sd.RatingVector_DeletedIndex)
      {
        s += "...";
        s.Add_LF();
      }
      const CBenchPassResult &pair = RatingVector[i];
      /*
        s += "g:"; s.Add_UInt32((UInt32)pair.EncInfo.GlobalTime);
        s += " u:"; s.Add_UInt32((UInt32)pair.EncInfo.UserTime);
        s.Add_Space();
      */
      AddRatingsLine(s, pair.Enc, pair.Dec
            #ifdef PRINT_ITER_TIME
            , pair.Ticks
            #endif
            );
      /*
      {
        UInt64 v = i + 1;
        if (sd.RatingVector_DeletedIndex >= 0 && i >= (unsigned)sd.RatingVector_DeletedIndex)
          v += sd.RatingVector_NumDeleted;
        char temp[64];
        ConvertUInt64ToString(v, temp);
        s += " : ";
        s += temp;
      }
      */
    }

    if (sd.BenchWasFinished)
    {
      s.Add_LF();
      s += "-------------";
      s.Add_LF();
      {
        // average time is not correct because of freq detection in first iteration
        AddRatingsLine(s, sd.Enc_BenchRes, sd.Dec_BenchRes
              #ifdef PRINT_ITER_TIME
              , (DWORD)(sd.TotalTicks / (sd.NumPasses_Finished ? sd.NumPasses_Finished : 1))
              #endif
              );
      }
    }
    // s.Add_LF(); s += "OnTimer: "; s.Add_UInt32(k_OnTimer_cnt);
    // s.Add_LF(); s += "finished Message: "; s.Add_UInt32(k_Message_Finished_cnt);
    // static cnt = 0; cnt++; s.Add_LF(); s += "Print: "; s.Add_UInt32(cnt);
    // s.Add_LF(); s += "NumEncProgress: "; s.Add_UInt32((UInt32)sd.NumEncProgress);
    // s.Add_LF(); s += "NumDecProgress: "; s.Add_UInt32((UInt32)sd.NumDecProgress);
    SetItemText(IDT_BENCH_LOG, s);
  }
}


bool CBenchmarkDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam)
{
  if (code == CBN_SELCHANGE &&
      (itemID == IDC_BENCH_DICTIONARY ||
       itemID == IDC_BENCH_NUM_PASSES ||
       itemID == IDC_BENCH_NUM_THREADS))
  {
    RestartBenchmark();
    return true;
  }
  return CModalDialog::OnCommand(code, itemID, lParam);
}


bool CBenchmarkDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND)
{
  switch (buttonID)
  {
    case IDB_RESTART:
      RestartBenchmark();
      return true;
    case IDB_STOP:
      OnStopButton();
      return true;
  }
  return CModalDialog::OnButtonClicked(buttonID, buttonHWND);
}





// ---------- Benchmark Thread ----------

struct CBenchCallback Z7_final: public IBenchCallback
{
  UInt64 dictionarySize;
  CBenchProgressSync *Sync;
  CBenchmarkDialog *BenchmarkDialog;
  
  HRESULT SetEncodeResult(const CBenchInfo &info, bool final) Z7_override;
  HRESULT SetDecodeResult(const CBenchInfo &info, bool final) Z7_override;
};

HRESULT CBenchCallback::SetEncodeResult(const CBenchInfo &info, bool final)
{
  bool needPost = false;
  {
    NSynchronization::CCriticalSectionLock lock(Sync->CS);
    if (Sync->Exit)
      return E_ABORT;
    CSyncData &sd = Sync->sd;
    // sd.NumEncProgress++;
    CTotalBenchRes2 &br = sd.Enc_BenchRes_1;
    {
      UInt64 dictSize = Sync->DictSize;
      if (final)
      {
        // sd.EncInfo = info;
      }
      else
      {
        /* if (!final), then CBenchInfo::NumIterations means totalNumber of threads.
           so we can reduce the dictionary */
        if (dictSize > info.UnpackSize)
          dictSize = info.UnpackSize;
      }
      br.Rating = info.GetRating_LzmaEnc(dictSize);
    }
    br.SetFrom_BenchInfo(info);
    sd.NeedPrint_Enc_1 = true;
    if (final)
    {
      sd.Enc_BenchRes.Update_With_Res2(br);
      sd.NeedPrint_Enc = true;
      needPost = true;
    }
  }

  if (needPost)
    BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished);

  return S_OK;
}


HRESULT CBenchCallback::SetDecodeResult(const CBenchInfo &info, bool final)
{
  NSynchronization::CCriticalSectionLock lock(Sync->CS);
  if (Sync->Exit)
    return E_ABORT;
  CSyncData &sd = Sync->sd;
  // sd.NumDecProgress++;
  CTotalBenchRes2 &br = sd.Dec_BenchRes_1;
  br.Rating = info.GetRating_LzmaDec();
  br.SetFrom_BenchInfo(info);
  sd.NeedPrint_Dec_1 = true;
  if (final)
    sd.Dec_BenchRes.Update_With_Res2(br);
  return S_OK;
}


struct CBenchCallback2 Z7_final: public IBenchPrintCallback
{
  CBenchProgressSync *Sync;
  bool TotalMode;

  void Print(const char *s) Z7_override;
  void NewLine() Z7_override;
  HRESULT CheckBreak() Z7_override;
};

void CBenchCallback2::Print(const char *s)
{
  if (TotalMode)
  {
    NSynchronization::CCriticalSectionLock lock(Sync->CS);
    Sync->Text += s;
    Sync->TextWasChanged = true;
  }
}

void CBenchCallback2::NewLine()
{
  Print("\xD\n");
}

HRESULT CBenchCallback2::CheckBreak()
{
  if (Sync->Exit)
    return E_ABORT;
  return S_OK;
}



struct CFreqCallback Z7_final: public IBenchFreqCallback
{
  CBenchmarkDialog *BenchmarkDialog;

  virtual HRESULT AddCpuFreq(unsigned numThreads, UInt64 freq, UInt64 usage) Z7_override;
  virtual HRESULT FreqsFinished(unsigned numThreads) Z7_override;
};

HRESULT CFreqCallback::AddCpuFreq(unsigned numThreads, UInt64 freq, UInt64 usage)
{
  HRESULT res;
  {
    CBenchProgressSync &sync = BenchmarkDialog->Sync;
    NSynchronization::CCriticalSectionLock lock(sync.CS);
    UString &s = sync.FreqString_Sync;
    if (sync.NumFreqThreadsPrev != numThreads)
    {
      sync.NumFreqThreadsPrev = numThreads;
      if (!s.IsEmpty())
        s.Add_LF();
      s.Add_UInt32(numThreads);
      s += "T Frequency (MHz):";
      s.Add_LF();
    }
    s.Add_Space();
    if (numThreads != 1)
    {
      s.Add_UInt64(GetUsagePercents(usage));
      s.Add_Char('%');
      s.Add_Space();
    }
    s.Add_UInt64(GetMips(freq));
    // BenchmarkDialog->Sync.sd.NeedPrint_Freq = true;
    res = sync.Exit ? E_ABORT : S_OK;
  }
  // BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished);
  return res;
}

HRESULT CFreqCallback::FreqsFinished(unsigned /* numThreads */)
{
  HRESULT res;
  {
    CBenchProgressSync &sync = BenchmarkDialog->Sync;
    NSynchronization::CCriticalSectionLock lock(sync.CS);
    sync.sd.NeedPrint_Freq = true;
    BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished);
    res = sync.Exit ? E_ABORT : S_OK;
  }
  BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished);
  return res;
}



// define USE_DUMMY only for debug
// #define USE_DUMMY
#ifdef USE_DUMMY
static unsigned dummy = 1;
static unsigned Dummy(unsigned limit)
{
  unsigned sum = 0;
  for (unsigned k = 0; k < limit; k++)
  {
    sum += dummy;
    if (sum == 0)
      break;
  }
  return sum;
}
#endif


HRESULT CThreadBenchmark::Process()
{
  /* the first benchmark pass can be slow,
     if we run benchmark while the window is being created,
     and (no freq detecion loop) && (dictionary is small) (-mtic is small) */
    
  // Sleep(300); // for debug
  #ifdef USE_DUMMY
  Dummy(1000 * 1000 * 1000); // for debug
  #endif

  CBenchProgressSync &sync = BenchmarkDialog->Sync;
  HRESULT finishHRESULT = S_OK;
  
  try
  {
    for (UInt32 passIndex = 0;; passIndex++)
    {
      // throw 1; // to debug
      // throw CSystemException(E_INVALIDARG); // to debug

      UInt64 dictionarySize;
      UInt32 numThreads;
      {
        NSynchronization::CCriticalSectionLock lock(sync.CS);
        if (sync.Exit)
          break;
        dictionarySize = sync.DictSize;
        numThreads = sync.NumThreads;
      }

      #ifdef PRINT_ITER_TIME
      const DWORD startTick = GetTickCount();
      #endif
      
      CBenchCallback callback;
      
      callback.dictionarySize = dictionarySize;
      callback.Sync = &sync;
      callback.BenchmarkDialog = BenchmarkDialog;
      
      CBenchCallback2 callback2;
      callback2.TotalMode = BenchmarkDialog->TotalMode;
      callback2.Sync = &sync;
      
      CFreqCallback freqCallback;
      freqCallback.BenchmarkDialog = BenchmarkDialog;

      HRESULT result;
     
      try
      {
        CObjectVector<CProperty> props;

        props = BenchmarkDialog->Props;

        if (BenchmarkDialog->TotalMode)
        {
          props = BenchmarkDialog->Props;
        }
        else
        {
          {
            CProperty prop;
            prop.Name = "mt";
            prop.Value.Add_UInt32(numThreads);
            props.Add(prop);
          }
          {
            CProperty prop;
            prop.Name = 'd';
            prop.Name.Add_UInt32((UInt32)(dictionarySize >> 10));
            prop.Name.Add_Char('k');
            props.Add(prop);
          }
        }
        
        result = Bench(EXTERNAL_CODECS_LOC_VARS
            BenchmarkDialog->TotalMode ? &callback2 : NULL,
            BenchmarkDialog->TotalMode ? NULL : &callback,
            props, 1, false,
            (!BenchmarkDialog->TotalMode) && passIndex == 0 ? &freqCallback: NULL);
        
        // result = S_FALSE; // for debug;
        // throw 1;
      }
      catch(...)
      {
        result = E_FAIL;
      }

      #ifdef PRINT_ITER_TIME
      const DWORD numTicks = GetTickCount() - startTick;
      #endif

      bool finished = true;

      NSynchronization::CCriticalSectionLock lock(sync.CS);

      if (result != S_OK)
      {
        sync.BenchFinish_Task_HRESULT = result;
        break;
      }

      {
        CSyncData &sd = sync.sd;

        sd.NumPasses_Finished++;
        #ifdef PRINT_ITER_TIME
        sd.TotalTicks += numTicks;
        #endif

        if (BenchmarkDialog->TotalMode)
          break;

        {
          CTotalBenchRes tot_BenchRes = sd.Enc_BenchRes_1;
          tot_BenchRes.Update_With_Res(sd.Dec_BenchRes_1);

          sd.NeedPrint_RatingVector = true;
          {
            CBenchPassResult pair;
            // pair.EncInfo = sd.EncInfo; // for debug
            pair.Enc = sd.Enc_BenchRes_1;
            pair.Dec = sd.Dec_BenchRes_1;
            #ifdef PRINT_ITER_TIME
            pair.Ticks = numTicks;
            #endif
            sync.RatingVector.Add(pair);
            // pair.Dec_Defined = true;
          }
        }
          
        sd.NeedPrint_Dec = true;
        sd.NeedPrint_Tot = true;

        if (sync.RatingVector.Size() > kRatingVector_NumBundlesMax)
        {
          // sd.RatingVector_NumDeleted++;
          sd.RatingVector_DeletedIndex = (int)(kRatingVector_NumBundlesMax / 4);
          sync.RatingVector.Delete((unsigned)(sd.RatingVector_DeletedIndex));
        }

        if (sync.sd.NumPasses_Finished < sync.NumPasses_Limit)
          finished = false;
        else
        {
          sync.sd.BenchWasFinished = true;
          // BenchmarkDialog->_finishTime = GetTickCount();
          // return 0;
        }
      }

      if (BenchmarkDialog->TotalMode)
        break;

      /*
      if (newTick - prevTick < 1000)
        numSameTick++;
      if (numSameTick > 5 || finished)
      {
        prevTick = newTick;
        numSameTick = 0;
      */
      // for (unsigned i = 0; i < 1; i++)
      {
        // we suppose that PostMsg messages will be processed in order.
        if (!BenchmarkDialog->PostMsg_Finish(k_Msg_WPARM_Iter_Finished))
        {
          finished = true;
          finishHRESULT = E_FAIL;
          // throw 1234567;
        }
      }
      if (finished)
        break;
    }
    // return S_OK;
  }
  catch(CSystemException &e)
  {
    finishHRESULT = e.ErrorCode;
    // BenchmarkDialog->MessageBoxError(HResultToMessage(e.ErrorCode));
    // return E_FAIL;
  }
  catch(...)
  {
    finishHRESULT = E_FAIL;
    // BenchmarkDialog->MessageBoxError(HResultToMessage(E_FAIL));
    // return E_FAIL;
  }

  if (finishHRESULT != S_OK)
  {
    NSynchronization::CCriticalSectionLock lock(sync.CS);
    sync.BenchFinish_Thread_HRESULT = finishHRESULT;
  }
  if (!BenchmarkDialog->PostMsg_Finish(k_Msg_WPARM_Thread_Finished))
  {
    // sync.BenchFinish_Thread_HRESULT = E_FAIL;
  }
  return 0;
}



static void ParseNumberString(const UString &s, NCOM::CPropVariant &prop)
{
  const wchar_t *end;
  UInt64 result = ConvertStringToUInt64(s, &end);
  if (*end != 0 || s.IsEmpty())
    prop = s;
  else if (result <= (UInt32)0xFFFFFFFF)
    prop = (UInt32)result;
  else
    prop = result;
}


HRESULT Benchmark(
    DECL_EXTERNAL_CODECS_LOC_VARS
    const CObjectVector<CProperty> &props, UInt32 numIterations, HWND hwndParent)
{
  CBenchmarkDialog bd;

  bd.TotalMode = false;
  bd.Props = props;
  if (numIterations == 0)
    numIterations = 1;
  bd.Sync.NumPasses_Limit = numIterations;
  bd.Sync.DictSize = (UInt64)(Int64)-1;
  bd.Sync.NumThreads = (UInt32)(Int32)-1;
  bd.Sync.Level = -1;

  COneMethodInfo method;

  UInt32 numCPUs = 1;
  #ifndef Z7_ST
  numCPUs = NSystem::GetNumberOfProcessors();
  #endif
  UInt32 numThreads = numCPUs;

  FOR_VECTOR (i, props)
  {
    const CProperty &prop = props[i];
    UString name = prop.Name;
    name.MakeLower_Ascii();
    if (name.IsEqualTo_Ascii_NoCase("m") && prop.Value == L"*")
    {
      bd.TotalMode = true;
      continue;
    }

    NCOM::CPropVariant propVariant;
    if (!prop.Value.IsEmpty())
      ParseNumberString(prop.Value, propVariant);
    if (name.IsPrefixedBy(L"mt"))
    {
      #ifndef Z7_ST
      RINOK(ParseMtProp(name.Ptr(2), propVariant, numCPUs, numThreads))
      if (numThreads != numCPUs)
        bd.Sync.NumThreads = numThreads;
      #endif
      continue;
    }
    /*
    if (name.IsEqualTo("time"))
    {
      // UInt32 testTime = 4;
      // RINOK(ParsePropToUInt32(L"", propVariant, testTime));
      continue;
    }
    RINOK(method.ParseMethodFromPROPVARIANT(name, propVariant));
    */
    // here we need to parse DictSize property, and ignore unknown properties
    method.ParseMethodFromPROPVARIANT(name, propVariant);
  }

  if (bd.TotalMode)
  {
    // bd.Bench2Text.Empty();
    bd.Bench2Text = "7-Zip " MY_VERSION_CPU;
    // bd.Bench2Text.Add_Char((char)0xD);
    bd.Bench2Text.Add_LF();
  }

  {
    UInt64 dict;
    if (method.Get_DicSize(dict))
      bd.Sync.DictSize = dict;
  }
  bd.Sync.Level = (int)method.GetLevel();

  // Dummy(1000 * 1000 * 1);

  {
    CThreadBenchmark &benchmarker = bd._threadBenchmark;
    #ifdef Z7_EXTERNAL_CODECS
    benchmarker._externalCodecs = _externalCodecs;
    #endif
    benchmarker.BenchmarkDialog = &bd;
  }

  bd.Create(hwndParent);

  return S_OK;
}


CBenchmarkDialog::~CBenchmarkDialog()
{
  if (_thread.IsCreated())
  {
    /* the following code will be not executed in normal code flow.
       it can be called, if there is some internal failure in dialog code. */
    Attach(NULL);
    MessageBoxError(L"The flaw in benchmark thread code");
    Sync.SendExit();
    _thread.Wait_Close();
  }
}
