////////////////////////////////////////////////////////////////////////////////
// TestTimer.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// By performing a stress test, TestTimer ensures that the implementation
// of the Timer and the TimerRoot class is solid.


#include <EDK.h>
#include "resource.h"

const int giResolution = 1;
const flag gfAllowZero = false;

int giChanges;
int giFired;

class StressTimer : public Object {
public:

  Timer X;
  int iTime;
  flag fActive;
  flag fMustFire;
  flag fHasFired;

  void SetRandom() {
    int iClocks = (rand() & 255) * giResolution;
    if (iClocks == 0 && !gfAllowZero) {
      iClocks = 1;
    }
    if (iClocks != 0) {
      X.SetTimer(iClocks);
      iTime = gTimerRoot.GetTime() + iClocks;
      fActive = true;
    } else {
      flag fError = false;
      try {
        X.SetTimer(0);
      } catch (char*) {
        fError = true;
      }
      if (!fError) {
        error("SetTimer(0) did not cause an error");
      }
    }
  }

  void OnTimer() {
    assert(iTime == gTimerRoot.GetTime());
    assert(fHasFired == false);
    fHasFired = true;
    assert(fActive == true);
    fActive = false;
    if (rand() & 1) {
      SetRandom();
      giChanges++;
    }
    giFired++;
  }

  StressTimer() {
    fActive = false;
    fMustFire = false;
    fHasFired = false;
  }

};



// usage:
//
// {
//   StatusDlg st(MAKEINTRESSOURCE(IDD_MyStatus), hwndParent);
//   for (int i = 0; i < 10000; i++) {
//     st.cprintf(IDC_Pass, "Pass number %d", i);
//     if (st.IsAbort()) {
//       break;
//     }
//   }
// }


class StatusDlg {

  HWND hwnd;
  flag fAbort;

public:

  // set 
  BOOL friend CALLBACK StatusDlgProc(HWND hwnd, UINT uMsg, WPARAM /*wParam*/, LPARAM lParam) {
    switch (uMsg) {
    case WM_INITDIALOG:
      {
        assert(lParam != NULL);
        verify(SetWindowLong(hwnd, GWL_USERDATA, lParam) == 0);
        CenterWindow(hwnd);
        return TRUE;
      }
    case WM_COMMAND:
      {
        StatusDlg* p = (StatusDlg*)GetWindowLong(hwnd, GWL_USERDATA);
        assert(p != NULL);
        p->fAbort = true;
        return TRUE;
      }
    }
    return FALSE;
  }

  // constructor
  StatusDlg(int iResource, HINSTANCE hinst /*= ghinst*/, HWND hwndParent /*= ghwnd*/) {
    hwnd = NULL;
    fAbort = false;
    win(hwnd = CreateDialogParam(hinst, MAKEINTRESOURCE(iResource), hwndParent, StatusDlgProc, (LPARAM)this));
    verify(ShowWindow(hwnd, SW_SHOW) == 0);
  }

  // destructor
  ~StatusDlg() {
    if (hwnd != NULL) {
      DestroyWindow(hwnd);
      hwnd = NULL;
    }
  }

  // set text in a control
  void __cdecl cprintf(int iControl, char* pcFormat, ...) {
    char ac[1024];
    wvsprintf(ac, pcFormat, (va_list)(&pcFormat + 1));
    SetDlgItemText(hwnd, iControl, ac);
  }

  // switch tasks and check for user abort
  flag YieldAndIsAbort() {
    MSG msg;
    while (PeekMessage(&msg, NULL, 0 ,0, PM_REMOVE)) {
      if (!IsDialogMessage(hwnd, &msg)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    return fAbort;
  }

};




void DoTheTest(HINSTANCE hinst) {

  try {

    gfTraceError = false;
    StatusDlg st(IDD_Status, hinst, NULL);

    // test with empty list
    int iTime = 0;
    for (int i = 0; i < 100; i++) {
      iTime += -gTimerRoot.iTime;
      gTimerRoot.iTime = 0;
      assert(gTimerRoot.GetTime() == iTime);
      gTimerRoot.Execute();
      assert(gTimerRoot.GetTime() == iTime);
      assert((int)gTimerRoot.GetTimeTotal() == iTime);
    }

    // stress test
    StressTimer a[256];

    for (i = 0; i < 256; i++) {
      char ac[10];
      wsprintf(ac, "[%d]", i);
      a[i].X.Init(ac, &a[i], (pfn)StressTimer::OnTimer);
    }

    int iStart = iTime;
    while (!st.YieldAndIsAbort()) {

      if ((iTime / giResolution & 255) == 0) {
        st.cprintf(IDC_Text, "%d clocks, %d changes, %d fired\r", iTime - iStart, giChanges, giFired);
      }

      // verify
      assert(gTimerRoot.GetTime() == iTime);
      for (int i = 0; i < 256; i++) {
        a[i].X.AssertValid();
        assert(a[i].X.IsActive() == a[i].fActive);
        assert(a[i].fMustFire == false);
        assert(a[i].fHasFired == false);
      }

      // execute
      iTime += giResolution;
      if ((gTimerRoot.iTime += giResolution) >= 0) {
        iTime -= gTimerRoot.iTime;
        gTimerRoot.iTime = 0;
        for (int i = 0; i < 256; i++) {
          assert(a[i].fMustFire == false);
          if (a[i].iTime == iTime && a[i].fActive) {
            a[i].fMustFire = true;
          }
        }
        gTimerRoot.Execute();
        for (i = 0; i < 256; i++) {
          if (a[i].fMustFire) {
            assert(a[i].fHasFired == true);
            a[i].fHasFired = false;
            a[i].fMustFire = false;
          }
        }
      }

      if ((rand() & 15) == 0) {
        int i1 = rand() & 255;
        if ((rand() & 1) == 0) {

          // set timer
          a[i1].SetRandom();

        } else {

          // kill timer
          if (a[i1].fActive) {
            a[i1].X.KillTimer();
            a[i1].fActive = false;
          } else {
            flag fError = false;
            try {
              a[i1].X.KillTimer();
            } catch (char*) {
              fError = true;
            }
            if (!fError) {
              error("KillTimer() of inactive timer did not cause an error");
            }
          }
        }
        giChanges++;
      }

    }

  } catch (...) {
    report();
  }

}


// DLL entry point
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID) {
  if (dwReason == DLL_PROCESS_ATTACH) {
    DoTheTest(hinst);
  }
  return TRUE;
}
