/* 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 "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, non_fake_input_wait_mask;
extern SELECT_TYPE process_only_mask, device_only_mask;

extern Lisp_Object Qdelete_device;

static struct event_stream *tty_event_stream;

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

/* 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. */
static struct low_level_timeout *tty_timer_queue;

static int
emacs_tty_add_timeout (EMACS_TIME time)
{
  return add_low_level_timeout (&tty_timer_queue, time);
}

static void
emacs_tty_remove_timeout (int id)
{
  remove_low_level_timeout (&tty_timer_queue, id);
}

static void
tty_timeout_to_emacs_event (struct Lisp_Event *emacs_event)
{
  emacs_event->event_type = timeout_event;
  emacs_event->timestamp  = 0; /* #### */
  emacs_event->event.timeout.interval_id =
    pop_low_level_timeout (&tty_timer_queue, 0);
}



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 :
			     non_fake_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 `sigint_happened' will always come from the
     controlling terminal.  If that doesn't exist, however, then the
     user manually sent us a SIGINT, and we pretend the C-g came from
     the selected-device. */
  struct device *d;

  if (DEVICEP (Vcontrolling_terminal) &&
      DEVICE_LIVE_P (XDEVICE (Vcontrolling_terminal)))
    d = XDEVICE (Vcontrolling_terminal);
  else
    d = XDEVICE (Fselected_device ());

  if (sigint_happened)
    {
      int ch = DEVICE_QUIT_CHAR (d);
      sigint_happened = 0;
      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;
      EMACS_SELECT_TIME select_time_to_block, *pointer_to_this;

      if (!get_low_level_timeout_interval (tty_timer_queue, &time_to_block))
	/* no timer events; block indefinitely */
 	pointer_to_this = 0;
      else
	{
	  EMACS_TIME_TO_SELECT_TIME (time_to_block, select_time_to_block);
	  pointer_to_this = &select_time_to_block;
	}

      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;
		}
	    }

	  /* We might get here when a fake event came through a signal. */
	  /* Return a dummy event, so that a cycle of the command loop will
	     occur. */
	  drain_signal_event_pipe ();
	  emacs_event->event_type = eval_event;
	  emacs_event->event.eval.function = Qidentity;
	  emacs_event->event.eval.object = Qnil;
	  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. */
}


/************************************************************************/
/*                            initialization                            */
/************************************************************************/

void
vars_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;
}

void
init_event_tty_late (void)
{
  event_stream = tty_event_stream;
}
