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

RegisterPersistentClass(Timer);


////////////////////////////////////////////////////////////////////////////////
// set a new clock

global void Timer::SetClock(Clock& NewClock) {

  // check conditions
  assert(!IsCounting());
  assert(pTimerRoot == NULL);

  // set new TimerRoot
  pTimerRoot = &NewClock.TimerRoot;

  // set clock for the parent class
  Object::SetClock(NewClock);
}


////////////////////////////////////////////////////////////////////////////////
// activate the timer

#ifdef P5ASM

__declspec(naked) global void Timer::StartCounter(int) { P5ASM {

  mov EDX,ESP
  mov EAX,[ECX]this.pTimerRoot

  push EBX
  push ESI

  mov EBX,[EAX]this.pNextTimer
  mov ESI,[EAX]this.iClocks

  push EDI
  push EBP

  mov EBP,[EDX+4]
  mov EDI,[EBX]this.iClocks

  add ESI,EDI
  mov EDX,[ECX]this.pNextTimer

  push ESI
  add ESI,EBP

  mov [ECX]this.iClocks,ESI
  mov EDI,[ECX]this.pPrevTimer

  test EDX,EDX
  je SearchNext

  mov [EDX]this.pPrevTimer,EDI
  mov [EDI]this.pNextTimer,EDX

  nop
  mov EBX,[EAX]this.pNextTimer

SearchNext:

  cmp EBX,EAX
  je FoundEBX

  mov EDI,[EBX]this.iClocks
  mov EBP,[EBX]this.pNextTimer

  cmp EDI,ESI
  jns FoundEBX

  cmp EBP,EAX
  je FoundEBP

  mov EDI,[EBP]this.iClocks
  mov EBX,[EBX]this.pNextTimer

  cmp EDI,ESI
  js SearchNext

FoundEBP:
  
  nop
  mov EBX,EBP

FoundEBX:

  mov EDX,[EBX]this.pPrevTimer
  mov [ECX]this.pNextTimer,EBX

  mov [ECX]this.pPrevTimer,EDX
  mov [EBX]this.pPrevTimer,ECX

  mov [EDX]this.pNextTimer,ECX
  mov EDI,[EAX]this.pNextTimer

  pop ECX
  pop EBP

  nop
  mov ESI,[EDI]this.iClocks

  sub ECX,ESI
  pop EDI

  nop
  mov [EAX]this.iClocks,ECX

  pop ESI
  pop EBX

  ret 4
}}

#else // !defined(P5ASM)

global void Timer::StartCounter(int iNewClocks) {

  // check parameters and conditions
  assert(pTimerRoot != NULL);
  assert(iNewClocks > 0);

  // convert system time to absolute time
  Timer* p = pTimerRoot->pNextTimer;
  pTimerRoot->iClocks += p->iClocks;

  // convert iClocks to absolute time
  iClocks = pTimerRoot->iClocks + iNewClocks;

  // remove the timer from the list if it was already active
  if (IsCounting()) {
    pNextTimer->pPrevTimer = pPrevTimer;
    pPrevTimer->pNextTimer = pNextTimer;

    // there may be a new head in the list
    p = pTimerRoot->pNextTimer;
  }

  // search for the position in the list. A simple sequential scan works
  // best because frequent elements will be at the top of the list
  while (p != pTimerRoot && (p->iClocks - iClocks) < 0) {
    p = p->pNextTimer;
  }

  // insert this before p
  pNextTimer = p;
  pPrevTimer = p->pPrevTimer;
  pNextTimer->pPrevTimer = this;
  pPrevTimer->pNextTimer = this;

  // convert system time back to relative
  pTimerRoot->iClocks -= pTimerRoot->pNextTimer->iClocks;
}

#endif // defined(P5ASM)


////////////////////////////////////////////////////////////////////////////////
// deactivate the timer

global void Timer::StopCounter() {

  // check conditions
  assert(pTimerRoot != NULL);
  assert(IsCounting());

  // convert system time to absolute
  pTimerRoot->iClocks += pTimerRoot->pNextTimer->iClocks;

  // remove the timer from the list
  pNextTimer->pPrevTimer = pPrevTimer;
  pPrevTimer->pNextTimer = pNextTimer;

  // mark the timer as not active
  pPrevTimer = NULL;

  // convert system time back to relative
  pTimerRoot->iClocks -= pTimerRoot->pNextTimer->iClocks;

}


////////////////////////////////////////////////////////////////////////////////
// called from the Clock when one or more timers have expired

global void Timer::Fire() {

  // this is valid for the root only
  assert(this == pTimerRoot);

  // the relative time must be 0
  assert(iClocks == 0);

  do {

    // get the new relative system time
    Timer* p = pNextTimer;
    iClocks = p->iClocks - p->pNextTimer->iClocks;

    // remove the top timer from the list
    pNextTimer = p->pNextTimer;
    pNextTimer->pPrevTimer = this;

    // mark the old top as not active
    p->pPrevTimer = NULL;

    // call the OnFire function
    (p->GetParent()->*p->pfnOnFire)();

  // treat all timers which have expired
  } while (iClocks == 0);

}


////////////////////////////////////////////////////////////////////////////////
// check the timer list for errors

global void Timer::AssertValid() {

  // for all active timers
  flag fInList = false;
  int iLastTime = pTimerRoot->iClocks + pTimerRoot->pNextTimer->iClocks;
  for (Timer* p = pTimerRoot->pNextTimer; p != pTimerRoot; p = p->pNextTimer) {

    // all timers must have the same root
    if (p->pTimerRoot != pTimerRoot) {
      error("Timer::AssertValid(): one timer in the list has a different root");
    }

    // check the linkage pointers
    if (p->pNextTimer->pPrevTimer != p) {
      error("Timer::AssertValid(): p->pNextTimer->pPrevTimer != p");
    }
    if (p->pPrevTimer->pNextTimer != p) {
      error("Timer::AssertValid(): p->pPrevTimer->pNextTimer != p");
    }

    // verify that timers are sorted in ascending order
    if (p->iClocks - iLastTime < 0) {
      error("Timer::AssertValid(): times are not in ascending order");
    }
    iLastTime = p->iClocks;

    // set flag if timer is in list
    if (p == this) {
      fInList = true;
    }

  }

  // verify that the timer is in list/is not in list
  if (IsCounting()) {
    if (!fInList) {
      error("Timer::AssertValid(): timer is active but not in list");
    }
  } else {
    if (fInList) {
      error("Timer::AssertValid(): timer is in list but is not active");
    }
  }

}
