////////////////////////////////////////////////////////////////////////////////
// Port.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm

RegisterPersistentClass(Port);


////////////////////////////////////////////////////////////////////////////////
// set the callback function when the state of the group changes

global void Port::SetOnChange(pfnbb pfnbbNewOnChange) {

  // check parameters and conditions
  assert(!IsConnected());

  // set the OnChange function
  pfnbbOnChange = pfnbbNewOnChange;

  // link function into chain
  if (pfnbbOnChange == NULL) {
    NoConnection.pFirstPortChange = NULL;
  } else {
    NoConnection.pFirstPortChange = this;

    // the default is 0xFF, otherwise call the OnChange functions
    if (GetOutput() != 0xFF) {
      (GetParent()->*pfnbbOnChange)(GetOutput(), (byte)(GetOutput() ^ 0xFF));
    }
  }
}


////////////////////////////////////////////////////////////////////////////////
// set the callback function when one output in the group changes

global void Port::SetOnEveryChange(pfn pfnNewOnEveryChange) {

  // check parameters and conditions
  assert(!IsConnected());

  // set the OnEveryChange function
  pfnOnEveryChange = pfnNewOnEveryChange;

  // link function into chain
  if (pfnOnEveryChange == NULL) {
    NoConnection.pFirstPortEveryChange = NULL;
  } else {
    NoConnection.pFirstPortEveryChange = this;
  }
}


////////////////////////////////////////////////////////////////////////////////
// change the output of this port

global void Port::SetOutput(byte bNewOutput) {

  // don't waste time if the output doesn't change
  if (bNewOutput != *pbOutput) {  

    // set the new output byte
    *pbOutput = bNewOutput;

    // logical AND the 16 outputs together
    dword dwFour = *(dword*)(pConnection->abOutput + 0) & *(dword*)(pConnection->abOutput + 4) & *(dword*)(pConnection->abOutput + 8) & *(dword*)(pConnection->abOutput + 12);
    dword dwTwo = dwFour & (dwFour >> 16);
    byte bNewState = (byte)(dwTwo & (dwTwo >> 8));

    // get the difference between the group's old and new state
    byte bChanges = (byte)(bNewState ^ pConnection->bState);

    // don't waste time if the state didn't change
    if (bChanges != 0) {

      // save the new state
      pConnection->bState = bNewState;

      // call the OnChange functions
      for (Port* p = pConnection->pFirstPortChange; p != NULL; p = p->pNextPortChange) {
        (p->GetParent()->*p->pfnbbOnChange)(bNewState, bChanges);
      }

      // verify that no OnChange function has changed the group
      #ifdef _DEBUG
        if (GetInput() != bNewState) {
          char ac[256];
          GetFullName(ac, ac + sizeof ac);
          if (strcmp(ac, "C64.CIA2.PA") != 0) { // TODO: fix IEC bus
            error("%s.SetOutput(): Recursion detected!", ac);
          }
        }
      #endif
    }

    // call the OnEveryChange functions
    for (Port* p = pConnection->pFirstPortEveryChange; p != NULL; p = p->pNextPortEveryChange) {
      (p->GetParent()->*p->pfnOnEveryChange)();
    }
  }
}


////////////////////////////////////////////////////////////////////////////////
// get the input without this port (for connections between single pins)

global byte Port::GetInputWithoutThis() {

  // no calculation needed if all output lines are high
  if (GetOutput() == 0xFF) {
    return GetInput();
  }

  // temporarily ignore the current port
  byte bSavedOutput = *pbOutput;
  *pbOutput = 0xFF;

  // logical AND the outputs of all other ports in the group together
  dword dwFour = *(dword*)(pConnection->abOutput + 0) & *(dword*)(pConnection->abOutput + 4) & *(dword*)(pConnection->abOutput + 8) & *(dword*)(pConnection->abOutput + 12);
  dword dwTwo = dwFour & (dwFour >> 16);
  byte bStateWithoutThis = (byte)(dwTwo & (dwTwo >> 8));

  // restore the current port
  *pbOutput = bSavedOutput;

  // return the state of all other ports in group
  return bStateWithoutThis;
}


////////////////////////////////////////////////////////////////////////////////
// connect two ports or two groups of ports

global void Port::ConnectTo(Port& OtherPort) {

  // verify that the ports have been initialized
  assert(GetParent() != NULL);
  assert(OtherPort.GetParent() != NULL);

  // verify that the ports are OK
  #ifdef _DEBUG
    AssertValid();
    OtherPort.AssertValid();
  #endif

  // avoid double connections within the same group
  assert(!IsConnectedTo(OtherPort));

  // combine the low lines of both groups
  byte bNewState = (byte)(GetInput() & OtherPort.GetInput());
  byte bChanges = (byte)(GetInput() ^ bNewState);
  if (bChanges != 0) {
    for (Port* p = pConnection->pFirstPortChange; p != NULL; p = p->pNextPortChange) {
      (p->GetParent()->*p->pfnbbOnChange)(bNewState, bChanges);
    }
  }
  bChanges = (byte)(OtherPort.GetInput() ^ bNewState);
  if (bChanges != 0) {
    for (Port* p = OtherPort.pConnection->pFirstPortChange; p != NULL; p = p->pNextPortChange) {
      (p->GetParent()->*p->pfnbbOnChange)(bNewState, bChanges);
    }
  }

  // set the new common state
  OtherPort.pConnection->bState = bNewState;

  // find the end of the lists
  Connection* pOldConnection = pConnection;
  Connection* pNewConnection = OtherPort.pConnection;
  Port* pLast = NULL;
  int iIndex = 0;
  for (Port* p = pOldConnection->pFirstPort; p != NULL; p = p->pNextPort) {
    pLast = p;

    // set connection pointer from the old group to the new group
    p->pConnection = pNewConnection;

    // find a free output byte in the new group
    while ((pNewConnection->iOutputsUsed & (1 << iIndex)) != 0) {
      iIndex++;
      assert(iIndex < 16);
    }
    pNewConnection->iOutputsUsed |= 1 << iIndex;

    // move output byte and pointer from the old group to the new group
    assert(pNewConnection->abOutput[iIndex] == 0xFF);
    pNewConnection->abOutput[iIndex] = *p->pbOutput;
    p->pbOutput = &pNewConnection->abOutput[iIndex];
  }
  Port* pLastChange = NULL;
  for (p = pOldConnection->pFirstPortChange; p != NULL; p = p->pNextPortChange) {
    pLastChange = p;
  }
  Port* pLastEveryChange = NULL;
  for (p = pOldConnection->pFirstPortEveryChange; p != NULL; p = p->pNextPortEveryChange) {
    pLastEveryChange = p;
  }

  // link the old list into the new list
  assert(pLast != NULL);
  pLast->pNextPort = pNewConnection->pFirstPort;
  pNewConnection->pFirstPort = pOldConnection->pFirstPort;
  if (pLastChange != NULL) {
    pLastChange->pNextPortChange = pNewConnection->pFirstPortChange;
    pNewConnection->pFirstPortChange = pOldConnection->pFirstPortChange;
  }
  if (pLastEveryChange != NULL) {
    pLastEveryChange->pNextPortEveryChange = pNewConnection->pFirstPortEveryChange;
    pNewConnection->pFirstPortEveryChange = pOldConnection->pFirstPortEveryChange;
  }

  // mark the old connection block as unused
  pOldConnection->Clear();

  // call the OnEveryChange functions
  for (p = pConnection->pFirstPortEveryChange; p != NULL; p = p->pNextPortEveryChange) {
    (p->GetParent()->*p->pfnOnEveryChange)();
  }

  // verify that nothing went wrong
  #ifdef _DEBUG
    AssertValid();
    OtherPort.AssertValid();
  #endif
}


////////////////////////////////////////////////////////////////////////////////
// disconnect a port from the group

global void Port::Disconnect() {

  // ensure that port is connected
  assert(IsConnected());

  // verify that the port is OK
  #ifdef _DEBUG
    AssertValid();
  #endif

  // set group connection block to another port
  if (pConnection == &NoConnection) {
    Port* pNew = NoConnection.pFirstPort;
    if (pNew == this) {
      pNew = pNew->pNextPort;
    }
    for (Port* p = NoConnection.pFirstPort; p != NULL; p = p->pNextPort) {
      int iIndex = p->pbOutput - &p->pConnection->abOutput[0];
      p->pConnection = &pNew->NoConnection;
      p->pbOutput = &pNew->NoConnection.abOutput[iIndex];
    }
    pNew->NoConnection = NoConnection;
    NoConnection.Clear();
  }

  // remove the port from the lists
  for (Port** pp = &pConnection->pFirstPort; *pp != this; pp = &(*pp)->pNextPort) {
    assert(*pp != NULL);
  }
  *pp = pNextPort;
  pNextPort = NULL;
  if (pfnbbOnChange != NULL) {
    for (pp = &pConnection->pFirstPortChange; *pp != this; pp = &(*pp)->pNextPortChange) {
      assert(*pp != NULL);
    }
    *pp = pNextPortChange;
    pNextPortChange = NULL;
  }
  if (pfnOnEveryChange != NULL) {
    for (pp = &pConnection->pFirstPortEveryChange; *pp != this; pp = &(*pp)->pNextPortEveryChange) {
      assert(*pp != NULL);
    }
    *pp = pNextPortEveryChange;
    pNextPortEveryChange = NULL;
  }

  // draw the lines high which were low in the other group only
  NoConnection.bState = GetInput();
  if (*pbOutput != 0xFF) {
    NoConnection.abOutput[0] = *pbOutput;
    SetOutput(0xFF);
  }
  byte bChanges = (byte)(NoConnection.abOutput[0] ^ NoConnection.bState);
  if (bChanges != 0) {
    if (pfnbbOnChange != NULL) {
      (GetParent()->*pfnbbOnChange)(NoConnection.abOutput[0], bChanges);
    }
    NoConnection.bState = NoConnection.abOutput[0];
  }

  // clear the output used bit in the group connection block
  int iIndex = pbOutput - &pConnection->abOutput[0];
  assert(iIndex >= 0);
  assert(iIndex < 16);
  assert((pConnection->iOutputsUsed & (1 << iIndex)) != 0);
  pConnection->iOutputsUsed &= ~(1 << iIndex);

  // set the connection to default
  NoConnection.pFirstPort = this;
  if (pfnbbOnChange != NULL) {
    NoConnection.pFirstPortChange = this;
  }
  if (pfnOnEveryChange != NULL) {
    NoConnection.pFirstPortEveryChange = this;
  }
  pbOutput = &NoConnection.abOutput[0];
  NoConnection.iOutputsUsed = 1;
  Port* pFirstPortEveryChange = pConnection->pFirstPortEveryChange;
  pConnection = &NoConnection;

  // call the OnEveryChange functions in both groups
  for (Port* p = pFirstPortEveryChange; p != NULL; p = p->pNextPortEveryChange) {
    (p->GetParent()->*p->pfnOnEveryChange)();
  }
  p = NoConnection.pFirstPortEveryChange;
  if (p != NULL) {
    (p->GetParent()->*p->pfnOnEveryChange)();
  }

  // verify that nothing went wrong
  #ifdef _DEBUG
    AssertValid();
  #endif
}


////////////////////////////////////////////////////////////////////////////////
// check the port and its connection for errors

global void Port::AssertValid() {

  // verify that the port has been initialized
  if (GetParent() == NULL) {
    error("Port::AssertValid(): Port has not been initialized");
  }

  // pConnection must always point to a connection
  if (pConnection == NULL) {
    error("Port::AssertValid(): pConnection == NULL");
  }

  // pbOutput must always point to pConnection->abOutput[0-15]
  int iIndex = pbOutput - &pConnection->abOutput[0];
  if (iIndex < 0 || iIndex >= 16) {
    error("Port::AssertValid(): pbOutput is not in range abOutput[0-15]");
  }

  // pbOutput must be marked as used
  if ((pConnection->iOutputsUsed & (1 << iIndex)) == 0) {
    error("Port::AssertValid(): output is not marked as used");
  }

  // check for circular lists
  for (Port* p = pConnection->pFirstPort; p != NULL; p = p->pNextPort) {
    for (Port* p1 = p->pNextPort; p1 != NULL; p1 = p1->pNextPort) {
      if (p1 == p) {
        error("Port::AssertValid(): circular list");
      }
    }
  }
  for (p = pConnection->pFirstPortChange; p != NULL; p = p->pNextPortChange) {
    for (Port* p1 = p->pNextPortChange; p1 != NULL; p1 = p1->pNextPortChange) {
      if (p1 == p) {
        error("Port::AssertValid(): circular OnChange list");
      }
    }
  }
  for (p = pConnection->pFirstPortEveryChange; p != NULL; p = p->pNextPortEveryChange) {
    for (Port* p1 = p->pNextPortEveryChange; p1 != NULL; p1 = p1->pNextPortEveryChange) {
      if (p1 == p) {
        error("Port::AssertValid(): circular OnEveryChange list");
      }
    }
  }

  // for all ports in the current group
  byte bState = 0xFF;
  int iOutputsUsed = 0;
  for (p = pConnection->pFirstPort; p != NULL; p = p->pNextPort) {
  
    // there may be only one connection block
    if (p->pConnection != pConnection) {
      error("Port::AssertValid(): port in the list points to another group");
    }

    // pbOutput must always point to pConnection->abOutput[0-15]
    int iIndex = p->pbOutput - &pConnection->abOutput[0];
    if (iIndex < 0 || iIndex >= 16) {
      error("Port::AssertValid(): a port in the group is not in range abOutput[0-15]");
    }

    // find out whether port is in OnChange list
    flag fInChangeList = false;
    for (Port* p1 = pConnection->pFirstPortChange; p1 != NULL; p1 = p1->pNextPortChange) {
      if (p1 == p) {
        fInChangeList = true;
      }
    }

    // if the port has an OnChange function, it must be in the list
    if (p->pfnbbOnChange != NULL) {
      if (!fInChangeList) {
        error("Port::AssertValid(): port has OnChange function but is not in OnChange list");
      }
    }

    // if the Port has no OnChange function, it may not be in the list
    if (p->pfnbbOnChange == NULL) {
      if (fInChangeList) {
        error("Port::AssertValid(): port has no OnChange function but is in OnChange list");
      }
    }

    // find out whether port is in OnEveryChange list
    flag fInEveryChangeList = false;
    for (p1 = pConnection->pFirstPortEveryChange; p1 != NULL; p1 = p1->pNextPortEveryChange) {
      if (p1 == p) {
        fInEveryChangeList = true;
      }
    }

    // if the port has an OnEveryChange function, it must be in the list
    if (p->pfnOnEveryChange != NULL) {
      if (!fInEveryChangeList) {
        error("Port::AssertValid(): port has OnEveryChange function but is not in OnEveryChange list");
      }
    }

    // if the Port has no OnEveryChange function, it may not be in the list
    if (p->pfnOnEveryChange == NULL) {
      if (fInEveryChangeList) {
        error("Port::AssertValid(): port has no OnEveryChange function but is in OnEveryChange list");
      }
    }

    // add low lines
    bState &= *p->pbOutput;

    // two outputs may not point to the same byte
    if ((iOutputsUsed & (1 << iIndex)) != 0) {
      error("Port::AssertValid(): output used twice");
    }
    iOutputsUsed |= 1 << iIndex;

  }

  // verify low lines
  if (pConnection->bState != bState) {
    error("Port::AssertValid(): pConnection->bState is wrong");
  }

  // verify used outputs
  if (pConnection->iOutputsUsed != iOutputsUsed) {
    error("Port::AssertValid(): pConnection->iOutputsUsed is wrong");
  }

  // all ports in OnChange list must be in group list
  for (p = pConnection->pFirstPortChange; p != NULL; p = p->pNextPortChange) {
    for (Port* p1 = pConnection->pFirstPort; p1 != p; p1 = p1->pNextPort) {
      if (p1 == NULL) {
        error("Port::AssertValid(): port in OnChange list is not in group list");
      }
    }
  }

  // all ports in OnEveryChange list must be in group list
  for (p = pConnection->pFirstPortEveryChange; p != NULL; p = p->pNextPortEveryChange) {
    for (Port* p1 = pConnection->pFirstPort; p1 != p; p1 = p1->pNextPort) {
      if (p1 == NULL) {
        error("Port::AssertValid(): port in OnEveryChange list is not in group list");
      }
    }
  }
}
