////////////////////////////////////////////////////////////////////////////////
// TestPort.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, TestPort ensures that the implementation
// of the Port class is solid.


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


class PortTest : public Object {
  
  Port A;
  Port B;
  Port C;

  byte bAState;
  byte bAChanges;
  byte bBState;
  byte bBChanges;

public:
  
  void OnAChange(byte bNewState, byte bChanges) {
    bAState = bNewState;
    bAChanges = bChanges;
  }
  
  void OnBChange(byte bNewState, byte bChanges) {
    bBState = bNewState;
    bBChanges = bChanges;
  }
  
  PortTest() {
    bAState = 0xFF;
    bAChanges = 0;
    bBState = 0xFF;
    bBChanges = 0;
    A.Init("A", this, (pfnbb)OnAChange);
    B.Init("B", this, (pfnbb)OnBChange);
    C.Init("C", this, NULL);
  }

  void Test() {

    // A   B   C
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(!A.IsConnected());
    assert(!B.IsConnected());
    assert(!C.IsConnected());
    assert(A.IsConnectedTo(A));
    assert(!A.IsConnectedTo(B));
    assert(!A.IsConnectedTo(C));
    assert(!B.IsConnectedTo(A));
    assert(B.IsConnectedTo(B));
    assert(!B.IsConnectedTo(C));
    assert(!C.IsConnectedTo(A));
    assert(!C.IsConnectedTo(B));
    assert(C.IsConnectedTo(C));
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0xFF);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0xFF);
    assert(B.GetPort() == 0xFF);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0xFF);
    assert(bBState == 0xFF);
    assert(bAChanges == 0);
    assert(bBChanges == 0);

    // a   B   C
    A.SetPort(0x55);
    A.AssertValid();
    assert(A.GetOutput() == 0x55);
    assert(A.GetPort() == 0x55);
    assert(bAState == 0x55);
    assert(bAChanges == 0xAA);

    // A   B   C
    A.SetPort(0xFF);
    A.AssertValid();
    assert(A.GetOutput() == 0xFF);
    assert(A.GetPort() == 0xFF);
    assert(bAState == 0xFF);
    assert(bAChanges == 0xAA);

    // A - B   C
    A.ConnectTo(B);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsConnected());
    assert(B.IsConnected());
    assert(!C.IsConnected());
    assert(A.IsConnectedTo(A));
    assert(A.IsConnectedTo(B));
    assert(!A.IsConnectedTo(C));
    assert(B.IsConnectedTo(A));
    assert(B.IsConnectedTo(B));
    assert(!B.IsConnectedTo(C));
    assert(!C.IsConnectedTo(A));
    assert(!C.IsConnectedTo(B));
    assert(C.IsConnectedTo(C));
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0xFF);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0xFF);
    assert(B.GetPort() == 0xFF);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0xFF);
    assert(bBState == 0xFF);
    assert(bAChanges == 0xAA);
    assert(bBChanges == 0);

    // a - B   C
    A.SetPort(0xF0);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.GetOutput() == 0xF0);
    assert(B.GetOutput() == 0xFF);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0xF0);
    assert(B.GetPort() == 0xF0);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0xF0);
    assert(bBState == 0xF0);
    assert(bAChanges == 0x0F);
    assert(bBChanges == 0x0F);

    // a - b   C
    B.SetPort(0x55);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.GetOutput() == 0xF0);
    assert(B.GetOutput() == 0x55);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0x50);
    assert(B.GetPort() == 0x50);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0x50);
    assert(bBState == 0x50);
    assert(bAChanges == 0xA0);
    assert(bBChanges == 0xA0);

    // A - b   C
    A.SetPort(0xFF);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0x55);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0x55);
    assert(B.GetPort() == 0x55);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0x55);
    assert(bBState == 0x55);
    assert(bAChanges == 0x05);
    assert(bBChanges == 0x05);

    // A - b   c
    C.SetPort(0x0F);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0x55);
    assert(C.GetOutput() == 0x0F);
    assert(A.GetPort() == 0x55);
    assert(B.GetPort() == 0x55);
    assert(C.GetPort() == 0x0F);
    assert(bAState == 0x55);
    assert(bBState == 0x55);
    assert(bAChanges == 0x05);
    assert(bBChanges == 0x05);

    // A - b - c
    C.ConnectTo(B);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsConnected());
    assert(B.IsConnected());
    assert(C.IsConnected());
    assert(A.IsConnectedTo(A));
    assert(A.IsConnectedTo(B));
    assert(A.IsConnectedTo(C));
    assert(B.IsConnectedTo(A));
    assert(B.IsConnectedTo(B));
    assert(B.IsConnectedTo(C));
    assert(C.IsConnectedTo(A));
    assert(C.IsConnectedTo(B));
    assert(C.IsConnectedTo(C));
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0x55);
    assert(C.GetOutput() == 0x0F);
    assert(A.GetPort() == 0x05);
    assert(B.GetPort() == 0x05);
    assert(C.GetPort() == 0x05);
    assert(bAState == 0x05);
    assert(bBState == 0x05);
    assert(bAChanges == 0x50);
    assert(bBChanges == 0x50);

    // A - b   c
    C.Disconnect();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.IsConnected());
    assert(B.IsConnected());
    assert(!C.IsConnected());
    assert(A.IsConnectedTo(A));
    assert(A.IsConnectedTo(B));
    assert(!A.IsConnectedTo(C));
    assert(B.IsConnectedTo(A));
    assert(B.IsConnectedTo(B));
    assert(!B.IsConnectedTo(C));
    assert(!C.IsConnectedTo(A));
    assert(!C.IsConnectedTo(B));
    assert(C.IsConnectedTo(C));
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0x55);
    assert(C.GetOutput() == 0x0F);
    assert(A.GetPort() == 0x55);
    assert(B.GetPort() == 0x55);
    assert(C.GetPort() == 0x0F);
    assert(bAState == 0x55);
    assert(bBState == 0x55);
    assert(bAChanges == 0x50);
    assert(bBChanges == 0x50);

    // A - b   C
    C.SetPort(0xFF);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0x55);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0x55);
    assert(B.GetPort() == 0x55);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0x55);
    assert(bBState == 0x55);
    assert(bAChanges == 0x50);
    assert(bBChanges == 0x50);

    // A   b   C
    A.Disconnect();
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(!A.IsConnected());
    assert(!B.IsConnected());
    assert(!C.IsConnected());
    assert(A.IsConnectedTo(A));
    assert(!A.IsConnectedTo(B));
    assert(!A.IsConnectedTo(C));
    assert(!B.IsConnectedTo(A));
    assert(B.IsConnectedTo(B));
    assert(!B.IsConnectedTo(C));
    assert(!C.IsConnectedTo(A));
    assert(!C.IsConnectedTo(B));
    assert(C.IsConnectedTo(C));
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0x55);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0xFF);
    assert(B.GetPort() == 0x55);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0xFF);
    assert(bBState == 0x55);
    assert(bAChanges == 0xAA);
    assert(bBChanges == 0x50);

    // A   B   C
    B.SetPort(0xFF);
    A.AssertValid();
    B.AssertValid();
    C.AssertValid();
    assert(A.GetOutput() == 0xFF);
    assert(B.GetOutput() == 0xFF);
    assert(C.GetOutput() == 0xFF);
    assert(A.GetPort() == 0xFF);
    assert(B.GetPort() == 0xFF);
    assert(C.GetPort() == 0xFF);
    assert(bAState == 0xFF);
    assert(bBState == 0xFF);
    assert(bAChanges == 0xAA);
    assert(bBChanges == 0xAA);

  }

};

class PortStress : public Object {
public:
  
  Port X;
  byte bState;
  byte bLastChanges;
  flag fChanges;
  
  void OnXChange(byte bNewState, byte bChanges) {
    bState = bNewState;
    bLastChanges = bChanges;
    fChanges = true;
  }

  PortStress() {
    bState = 0xFF;
    bLastChanges = 0;
    fChanges = false;
  }

};

PortStress a[16];
byte abOutput[16];
byte abOldState[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];
}

byte GetOutput(int i1) {
  return abOutput[i1];
}

byte GetPort(int i1) {
  byte bState = 0xFF;
  for (int i = 0; i < 16; i++) {
    if (AreConnected(i, i1)) {
      bState &= abOutput[i];
    }
  }
  return bState;
}



// 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:

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

};


int DoTheTest(HINSTANCE hinst) {

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

    // basic functionality
    PortTest 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) ? (pfnbb)PortStress::OnXChange : NULL);
    }

    for (i = 0; i < 16; i++) {
      abOutput[i] = 0xFF;
      abOldState[i] = 0xFF;
      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);
      }

      // check all ports
      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.GetOutput() == GetOutput(i));
        byte bState = GetPort(i);
        assert(a[i].X.GetPort() == bState);
        if ((i & 1) != 0) {
          assert(a[i].bState == bState);
          if (abOldState[i] != bState) {
            assert(a[i].fChanges == true);
            assert(a[i].bLastChanges == abOldState[i] ^ bState);
            abOldState[i] = bState;
          }
          a[i].fChanges = false;
        } else {
          assert(a[i].fChanges == false);
        }
      }

      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 (...) {
              fError = true;
            }
            if (!fError) {
              error("Connecting already connected ports 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 (...) {
              fError = true;
            }
            if (!fError) {
              error("Disconnecting unconnected port caused no exception");
            }
          }

        }

        iConnections++;
      } else {

        // set output
        byte bOldState = GetPort(i1);
        byte bOutput = (byte)rand();
        a[i1].X.SetPort(bOutput);
        abOutput[i1] = bOutput;

        // verify that OnXChange() functions have been called correctly
        byte bNewState = GetPort(i1);
        byte bChanges = (byte)(bNewState ^ bOldState);
        if (bChanges != 0) {
          for (int i = 0; i < 16; i++) {
            if (AreConnected(i, i1)) {
              if ((i & 1) != 0) {
                assert(a[i].fChanges == true);
                assert(a[i].bLastChanges == bChanges);
              }
            }
          }
        }

        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;
}
