/* Copyright 1986 - 1996 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

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

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

/*
 * really should be divided into two sections, just like main.c (that
 * is to say just like main.c should be, it isn't yet)
 */

#include "rsys/common.h"

#include "QuickDraw.h"
#include "MemoryMgr.h"
#include "CQuickDraw.h"
#include "ToolboxEvent.h"
#include "OSEvent.h"
#include "EventMgr.h"
#include "OSUtil.h"
#include "ResourceMgr.h"
#include "ProcessMgr.h"
#include "AppleEvents.h"

#include "rsys/cquick.h"
#include "rsys/mman.h"
#include "rsys/arrowkeys.h"
#include "rsys/notmac.h"
#include "rsys/blockinterrupts.h"
#include "rsys/time.h"
#include "rsys/prefs.h"
#include "rsys/vdriver.h"
#include "rsys/host.h"
#include "rsys/next.h"
#include "rsys/segment.h"
#include "rsys/toolevent.h"
#include "rsys/osevent.h"
#include "rsys/dirtyrect.h"
#include "rsys/license.h"
#include "rsys/stdfile.h"
#include "rsys/system_error.h"

#include "rsys/string.h"
#include "rsys/keyboard.h"

#if defined (NEXTSTEP)
#include "contextswitch.h"
#endif

#include "DialogMgr.h"
#include "SegmentLdr.h"

#if defined (MSDOS)
#include "dosevents.h"
#endif

#define NEVENT	20

PRIVATE int nevent = 0;
PRIVATE EvQEl evs[NEVENT], *freeelem = evs+NEVENT-1;

#define ROMlib_curs	MouseLocation

PUBLIC INTEGER ROMlib_mods = btnState;
PRIVATE LONGINT autoticks;
PRIVATE LONGINT lastdown = -1;

#if !defined(NEXTSTEP)
PUBLIC short ROMlib_pinned = FALSE;
#else
PUBLIC short ROMlib_pinned = TRUE;
#endif

PRIVATE Ptr kchr_ptr;

PUBLIC void
invalidate_kchr_ptr (void)
{
  kchr_ptr = 0;
}

PRIVATE INTEGER kchr_id = 0;

PUBLIC Ptr
ROMlib_kchr_ptr (void)
{
  if (!kchr_ptr)
    {
      ZONE_SAVE_EXCURSION
	(SysZone,
	 {
	   Handle kchr_hand;

	   kchr_hand = GetResource (TICK ("KCHR"), kchr_id);
	   gui_assert (kchr_hand);
	   LoadResource (kchr_hand);
	   HLock (kchr_hand);
	   kchr_ptr = STARH (kchr_hand);
	 });
    }
  return kchr_ptr;
}

PUBLIC boolean_t
ROMlib_set_keyboard (const char *keyboardname)
{
  Handle new_h;

  ZONE_SAVE_EXCURSION
    (SysZone,
     {
       Str255 pkeyboardname;

       str255_from_c_string (pkeyboardname, keyboardname);
       new_h = GetNamedResource (TICK ("KCHR"), pkeyboardname);
       if (new_h)
	 {
	   GetResInfo (new_h, &kchr_id, 0, 0);
	   kchr_id = CW (kchr_id);
	   LoadResource (new_h);
	   if (kchr_ptr)
	     {
	       Handle kchr_hand;

	       kchr_hand = RecoverHandle (kchr_ptr);
	       HUnlock (kchr_hand);
	     }
	   HLock (new_h);
	   kchr_ptr = STARH (new_h);
	 }
     });
  return !!new_h;
}

PRIVATE boolean_t map_right_to_left = TRUE;

PUBLIC uint16
ROMlib_right_to_left_key_map (uint16 what)
{
  uint16 retval;

  retval = what;
  if (map_right_to_left)
    switch (what)
      {
      default:
	break;
      case MKV_RIGHTSHIFT:
	retval = MKV_LEFTSHIFT;
	break;
      case MKV_RIGHTOPTION:
	retval = MKV_LEFTOPTION;
	break;
      case MKV_RIGHTCNTL:
	retval = MKV_LEFTCNTL;
	break;
      }
  return retval;
}

/*
 * NOTE: we figure out the value for a down keystroke, then we just remember
 * what we figured out and return that value on the up.  This probably isn't
 * how the Mac does it, but it's probably close enough.  Largely this
 * routine is just a wrapper for KeyTrans now.  See IMV for an explanation
 * of what's going on here.
 */

PUBLIC LONGINT
ROMlib_xlate (INTEGER virt, INTEGER modifiers, boolean_t down_p)
{
  static uint16 down_value[VIRT_MASK + 1];
  LONGINT retval;
  
  if (!down_p)
    retval = down_value[virt & VIRT_MASK];
  else
    {
      static LONGINT state;
      
      retval = KeyTrans (ROMlib_kchr_ptr (),
			 modifiers | (virt & VIRT_MASK), &state);
      down_value[virt & VIRT_MASK] = (retval >> 16
				      ? retval >> 16
				      : retval & 0xFFFF);
    }
  return retval;
}

A1(PUBLIC, void, ROMlib_eventinit, boolean_t, graphics_valid_p)	/* INTERNAL */
{
    static int beenhere = 0;
    EvQEl *p, *ep;

    if (!beenhere)
      {
	MouseLocation.h = 0;
	MouseLocation.v = 0;
	MouseLocation2.h = 0;
	MouseLocation2.v = 0;
	ScrDmpEnb = TRUE;
	evs[0].qLink = 0;	/* end of the line */
	beenhere = 1;
	for (p = evs+1, ep = evs+NEVENT; p != ep; p++)
	  p->qLink = RM((QElemPtr) (p-1));
	SysEvtMask = CWC(~(1L<< keyUp)); /* EVERYTHING except keyUp */
#if defined (NEXT)
	ROMlib_started = 3;
#endif
	if (graphics_valid_p)
	  {
	    Rect *main_gd_bounds;
	    
	    main_gd_bounds = &GD_BOUNDS (MR (MainDevice));
#if defined (MSDOS)
	    init_dos_events (CW (main_gd_bounds->right),
			     CW (main_gd_bounds->bottom));
#elif defined (EVENT_SVGALIB)
	    if (!event_init (CW (main_gd_bounds->right),
			     CW (main_gd_bounds->bottom)))
	      {
		fprintf (stderr,
		  "Unable to initialize svgalib events.\n"
		  "Make sure that executor-svga is installed setuid root,\n"
		  "that libvga.config has your mouse properly identified\n"
		  "and that /dev/mouse is a symlink to the right place\n");
		exit (-1);
	      }
#endif
	  }
      }
}

A1(PRIVATE, void, dropevent, EvQEl *, qp)
{
    Dequeue((QElemPtr) qp, &EventQueue);
    qp->qLink = RM((QElemPtr) freeelem);
    freeelem = qp;
    nevent--;
}

EvQEl *
geteventelem (void)
{
    EvQEl *retval = freeelem;

    if (nevent == NEVENT) {
	dropevent((EvQEl *) MR(EventQueue.qHead));
	retval = freeelem;
    }
    freeelem = (EvQEl *) MR(freeelem->qLink);
    nevent++;
    return retval;
}

PUBLIC boolean_t
ROMlib_get_index_and_bit (LONGINT loc, int *indexp, uint8 *bitp)
{
  boolean_t retval;

  if (loc < 0 || loc / 8 >= sizeof_KeyMap)
    retval = FALSE;
  else
    {
      retval = TRUE;
      *indexp = loc / 8;
      *bitp = (1 << (loc % 8));
    }
  return retval;
}

A2(PUBLIC, void, ROMlib_zapmap, LONGINT, loc, LONGINT, val)
{
  int i;
  uint8 bit;

  if (ROMlib_get_index_and_bit (loc, &i, &bit))
    {
      if (val)
	KeyMap[i] |= bit;
      else
	KeyMap[i] &= ~(bit);
    }
}

PRIVATE boolean_t
key_down (uint8 loc)
{
  boolean_t retval;
  int i;
  uint8 bit;

  if (!ROMlib_get_index_and_bit (loc, &i, &bit))
    retval = FALSE;
  else
    retval = !!(KeyMap[i] & bit);
  return retval;
}

A3(PUBLIC trap, OSErrRET, PPostEvent, INTEGER, evcode,		/* IMIV-85 */
				      LONGINT, evmsg, HIDDEN_EvQElPtr *, qelp)
{
    EvQEl *qp;
    LONGINT tmpticks;

    /*
     * Here is where the portable autokey stuff should go
     * If it is a keyUp event, clear autoticks, if it is
     * a keyDown then set the appropriate bugger ...
     *
     * Now that the portable code is here, SCO stuff is out of date
     */

    /*
     * NOTE:  The code below isn't strictly correct, since IMI-260 says
     * you can have at most 2 non modifier keys down at one time.
     */

    tmpticks = TickCount();
    if (evcode == keyUp) {
	ROMlib_zapmap((evmsg >> 8) & 0xFF, 0);
	if (!(evmsg & 0xff))
	  {
	    if (qelp)
	      qelp->p = 0;
	    return noErr;
	  }
	lastdown = -1;
    } else if (evcode == keyDown) {
	ROMlib_zapmap((evmsg >> 8) & 0xFF, 1);
	if (!(evmsg & 0xff))
	  {
	    if (qelp)
	      qelp->p = 0;
	    return noErr;
	  }
	lastdown = evmsg;
	autoticks = tmpticks + Cx(KeyThresh);

	if ((evmsg & 0xff) == '2' && /* cmd-shift-2 */
	    key_down (MKV_CLOVER) &&
	    (key_down (MKV_LEFTSHIFT) || key_down (MKV_RIGHTSHIFT)))
	
	  dofloppymount();
    }

    if (!((1 << evcode)&Cx(SysEvtMask)))
/*-->*/	return evtNotEnb;
    qp = geteventelem();
    qp->evtQWhat      = CW(evcode);
    qp->evtQMessage   = CL(evmsg);
    qp->evtQWhen      = CL(tmpticks);
    qp->evtQWhere     = ROMlib_curs;
    qp->evtQModifiers = CW(ROMlib_mods);
    Enqueue((QElemPtr) qp, &EventQueue);
    if (qelp)
	(*qelp).p = qp;
    return noErr;
}

A3(PRIVATE, OSErrRET, _PPostEvent, INTEGER, evcode,
				      LONGINT, evmsg, HIDDEN_EvQElPtr *, qelpp)
{
    OSErrRET ret;
    ProcPtr proc;
    HIDDEN_EvQElPtr retquelp;

    proc = (ProcPtr) ostraptable[0x2F];

#if 0	/* FIXME */
    if (proc == osstuff[0x2F].orig)
#endif
	ret = PPostEvent(evcode, evmsg, &retquelp);
#if 0
    else {
	EM_A0 = evcode;
	EM_D0 = evmsg;
	CALL_EMULATOR((syn68k_addr_t) proc);
	retquelp = EM_A0;
	ret = EM_D0;
    }
#endif

    if (qelpp)
	*qelpp = retquelp;
    return ret;
}

A6(PUBLIC, OSErrRET, ROMlib_PPostEvent, INTEGER, evcode, LONGINT, evmsg,
	HIDDEN_EvQElPtr *, qelp, LONGINT, when, Point, where, INTEGER, butmods)
{
    MouseLocation2.h = ROMlib_curs.h = CW(where.h);
    MouseLocation2.v = ROMlib_curs.v = CW(where.v);
    ROMlib_mods = butmods;

    return _PPostEvent(evcode, evmsg, qelp);
}

A2(PUBLIC trap, OSErrRET, PostEvent, INTEGER, evcode, LONGINT, evmsg)
{
    return _PPostEvent(evcode, evmsg, (HIDDEN_EvQElPtr *) 0);
}

A2(PUBLIC trap, void, FlushEvents, INTEGER, evmask,
					    INTEGER, stopmask)	/* II-69 */
{
    EvQEl *qp, *next;
    int x;
    virtual_int_state_t block;

    block = block_virtual_ints ();
    for (qp = (EvQEl *) MR(EventQueue.qHead);
	    qp && !((x=1<<Cx(qp->evtQWhat))&stopmask); qp = next) {
	next = (EvQEl *) MR(qp->qLink);	/* save before dropping event */
	if (x & evmask)
	    dropevent(qp);
    }
    restore_virtual_ints (block);
    /* NOTE:  According to IMII-69 we should be leaving stuff in d0 */
}

PUBLIC BOOLEAN ROMlib_bewaremovement;

PUBLIC int ROMlib_refresh = 0;


A3(PRIVATE, BOOLEAN, OSEventCommon, INTEGER, evmask, EventRecord *, eventp,
							    BOOLEAN, dropit)
{
    EvQEl *qp;
    virtual_int_state_t block;
    BOOLEAN retval;
    static Point oldpoint = { -1, -1 };
    LONGINT ticks;
#if !defined(MSDOS) && !defined(CYGWIN32)
    static LONGINT protector_ticks;
#endif

    /* We tend to call this routine from various ROMlib modal loops, so this
     * is a good place to check for timer interrupts, etc. */
    check_virtual_interrupt ();
    
    if (send_application_open_aevt_p
	&& application_accepts_open_app_aevt_p)
      {
	ProcessSerialNumber psn;
	OSErr err;
	
	GetCurrentProcess (&psn);
	
	{
	  AppleEvent *aevt = alloca (sizeof *aevt);
	  AEAddressDesc *target = alloca (sizeof *target);
	  
	  AEDescList *list = alloca (sizeof *list);
	  int16 count, dummy;
	  
	  err = AECreateDesc (typeProcessSerialNumber,
			      (Ptr) &psn, sizeof psn, target);
	  
	  CountAppFiles (&dummy, &count);
	  count = CW (count);
	  
	  if (count)
	    {
	      int i;
	      
	      err = AECreateAppleEvent (kCoreEventClass, kAEOpenDocuments,
					target,
					/* dummy */ -1, /* dummy */ -1,
					aevt);
	      err = AECreateList (NULL, 0, FALSE, list);
	      
	      for (i = 1; i <= count; i ++)
		{
		  FSSpec spec;
		  AppFile file;
		  
		  GetAppFiles (i, &file);
		  
#if 0
		  fprintf (stderr, "%d:`%s'\n",
			   i,
			   TEMP_C_STRING_FROM_STR255 (file.fName));
#endif
		  
		  FSMakeFSSpec (CW (file.vRefNum), 0, file.fName, &spec);
		  
		  AEPutPtr (list, i, typeFSS, (Ptr) &spec, sizeof spec);
		}
	      
	      AEPutKeyDesc (aevt, keyDirectObject, list);
	      
	      AESend (aevt,
		      /* dummy */ NULL,
		      kAENoReply,
		      /* dummy */ -1, /* dummy */ -1,
		      NULL, NULL);
	    }
	  else
	    {
	      err = AECreateAppleEvent (kCoreEventClass, kAEOpenApplication,
					target,
					/* dummy */ -1, /* dummy */ -1,
					aevt);
	      
	      AESend (aevt, /* dummy */ NULL,
		      kAENoReply, /* dummy */ -1,
		      /* dummy */ -1, NULL, NULL);
	    }
	  
	  send_application_open_aevt_p = FALSE;
	}
      }
    
    eventp->message = CLC(0);
#if defined (NEXTSTEP)
    contextswitch(&romlib_sp, &nextstep_sp);
#endif /* NEXTSTEP */
    ROMlib_memnomove_p = FALSE;	/* this is an icky hack needed for Excel */
    ticks = TickCount();

#if !defined(MSDOS) && !defined (CYGWIN32)
#define TICKS_PER_MINUTE (60 * 60)
    if (ticks - protector_ticks > 4 * TICKS_PER_MINUTE)
      {
	protectus(0, 0);
	protector_ticks = ticks;
      }
#endif

#if defined (TIME_OUT)
    {
      static char in_alert = FALSE;

      if (!ROMlib_info.serialnumber && ticks > TIME_OUT * 60 * 60 && !in_alert)
	{
	  in_alert = TRUE;
	
	  ROMlib_exit = TRUE;
	  system_error ("Time's up!", 0, "Exit", NULL, NULL,
			C_ExitToShell, NULL, NULL);
	}
    }
#endif


#if defined (X)
    /* if we are running on a version of linux that doesn't support
       SIGIO this will handle events (although not asynchronously) */
    
    if (x_event_pending_p ())
      post_pending_x_events (/* dummy */ -1, /* dummy */ NULL);

#endif /* X */

#if defined (SDL)
    /* if we are running SDL with the event thread disabled... */
    handle_sdl_events (/* dummy */ -1, /* dummy */ NULL);

#endif /* SDL */

#if defined(CYGWIN32) && !defined(SDL)
    /* Run the Win32 event loop, since currently we don't do this
       in a separate thread. */
    process_win32_events();

#endif /* CYGWIN32 */

    block = block_virtual_ints ();
    for (qp = (EvQEl *) MR(EventQueue.qHead); qp && !((1<<Cx(qp->evtQWhat))&evmask) ;
						    qp = (EvQEl *) MR(qp->qLink))
	;
    if (qp) {
	*eventp = *(EventRecord *)(&qp->evtQWhat);
	if (dropit) {
	    dropevent(qp);
	}
	retval = TRUE;
    } else {
	eventp->when      = CL(TickCount());

	{
#if defined(X) || defined(NEXTSTEP)
	    if (!ROMlib_pinned) {
	    	LONGINT x, y;
		LONGINT newmods;

		querypointerX(&x, &y, &newmods);
		eventp->where.h   = MouseLocation2.h = ROMlib_curs.h = CW(x);
		eventp->where.v   = MouseLocation2.v = ROMlib_curs.v = CW(y);
	    } else
#endif
		MouseLocation2 = eventp->where = ROMlib_curs;
	}

#if defined (MSDOS) || defined (EVENT_SVGALIB)
	{
	  LONGINT x, y;

	  querypointerX (&x, &y, NULL);
	  eventp->where.h = MouseLocation2.h = ROMlib_curs.h = CW (x);
	  eventp->where.v = MouseLocation2.v = ROMlib_curs.v = CW (y);
	}
#endif

	eventp->modifiers = CW(ROMlib_mods);
	if ((evmask & autoKeyMask) && lastdown != -1 && ticks > autoticks) {
	    autoticks = ticks + Cx(KeyRepThresh);
	    eventp->what = CWC(autoKey);
	    eventp->message = CL(lastdown);
	    retval = TRUE;
	} else {
	    eventp->what = CWC(nullEvent);
	    retval = FALSE;
	}
    }
    restore_virtual_ints (block);
    if (eventp->where.h != oldpoint.h || eventp->where.v != oldpoint.v) {
	oldpoint = eventp->where;
	if (ROMlib_bewaremovement) {
	    ROMlib_showhidecursor();
	    ROMlib_bewaremovement = FALSE;
	}
    }
#if defined(NEXTSTEP)
    if (ROMlib_printtimeout < 0) {	/* see MacViewClass.m */
	dirty_rect_update_screen ();
	ROMlib_printtimeout = 1;
    } else
#endif
    if (ROMlib_when == WriteInOSEvent) {
	dirty_rect_update_screen ();
    } else if (ROMlib_when == WriteAtEndOfTrap) {
	dirty_rect_update_screen ();
    }

    return retval;
}


A2(PUBLIC trap, BOOLEANRET, GetOSEvent, INTEGER, evmask, EventRecord *, eventp)
{
    return OSEventCommon(evmask, eventp, TRUE);
}

A2(PUBLIC trap, BOOLEANRET, OSEventAvail, INTEGER, evmask,
							 EventRecord *, eventp)
{
    return OSEventCommon(evmask, eventp, FALSE);
}

A1(PUBLIC trap, void, SetEventMask, INTEGER, evmask)
{
    SysEvtMask = CW(evmask);
}

A0(PUBLIC, QHdrPtr, GetEvQHdr)
{
    return &EventQueue;
}

PUBLIC void
post_keytrans_key_events (INTEGER evcode, LONGINT keywhat, int32 when,
			  Point where, uint16 button_state, unsigned char virt)
{
  INTEGER first_key, second_key;

  first_key = keywhat >> 16;
  second_key = keywhat;

  if (first_key)
    {
      ROMlib_PPostEvent (evcode, (virt << 8) | first_key, 0, when, where,
			 button_state);
      if (second_key)
	ROMlib_PPostEvent (keyUp, (virt << 8) | first_key, 0, when, where,
			   button_state);
    }
  if (second_key || !first_key)
    ROMlib_PPostEvent (evcode, (virt << 8) | second_key, 0, when, where,
		       button_state);
}

PRIVATE int
compare (const void *p1, const void *p2)
{
  int retval;

  retval = ROMlib_strcmp (p1, p2);
  return retval;
}

PUBLIC void
display_keyboard_choices (void)
{
  INTEGER nres, i, nfound;
  unsigned char (*names)[256];

  vdriver_shutdown ();
  printf ("Available keyboard maps:\n");
  SetResLoad (FALSE);
  nres = CountResources (TICK ("KCHR"));
  names = alloca (nres * sizeof (*names));
  nfound = 0;
  for (i = 1; i <= nres; ++i)
    {
      Handle h;
      
      h = GetIndResource (TICK ("KCHR"), i);
      if (h)
	{
	  GetResInfo (h, 0, 0, (StringPtr) names[nfound]);
	  ++nfound;
	}
    }
  qsort (names, nfound, sizeof (names[0]), compare);

  for (i = 0; i < nfound; ++i)
    printf ("%.*s\n", names[i][0], (char *) &names[i][1]);

  exit (0);
}

PUBLIC void
maybe_wait_for_keyup (void)
{
#if defined (SDL) && defined (CYGWIN32)
  /* Run SDL's event processor so that any pending events get
     sent to us instead of the Win32 print stuff.  Specifically
     we don't want to lose the key-up if someone hit <CR> to
     choose the default button in a dialog.  Losing the key-up
     can cause all sorts of trouble. */
  while (lastdown != -1)
    handle_sdl_events (0, NULL);
#endif
}
