/* The event_stream interface for tty's.
   Copyright (C) 1994, 1995 Board of Trustees, University of Illinois

This file is part of XEmacs.

XEmacs is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

XEmacs is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with XEmacs; see the file COPYING.  If not, write to the Free
Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Synched up with: Not in FSF. */

#include <config.h>
#include "lisp.h"

#include "blocktype.h"
#include "device.h"
#include "device-tty.h"
#include "events.h"
#include "frame.h"
#include "process.h"
#include "redisplay.h" /* for asynch_device_change_pending */

#include "sysproc.h"
#include "syswait.h"
#include "systime.h"

/* Mask of bits indicating the descriptors that we wait for input on */
extern SELECT_TYPE input_wait_mask, process_only_mask, device_only_mask;

extern Lisp_Object Qdelete_device;

static struct event_stream *tty_event_stream;


/************************************************************************/
/*				timeout events				*/
/************************************************************************/

struct tty_timeout
{
  int id;
  EMACS_TIME time;
  struct tty_timeout *next;
};

/* The pending timers are stored in an ordered list, where the first timer
   on the list is the first one to fire.  Times recorded here are
   absolute, computed by adding the interval passed to
   emacs_tty_generate_wakeup() to the current time. */
static struct tty_timeout *tty_timer_queue;

static int tty_timeout_id_tick;

struct tty_timeout_blocktype
{
  Blocktype_declare (struct tty_timeout);
} *the_tty_timeout_blocktype;

static int
emacs_tty_add_timeout (unsigned int msec)
{
  struct tty_timeout *tm;
  struct tty_timeout *t, **tt;
  EMACS_TIME current_time;

  /* Allocate a new time struct. */

  tm = Blocktype_alloc (the_tty_timeout_blocktype);
  tm->next = NULL;
  tm->id = tty_timeout_id_tick++;
  EMACS_SET_SECS_USECS (tm->time, msec / 1000, (msec % 1000) * 1000);
  EMACS_GET_TIME (current_time);
  EMACS_ADD_TIME (tm->time, tm->time, current_time);

  /* Add it to the queue. */

  tt = &tty_timer_queue;
  t  = *tt;
  while (t && EMACS_TIME_EQUAL_OR_GREATER (tm->time, t->time))
    {
      tt = &t->next;
      t  = *tt;
    }
  tm->next = t;
  *tt = tm;

  return tm->id;
}

static void
emacs_tty_remove_timeout (int id)
{
  struct tty_timeout *t, *prev;
  
  /* find it */
  
  for (t = tty_timer_queue, prev = NULL; t && t->id != id; t = t->next)
    prev = t;
  
  if (!t)
    return; /* couldn't find it */

  if (!prev)
    tty_timer_queue = t->next;
  else prev->next = t->next;

  Blocktype_free (the_tty_timeout_blocktype, t);
}

static void
tty_timeout_to_emacs_event (struct Lisp_Event *emacs_event)
{
  struct tty_timeout *tm = tty_timer_queue;

  assert (tm);
  emacs_event->event_type = timeout_event;
  emacs_event->timestamp  = 0; /* #### */
  emacs_event->event.timeout.interval_id = tm->id;
  tty_timer_queue = tm->next;
  Blocktype_free (the_tty_timeout_blocktype, tm);
}



static int
emacs_tty_event_pending_p (int user_p)
{
  if (!user_p)
    {
      EMACS_TIME sometime;
      /* see if there's a pending timeout. */
      EMACS_GET_TIME (sometime);
      if (tty_timer_queue &&
	  EMACS_TIME_EQUAL_OR_GREATER (sometime, tty_timer_queue->time))
	return 1;
    }

  return poll_fds_for_input (user_p ? device_only_mask : input_wait_mask);
}

static struct device *
find_device_from_fd (int fd)
{
  Lisp_Object rest;

  DEVICE_LOOP (rest)
    {
      struct device *d;

      assert (DEVICEP (XCAR (rest)));
      d = XDEVICE (XCAR (rest));
      if (DEVICE_IS_TTY (d) && fileno (DEVICE_TTY_DATA (d)->infd) == fd)
	return d;
    }

  return 0;
}

int
read_event_from_tty_or_stream_desc (struct Lisp_Event *event,
				    struct device *d, int fd)
{
  unsigned char ch;
  int nread;

  nread = read (fd, &ch, 1);
  if (nread == 0)
    {
      Lisp_Object device;
      XSETDEVICE (device, d);
      /* deleting the device might not be safe right now ... */
      Fenqueue_eval_event (Qdelete_device, device);
      /* but we definitely need to unselect it to avoid infinite
	 loops reading EOF's */
      Fdevice_disable_input (device);
    }
  else if (nread > 0)
    {
      character_to_event (ch, event, d);
      event->channel = DEVICE_SELECTED_FRAME (d);
      return 1;
    }
  else
    {
      /* #### What to do if there's an error? */
    }
  return 0;
}

int
maybe_read_quit_event (struct Lisp_Event *event)
{
  /* A C-g that came from `quit-flag' will always come from the
     TTY attached to stdin.  If that doesn't exist, however,
     then the user manually set `quit-flag' or sent us a SIGINT,
     and we pretend the C-g came from the selected-device. */
  struct device *d = find_device_from_fd (0);
  if (!d)
    d = XDEVICE (Fselected_device ());

  if (!EQ (Vquit_flag, Qnil))
    {
      int ch = DEVICE_QUIT_CHAR (d);
      Vquit_flag = Qnil;
      character_to_event (ch, event, d);
      event->channel = DEVICE_SELECTED_FRAME (d);
      return 1;
    }
  return 0;
}

static void
emacs_tty_next_event (struct Lisp_Event *emacs_event)
{
  while (1)
    {
      int ndesc;
      int i;
      SELECT_TYPE temp_mask = input_wait_mask;
      EMACS_TIME time_to_block, *pointer_to_this;

      /* If C-g was pressed, treat it as a character to be read.
         Note that if C-g was pressed while we were in select(),
	 the SIGINT signal handler will be called and it will
	 set Vquit_flag; when it returns, the select() system call
	 will return a value less than zero (and set errno to
	 EINTR). */
      if (maybe_read_quit_event (emacs_event))
	return;

      if (!tty_timer_queue) /* no timer events; block indefinitely */
	pointer_to_this = 0;
      else
	{
	  EMACS_TIME current_time;

	  /* The time to block is the difference between the first
	     (earliest) timer on the queue and the current time.
	     If that is negative, then the timer will fire immediately
	     but we still have to call select(), with a zero-valued
	     timeout: user events must have precedence over timer events. */
	  EMACS_GET_TIME (current_time);
	  if (EMACS_TIME_GREATER (tty_timer_queue->time, current_time))
	    EMACS_SUB_TIME (time_to_block, tty_timer_queue->time,
			    current_time);
	  else
	    EMACS_SET_SECS_USECS (time_to_block, 0, 0);
	  pointer_to_this = &time_to_block;
	}

      if (asynch_device_change_pending)
        /* This will handle the device-change and redisplay so
           it's actually visible */
        redisplay ();

      ndesc = select (MAXDESC, &temp_mask, 0, 0, pointer_to_this);
      if (ndesc > 0)
	{
	  /* Look for a TTY event */
	  for (i = 0; i < MAXDESC; i++)
	    {
	      /* To avoid race conditions (among other things, an infinite
		 loop when called from Fdiscard_input()), we must return
		 user events ahead of process events. */
	      if (FD_ISSET (i, &temp_mask) && FD_ISSET (i, &device_only_mask))
		{
		  struct device *d = find_device_from_fd (i);
		  
		  assert (d);
		  if (read_event_from_tty_or_stream_desc (emacs_event, d, i))
		    return;
		}
	    }

	  /* Look for a process event */
	  for (i = 0; i < MAXDESC; i++)
	    {
	      if (FD_ISSET (i, &temp_mask) && FD_ISSET (i, &process_only_mask))
		{
		  Lisp_Object process;
		  struct Lisp_Process *p =
		    get_process_from_input_descriptor (i);
		  
		  assert (p);
		  XSETPROCESS (process, p);
		  emacs_event->event_type = process_event;
		  emacs_event->timestamp  = 0; /* #### */
		  emacs_event->event.process.process = process;
		  return;
		}
	    }
	}
      else if (ndesc == 0) /* timeout fired */
	{
	  tty_timeout_to_emacs_event (emacs_event);
	  return;
	}
    }
}


static void
emacs_tty_handle_magic_event (struct Lisp_Event *emacs_event)
{
  /* Nothing to do currently */
}


static void
emacs_tty_select_process (struct Lisp_Process *process)
{
  /* Nothing to do currently */
}

static void
emacs_tty_unselect_process (struct Lisp_Process *process)
{
  /* Nothing to do currently */
}

static void
emacs_tty_select_device (struct device *d)
{
  /* Nothing to do currently */
}

static void
emacs_tty_unselect_device (struct device *d)
{
  /* Nothing to do currently */
}


static void
emacs_tty_quit_p (void)
{
  /* Nothing to do currently because QUIT is handled through SIGINT.
     This could change. */
}


void
init_event_tty_late (void)
{
  event_stream = tty_event_stream;
}

void
syms_of_event_tty (void)
{
  tty_event_stream =
    (struct event_stream *) xmalloc (sizeof (struct event_stream));

  tty_event_stream->event_pending_p 	= emacs_tty_event_pending_p;
  tty_event_stream->next_event_cb	= emacs_tty_next_event;
  tty_event_stream->handle_magic_event_cb = emacs_tty_handle_magic_event;
  tty_event_stream->add_timeout_cb 	= emacs_tty_add_timeout;
  tty_event_stream->remove_timeout_cb 	= emacs_tty_remove_timeout;
  tty_event_stream->select_device_cb 	= emacs_tty_select_device;
  tty_event_stream->unselect_device_cb 	= emacs_tty_unselect_device;
  tty_event_stream->select_process_cb 	= emacs_tty_select_process;
  tty_event_stream->unselect_process_cb = emacs_tty_unselect_process;
  tty_event_stream->quit_p_cb		= emacs_tty_quit_p;

  FD_ZERO (&input_wait_mask);
  FD_ZERO (&process_only_mask);
  FD_ZERO (&device_only_mask);

  the_tty_timeout_blocktype = Blocktype_new (struct tty_timeout_blocktype);
}
