////////////////////////////////////////////////////////////////////////////////
// TestLine.cpp -- this file is part of the Emulator Developers Kit(EDK)
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// By performing a stress test, TestLine verifies that the implementation
// of the Line class is solid.

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


class LineTest : public Object {
  
  Line A;
  Line B;
  Line C;

  flag fA;
  flag fB;
  flag fC;

public:

  void OnAHigh() {
    fA = true;
  }
  
  void OnALow() {
    fA = false;
  }
  
  void OnBHigh() {
    fB = true;
  }
  
  void OnCLow() {
    fC = false;
  }
  
  LineTest() {
    fA = true;
    fB = true;
    fC = true;
    A.Init("A", this, (pfn)OnAHigh, (pfn)OnALow);
    B.Init("B", this, (pfn)OnBHigh, NULL);
    C.Init("C", this, NULL, (pfn)OnCLow);
  }

  void Test() {

    // A   B   C
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsConnectedTo(A));
    assert(B.IsConnectedTo(B));
    assert(C.IsConnectedTo(C));
    assert(!A.IsConnected());
    assert(!B.IsConnected());
    assert(!C.IsConnected());
    assert(fA == true);
    assert(fB == true);
    assert(fC == true);
    assert(A.IsOutputHigh());
    assert(B.IsOutputHigh());
    assert(C.IsOutputHigh());
    assert(A.IsHigh());
    assert(B.IsHigh());
    assert(C.IsHigh());

    // a   B   C
    A.SetOutputLow();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputLow());
    assert(A.IsLow());
    assert(fA == false);

    // A   B   C
    A.SetOutputHigh();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputHigh());
    assert(A.IsHigh());
    assert(fA == true);

    // A - B   C
    A.ConnectTo(B);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsConnected());
    assert(B.IsConnected());
    assert(!C.IsConnected());
    assert(A.IsConnectedTo(B));
    assert(!A.IsConnectedTo(C));
    assert(B.IsConnectedTo(A));
    assert(!B.IsConnectedTo(C));
    assert(!C.IsConnectedTo(A));
    assert(!C.IsConnectedTo(B));

    // a - B   C
    A.SetOutputLow();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputLow());
    assert(B.IsOutputHigh());
    assert(A.IsLow());
    assert(B.IsLow());
    assert(fA == false);
    assert(fB == true); // no OnBLow
    fB = false;

    // a - b   C
    B.SetOutputLow();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputLow());
    assert(B.IsOutputLow());
    assert(A.IsLow());
    assert(B.IsLow());
    assert(fA == false);
    assert(fB == false);

    // A - b   C
    A.SetOutputHigh();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputHigh());
    assert(B.IsOutputLow());
    assert(A.IsLow());
    assert(B.IsLow());
    assert(fA == false);
    assert(fB == false);

    // A - B   C
    B.SetOutputHigh();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputHigh());
    assert(B.IsOutputHigh());
    assert(A.IsHigh());
    assert(B.IsHigh());
    assert(fA == true);
    assert(fB == true);

    // a - B   C
    A.SetOutputLow();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsLow());
    assert(B.IsLow());
    assert(C.IsHigh());
    assert(fA == false);
    assert(fB == true); // no OnBLow
    fB = false;
    assert(fC == true);

    // a - B - C
    C.ConnectTo(B);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsConnected());
    assert(B.IsConnected());
    assert(C.IsConnected());
    assert(A.IsConnectedTo(B));
    assert(A.IsConnectedTo(C));
    assert(B.IsConnectedTo(A));
    assert(B.IsConnectedTo(C));
    assert(C.IsConnectedTo(A));
    assert(C.IsConnectedTo(B));
    assert(A.IsLow());
    assert(B.IsLow());
    assert(C.IsLow());
    assert(fA == false);
    assert(fB == false);
    assert(fC == false);

    // a - B - c
    C.SetOutputLow();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputLow());
    assert(B.IsOutputHigh());
    assert(C.IsOutputLow());
    assert(A.IsLow());
    assert(B.IsLow());
    assert(C.IsLow());
    assert(fA == false);
    assert(fB == false);
    assert(fC == false);

    // A - B - c
    A.SetOutputHigh();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputHigh());
    assert(B.IsOutputHigh());
    assert(C.IsOutputLow());
    assert(A.IsLow());
    assert(B.IsLow());
    assert(C.IsLow());
    assert(fA == false);
    assert(fB == false);
    assert(fC == false);

    // A - B   c
    C.Disconnect();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputHigh());
    assert(B.IsOutputHigh());
    assert(C.IsOutputLow());
    assert(A.IsHigh());
    assert(B.IsHigh());
    assert(C.IsLow());
    assert(fA == true);
    assert(fB == true);
    assert(fC == false);

    // A - B - c
    A.ConnectTo(C);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputHigh());
    assert(B.IsOutputHigh());
    assert(C.IsOutputLow());
    assert(A.IsLow());
    assert(B.IsLow());
    assert(C.IsLow());
    assert(fA == false);
    assert(fB == true); // no OnBLow
    fB = false;
    assert(fC == false);

    // A - B   c
    C.Disconnect();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsOutputHigh());
    assert(B.IsOutputHigh());
    assert(C.IsOutputLow());
    assert(A.IsHigh());
    assert(B.IsHigh());
    assert(C.IsLow());
    assert(fA == true);
    assert(fB == true);
    assert(fC == false);

    // A - B   C
    C.SetOutputHigh();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(C.IsOutputHigh());
    assert(fC == false); // no OnCHigh
    fC = true;

  }

};

class LineStress : public Object {
public:
  
  Line X;
  flag fHigh;
  
  void OnXHigh() {
    fHigh = true;
  }
  
  void OnXLow() {
    fHigh = false;
  }

  LineStress() {
    fHigh = true;
  }

};

LineStress a[16];
flag afHigh[16];
int aiConnect[16];
const int aiMask[16] = {
  1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
};

flag IsConnected(int i1) {
  return (aiConnect[i1] & ~aiMask[i1]) != 0;
}

flag AreConnected(int i1, int i2) {
  return (aiConnect[i1] & aiMask[i2]) != 0;
}

void Connect(int i1, int i2) {
  int iMask = 0;
  for (int i = 0; i < 16; i++) {
    if (AreConnected(i, i1) || AreConnected(i, i2)) {
      iMask |= aiMask[i];
    }
  }
  for (i = 0; i < 16; i++) {
    if (iMask & aiMask[i]) {
      aiConnect[i] = iMask;
    }
  }
}

void Disconnect(int i1) {
  for (int i = 0; i < 16; i++) {
    aiConnect[i] &= ~aiMask[i1];
  }
  aiConnect[i1] = aiMask[i1];
}

flag IsOutputHigh(int i1) {
  return afHigh[i1];
}

flag IsHigh(int i1) {
  for (int i = 0; i < 16; i++) {
    if (AreConnected(i, i1)) {
      if (!IsOutputHigh(i)) {
        return false;
      }
    }
  }
  return true;
}


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


class StatusDlg {

  HWND hwnd;
  flag fAbort;

public:

  // window function
  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;
  }

};


int DoTheTest(HINSTANCE hinst) {

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

    // basic functionality
    LineTest X;
    X.Test();

    // stress test
    for (int i = 0; i < 16; i++) {
      char ac[10];
      wsprintf(ac, "[%d]", i);
      a[i].X.Init(ac, &a[i], (i & 1) ? (pfn)LineStress::OnXHigh : NULL, (i & 2) ? (pfn)LineStress::OnXLow : NULL);
    }

    for (i = 0; i < 16; i++) {
      afHigh[i] = true;
      aiConnect[i] = aiMask[i];
    }

    byte bCount = 0;
    int iConnections = 0;
    int iChanges = 0;
    while (!st.YieldAndIsAbort()) {

      if (bCount++ == 0) {
        st.cprintf(IDC_Text, "%d connections, %d changes\r", iConnections, iChanges);
      }

      for (i = 0; i < 16; i++) {
        a[i].X.AssertValid();
        assert(a[i].X.IsConnected() == IsConnected(i));
        for (int i1 = 0; i1 < 16; i1++) {
          assert(a[i].X.IsConnectedTo(a[i1].X) == AreConnected(i, i1));
        }
        assert(a[i].X.IsOutputHigh() == IsOutputHigh(i));
        assert(a[i].X.IsOutputLow() != IsOutputHigh(i));
        flag fHigh = IsHigh(i);
        assert(a[i].X.IsHigh() == fHigh);
        assert(a[i].X.IsLow() != fHigh);
        if (fHigh) {
          if ((i & 1) == 0) {
            a[i].fHigh = true;
          }
        } else {
          if ((i & 2) == 0) {
            a[i].fHigh = false;
          }
        }
        assert(a[i].fHigh == fHigh);
      }

      int i1 = rand() & 15;
      if ((rand() & 3) == 0) {
        if (rand() & 1) {
      
          // connect
          int i2 = rand() & 15;
          if (AreConnected(i1, i2)) {
            flag fError = false;
            try {
              a[i1].X.ConnectTo(a[i2].X);
            } catch (char*) {
              fError = true;
            }
            if (!fError) {
              error("Connecting already connected lines caused no exception");
            }
          } else {
            a[i1].X.ConnectTo(a[i2].X);
            Connect(i1, i2);
          }

        } else {

          // Disconnect
          if (IsConnected(i1)) {
            a[i1].X.Disconnect();
            Disconnect(i1);
          } else {
            flag fError = false;
            try {
              a[i1].X.Disconnect();
            } catch (char*) {
              fError = true;
            }
            if (!fError) {
              error("Disconnecting unconnected line caused no exception");
            }
          }

        }

        iConnections++;
      } else {
        if (rand() & 1) {
      
          // set high
          if (a[i1].X.IsOutputHigh()) {
            flag fError = false;
            try {
              a[i1].X.SetOutputHigh();
            } catch (char*) {
              fError = true;
            }
            if (!fError) {
              error("Setting high line high caused no exception");
            }
          } else {
            a[i1].X.SetOutputHigh();
            afHigh[i1] = true;
          }

        } else {

          // set low
          if (a[i1].X.IsOutputLow()) {
            flag fError = false;
            try {
              a[i1].X.SetOutputLow();
            } catch (char*) {
              fError = true;
            }
            if (!fError) {
              error("Setting low line low caused no exception");
            }
          } else {
            a[i1].X.SetOutputLow();
            afHigh[i1] = false;
          }

        }

        iChanges++;
      }

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

  return 0;
}


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