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

RegisterPersistentClass(Line);


////////////////////////////////////////////////////////////////////////////////
// set callback function when the group goes high

global void Line::SetOnHigh(pfn pfnNewOnHigh) {

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

  // set the OnHigh function
  pfnOnHigh = pfnNewOnHigh;

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


////////////////////////////////////////////////////////////////////////////////
// set callback function when the group goes low

global void Line::SetOnLow(pfn pfnNewOnLow) {

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

  // set the OnLow function
  pfnOnLow = pfnNewOnLow;

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

    // call the OnLow function if the line is low
    if (IsInputLow()) {
      (GetParent()->*pfnOnLow)();
    }
  }
}


////////////////////////////////////////////////////////////////////////////////
// set callback function when any line in the group changes its state

global void Line::SetOnEveryChange(pfn pfnNewOnEveryChange) {

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

  // set the OnLow function
  pfnOnEveryChange = pfnNewOnEveryChange;

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


////////////////////////////////////////////////////////////////////////////////
// set a low line to high

global void Line::SetOutputHigh() {

  // forcing parent to check state brings speed
  assert(!IsOutputHigh());

  // draw the current line high
  fOutputLow = false;

  // decrement the number of low lines in this group
  // and call the OnHigh() functions when there are no more low lines
  if (--pConnection->iCountLow == 0) {
    for (Line* p = pConnection->pFirstLineHigh; p != NULL; p = p->pNextLineHigh) {
      (p->GetParent()->*p->pfnOnHigh)();
    }

    // verify that no OnHigh function has set the group to low
    #ifdef _DEBUG
      if (!IsInputHigh()) {
        char ac[256];
        GetFullName(ac, ac + sizeof ac);
        error("%s.SetOutputHigh(): Recursion detected!", ac);
      }
    #endif
  }

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


////////////////////////////////////////////////////////////////////////////////
// set a high line to low

global void Line::SetOutputLow() {

  // forcing parent to check state brings speed
  assert(!IsOutputLow());

  // draw current line low
  fOutputLow = true;

  // increment the number of low lines in this connection
  // and call the OnLow() functions when it was the first low line
  if (pConnection->iCountLow++ == 0) {
    for (Line* p = pConnection->pFirstLineLow; p != NULL; p = p->pNextLineLow) {
      (p->GetParent()->*p->pfnOnLow)();
    }

    // verify that no OnLow function has set the group to high
    #ifdef _DEBUG
      if (!IsInputLow()) {
        char ac[256];
        GetFullName(ac, ac + sizeof ac);
        error("%s.SetOutputLow(): Recursion detected!", ac);
      }
    #endif
  }

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


////////////////////////////////////////////////////////////////////////////////
// connect two lines or two groups of lines

global void Line::ConnectTo(Line& OtherLine) {

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

  // verify that the lines are OK
  #ifdef _DEBUG
    AssertValid();
    OtherLine.AssertValid();
  #endif

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

  // if one group is high and the other group is low, then draw the
  // high group to low
  Line* p = NULL;
  if (IsInputHigh()) {
    if (OtherLine.IsInputLow()) {
      p = pConnection->pFirstLineLow;
    }
  } else {
    if (OtherLine.IsInputHigh()) {
      p = OtherLine.pConnection->pFirstLineLow;
    }
  }
  while (p != NULL) {
    (p->GetParent()->*p->pfnOnLow)();
    p = p->pNextLineLow;
  }

  // add the number of low lines in the two groups
  OtherLine.pConnection->iCountLow += pConnection->iCountLow;

  // find the end of the lists
  Connection* pOldConnection = pConnection;
  Line* pLast = NULL;
  for (p = pOldConnection->pFirstLine; p != NULL; p = p->pNextLine) {
    pLast = p;

    // set all connection pointers in the old group to the new group
    p->pConnection = OtherLine.pConnection;
  }
  Line* pLastHigh = NULL;
  for (p = pOldConnection->pFirstLineHigh; p != NULL; p = p->pNextLineHigh) {
    pLastHigh = p;
  }
  Line* pLastLow = NULL;
  for (p = pOldConnection->pFirstLineLow; p != NULL; p = p->pNextLineLow) {
    pLastLow = p;
  }
  Line* pLastEveryChange = NULL;
  for (p = pOldConnection->pFirstLineEveryChange; p != NULL; p = p->pNextLineEveryChange) {
    pLastEveryChange = p;
  }

  // link the old lists into the new lists
  assert(pLast != NULL);
  pLast->pNextLine = OtherLine.pConnection->pFirstLine;
  OtherLine.pConnection->pFirstLine = pOldConnection->pFirstLine;
  if (pLastHigh != NULL) {
    pLastHigh->pNextLineHigh = OtherLine.pConnection->pFirstLineHigh;
    OtherLine.pConnection->pFirstLineHigh = pOldConnection->pFirstLineHigh;
  }
  if (pLastLow != NULL) {
    pLastLow->pNextLineLow = OtherLine.pConnection->pFirstLineLow;
    OtherLine.pConnection->pFirstLineLow = pOldConnection->pFirstLineLow;
  }
  if (pLastEveryChange != NULL) {
    pLastEveryChange->pNextLineEveryChange = pConnection->pFirstLineEveryChange;
    pConnection->pFirstLineEveryChange = pOldConnection->pFirstLineEveryChange;
  }

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

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

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


////////////////////////////////////////////////////////////////////////////////
// Disconnect line from group

global void Line::Disconnect() {

  // check if line is connected
  assert(IsConnected());

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

  // set group connection block to another line
  if (pConnection == &NoConnection) {
    Line* pNew = NoConnection.pFirstLine;
    if (pNew == this) {
      pNew = pNew->pNextLine;
    }
    for (Line* p = NoConnection.pFirstLine; p != NULL; p = p->pNextLine) {
      p->pConnection = &pNew->NoConnection;
    }
    pNew->NoConnection = NoConnection;
    NoConnection.Clear();
  }

  // remove the line from the lists
  for (Line** pp = &pConnection->pFirstLine; *pp != this; pp = &(*pp)->pNextLine) {
    assert(*pp != NULL);
  }
  *pp = pNextLine;
  pNextLine = NULL;
  if (pfnOnHigh != NULL) {
    for (pp = &pConnection->pFirstLineHigh; *pp != this; pp = &(*pp)->pNextLineHigh) {
      assert(*pp != NULL);
    }
    *pp = pNextLineHigh;
    pNextLineHigh = NULL;
  }
  if (pfnOnLow != NULL) {
    for (pp = &pConnection->pFirstLineLow; *pp != this; pp = &(*pp)->pNextLineLow) {
      assert(*pp != NULL);
    }
    *pp = pNextLineLow;
    pNextLineLow = NULL;
  }
  if (pfnOnEveryChange != NULL) {
    for (pp = &pConnection->pFirstLineEveryChange; *pp != this; pp = &(*pp)->pNextLineEveryChange) {
      assert(*pp != NULL);
    }
    *pp = pNextLineEveryChange;
    pNextLineEveryChange = NULL;
  }

  // draw the group/line high if the other line/group was low
  if (IsOutputLow()) {
    if (--pConnection->iCountLow == 0) {
      for (Line* p = pConnection->pFirstLineHigh; p != NULL; p = p->pNextLineHigh) {
        (p->GetParent()->*p->pfnOnHigh)();
      }
    }
  } else {
    if (pConnection->pFirstLine->IsInputLow()) {
      if (pfnOnHigh != NULL) {
        (GetParent()->*pfnOnHigh)();
      }
    }
  }

  // set the connection to default
  NoConnection.pFirstLine = this;
  if (pfnOnHigh != NULL) {
    NoConnection.pFirstLineHigh = this;
  }
  if (pfnOnLow != NULL) {
    NoConnection.pFirstLineLow = this;
  }
  if (pfnOnEveryChange != NULL) {
    NoConnection.pFirstLineEveryChange = this;
  }
  if (IsOutputLow()) {
    NoConnection.iCountLow = 1;
  }
  Line* pFirstLineEveryChange = pConnection->pFirstLineEveryChange;
  pConnection = &NoConnection;

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

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


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

global void Line::AssertValid() {

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

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

  // check for circular lists
  for (Line* p = pConnection->pFirstLine; p != NULL; p = p->pNextLine) {
    for (Line* p1 = p->pNextLine; p1 != NULL; p1 = p1->pNextLine) {
      if (p1 == p) {
        error("Line::AssertValid(): Circular list");
      }
    }
  }
  for (p = pConnection->pFirstLineHigh; p != NULL; p = p->pNextLineHigh) {
    for (Line* p1 = p->pNextLineHigh; p1 != NULL; p1 = p1->pNextLineHigh) {
      if (p1 == p) {
        error("Line::AssertValid(): Circular OnHigh list");
      }
    }
  }
  for (p = pConnection->pFirstLineLow; p != NULL; p = p->pNextLineLow) {
    for (Line* p1 = p->pNextLineLow; p1 != NULL; p1 = p1->pNextLineLow) {
      if (p1 == p) {
        error("Line::AssertValid(): Circular OnLow list");
      }
    }
  }
  for (p = pConnection->pFirstLineEveryChange; p != NULL; p = p->pNextLineEveryChange) {
    for (Line* p1 = p->pNextLineEveryChange; p1 != NULL; p1 = p1->pNextLineEveryChange) {
      if (p1 == p) {
        error("Line::AssertValid(): Circular OnEveryChange list");
      }
    }
  }

  // for all lines in the current group
  int iCountLow = 0;
  for (p = pConnection->pFirstLine; p != NULL; p = p->pNextLine) {

    // there may be only one connection block
    if (p->pConnection != pConnection) {
      error("Line::AssertValid(): A line in the list points to another group");
    }

    // find out whether line is in OnHigh/OnLow lists
    flag fInHighList = false;
    for (Line* p1 = pConnection->pFirstLineHigh; p1 != NULL; p1 = p1->pNextLineHigh) {
      if (p1 == p) {
        fInHighList = true;
      }
    }
    flag fInLowList = false;
    for (p1 = pConnection->pFirstLineLow; p1 != NULL; p1 = p1->pNextLineLow) {
      if (p1 == p) {
        fInLowList = true;
      }
    }

    // if the line has an OnHigh/OnLow function, it must be in the list
    if (p->pfnOnHigh != NULL) {
      if (!fInHighList) {
        error("Line::AssertValid(): Line has OnHigh function but is not in OnHigh list");
      }
    }
    if (p->pfnOnLow != NULL) {
      if (!fInLowList) {
        error("Line::AssertValid(): Line has OnLow function but is not in OnLow list");
      }
    }

    // if the line has no OnHigh/OnLow function, it may not be in the list
    if (p->pfnOnHigh == NULL) {
      if (fInHighList) {
        error("Line::AssertValid(): Line has no OnHigh function but is in OnHigh list");
      }
    }
    if (p->pfnOnLow == NULL) {
      if (fInLowList) {
        error("Line::AssertValid(): Line has no OnLow function but is in OnLow list");
      }
    }

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

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

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

    // get count of low lines
    if (p->IsOutputLow()) {
      iCountLow++;
    }
  }

  // verify count of low lines
  if (pConnection->iCountLow != iCountLow) {
    error("Line::AssertValid(): Number of low lines is wrong");
  }

  // all lines in OnHigh/OnLow lists must be in group list
  for (p = pConnection->pFirstLineHigh; p != NULL; p = p->pNextLineHigh) {
    for (Line* p1 = pConnection->pFirstLine; p1 != p; p1 = p1->pNextLine) {
      if (p1 == NULL) {
        error("Line::AssertValid(): Line in OnHigh list is not in group list");
      }
    }
  }
  for (p = pConnection->pFirstLineLow; p != NULL; p = p->pNextLineLow) {
    for (Line* p1 = pConnection->pFirstLine; p1 != p; p1 = p1->pNextLine) {
      if (p1 == NULL) {
        error("Line::AssertValid(): Line in OnLow list is not in group list");
      }
    }
  }

  // all lines in OnEveryChange list must be in group list
  for (p = pConnection->pFirstLineEveryChange; p != NULL; p = p->pNextLineEveryChange) {
    for (Line* p1 = pConnection->pFirstLine; p1 != p; p1 = p1->pNextLine) {
      if (p1 == NULL) {
        error("Line::AssertValid(): Line in OnEveryChange list is not in group list");
      }
    }
  }
}
