/* Copyright 1989, 1990, 1995 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_vbl[] =
"$Id: vbl.c 63 2004-12-24 18:19:43Z ctm $";
#endif

/* Forward declarations in VRetraceMgr.h (DO NOT DELETE THIS LINE) */

#include "rsys/common.h"
#include "OSUtil.h"
#include "TimeMgr.h"
#include "VRetraceMgr.h"
#include "DeviceMgr.h"
#include "SoundDvr.h"
#include "OSEvent.h"
#include "ToolboxEvent.h"

#include "rsys/pstuff.h"
#include "rsys/vbl.h"
#include "rsys/time.h"
#include "rsys/hook.h"

#include "rsys/soundopts.h"
#include "rsys/prefs.h"


typedef enum
  {
    CLOCKOFF = 0,
    CLOCKTEMPON,
    CLOCKON,
    CLOCKTEMPONSOUND,
    CLOCKONSOUND,
  }
clockstatus_t;

PUBLIC int ROMlib_clock = CLOCKOFF;

/*
 * ROMlib_vcatch is called by the time manager, hence it doesn't need to
 * block signals
 */

/*
 * NOTE: we put a shim in the VBL queue, because Prince of Persia
 *       has a bug in it that overlooks the first element in the queue
 *       when it's looking for itself!  We put in a magic ptr value we
 *       can recognize in case some bozo actually ever tries to call it.
 *       Note, however, that VInstall fills this in with a pointer to
 *       a m68k RTS instruction, just to be safe.
 */

VBLTask vblshim = { 0, CWC (vType), (ProcPtr) CLC (0xAEDCBA98), 0, 0 };

PRIVATE TMTask vbltm;

#define VBUSY	(1 << 6)

A0 (PUBLIC, void, C_ROMlib_vcatch)
{
  VBLTaskPtr vp, next;
  ULONGINT old_ticks, new_ticks, ticks_elapsed;
  LONGINT msecs_to_next_tick;
  CCRElement saved_ccnz, saved_ccn, saved_ccc, saved_ccv, saved_ccx;
  M68kReg saved_regs[16];

  /* We save the 68k registers away.  This isn't necessary if we can
   * only be called from the time manager, since it has already saved
   * all of the registers.
   */
  memcpy (saved_regs, &cpu_state.regs, sizeof saved_regs);
  saved_ccnz = cpu_state.ccnz;
  saved_ccn  = cpu_state.ccn;
  saved_ccc  = cpu_state.ccc;
  saved_ccv  = cpu_state.ccv;
  saved_ccx  = cpu_state.ccx;

  EM_A7 = (EM_A7 - 32) & ~3;  /* Might as well long-align it. */

  /* Save the old Ticks value & compute new value. */
  old_ticks = CL (Ticks);
  new_ticks = C_TickCount ();
  ticks_elapsed = new_ticks - old_ticks;

  /* Compute the milliseconds until Ticks changes again.
   * Here we rely on the fact that 1000 / 60 == 50 / 3, to avoid overflow.
   */
  msecs_to_next_tick = (((new_ticks + 1) * 50 + 2) / 3) - msecs_elapsed ();
  if (msecs_to_next_tick <= 0)
    msecs_to_next_tick = 1;

  PrimeTime ((QElemPtr) &vbltm, msecs_to_next_tick);
  
  for (vp = (VBLTaskPtr) MR (VBLQueue.qHead); vp; vp = next)
    {
      INTEGER old_vbl_count, new_vbl_count;

      next = (VBLTaskPtr) MR (vp->qLink);

      /* Blow off the Prince of Persia shim. */
      if (vp == &vblshim)
/*-->*/ continue;

      /* Account for possible missed ticks by possibly subtracting
       * off more than one tick from the VBL count. */
      old_vbl_count = CW (vp->vblCount);
      new_vbl_count = old_vbl_count - ticks_elapsed;
      if (old_vbl_count > 0 && new_vbl_count < 0)
	new_vbl_count = 0;  /* Only compensate for zero crossings. */

      vp->vblCount = CW (new_vbl_count);
      if (new_vbl_count == 0)
	{
	  VBLQueue.qFlags |= CWC (VBUSY);
	  ROMlib_hook (vbl_number);

	  /* No need to save/restore syn68k regs here; we do that
	   * outside the loop.
	   */

	  EM_A0 = (LONGINT) (long) US_TO_SYN68K_CHECK0(vp);
	  EM_A1 = (LONGINT) CL ((long) vp->vblAddr);
	  
	  CALL_EMULATOR ((syn68k_addr_t) EM_A1);

	  VBLQueue.qFlags &= CWC (~VBUSY);
	  if (vp->vblCount == CWC (0))
	    Dequeue ((QElemPtr) vp, &VBLQueue);
	}
    }

  memcpy (&cpu_state.regs, saved_regs, sizeof saved_regs);
  cpu_state.ccnz = saved_ccnz;
  cpu_state.ccn  = saved_ccn;
  cpu_state.ccc  = saved_ccc;
  cpu_state.ccv  = saved_ccv;
  cpu_state.ccx  = saved_ccx;
}

PRIVATE void 
startclock (void)
{
  vbltm.tmAddr = RM ((ProcPtr) P_ROMlib_vcatch);
  InsTime ((QElemPtr) &vbltm);
  PrimeTime ((QElemPtr) &vbltm, 17);
  /* don't adjust ROMlib_clock; it could be temporary */
}

PRIVATE void 
stopclock (void)
{
  if (!VBLQueue.qTail)
    {
      RmvTime ((QElemPtr) &vbltm);
      ROMlib_clock = CLOCKOFF;
    }
  else
    ROMlib_clock = CLOCKTEMPON;
}

A1 (PUBLIC, void, ROMlib_clockonoff, LONGINT, onoroff)
{
  if (onoroff)
    {
      if (ROMlib_clock == CLOCKOFF)
	startclock ();
      ROMlib_clock = CLOCKON;
    }
  else
    stopclock ();
}

A2 (PUBLIC trap, OSErrRET, SlotVInstall,
    VBLTaskPtr, vtaskp,
    INTEGER, slot)
{
  return VInstall (vtaskp);
}

A1 (PUBLIC trap, OSErrRET, VInstall, VBLTaskPtr, vtaskp)
{
  static unsigned short m68k_rts = CWC (0x4E75);	/* RTS */

  vtaskp->vblCount = CW (CW (vtaskp->vblCount) + CW (vtaskp->vblPhase));
  if (vtaskp->qType == CWC ((INTEGER) vType))
    {
      if (!VBLQueue.qHead)
	{
	  vblshim.vblAddr = (ProcPtr) RM (&m68k_rts);	/* legal 68k code. */
	  Enqueue ((QElemPtr) &vblshim, &VBLQueue);
	}
      Enqueue ((QElemPtr) vtaskp, &VBLQueue);
      if (ROMlib_clock == CLOCKOFF)
	{
	  ROMlib_clock = CLOCKTEMPON;
	  startclock ();
	}
/*-->*/ return noErr;
    }
  else
    return vTypErr;
}

A2 (PUBLIC trap, OSErrRET, SlotVRemove,
    VBLTaskPtr, vtaskp,
    INTEGER, slot)
{
  return VRemove (vtaskp);
}

A1 (PUBLIC trap, OSErrRET, VRemove, VBLTaskPtr, vtaskp)
{
  if (vtaskp->qType == CWC ((INTEGER) vType))
    {
      Dequeue ((QElemPtr) vtaskp, &VBLQueue);
      if ((VBLTaskPtr) MR (VBLQueue.qHead) == &vblshim
	  && (VBLTaskPtr) MR (VBLQueue.qTail) == &vblshim)
	{
	  Dequeue ((QElemPtr) &vblshim, &VBLQueue);
	  if (ROMlib_clock == CLOCKTEMPON)
	    stopclock ();
	}
/*-->*/ return noErr;
    }
  else
    return vTypErr;
}

A0 (PUBLIC, QHdrPtr, GetVBLQHdr)
{
  return &VBLQueue;
}
