/* libsizzle/fdport.c - file descriptor port handling
 
  Copyright (C) 1999, 2000 Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
 
  This 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.
  
  This software 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 this package; see the file COPYING.  If not, write to
  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  Boston, MA 02111-1307, USA.  */

#if HAVE_CONFIG_H
# include <config.h>
#endif 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#if HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#ifdef __MINGW32__
# include <windows.h>
# include <io.h>
#endif

#ifdef _AIX
# undef _NO_PROTO
# ifndef _USE_IRS
#  define _USE_IRS 1
# endif
# define _XOPEN_SOURCE_EXTENDED 1
# define _ALL_SOURCE 1
#if HAVE_STRINGS_H
# include <strings.h>
#endif
#endif

#if ENABLE_NLS
# include <libintl.h>
# define _(String) gettext(String)
#else
# define _(String) (String)
#endif

#include "alloc.h"
#include "eval.h"
#include "node.h"
#include "capi.h"
#include "port.h"
#include "fdport.h"

/* The port type identifier for file descriptor port objects.  */
zzz_object_t zzz_fdport_port_type;

/* File descriptor ports connected to the standard input/output/error
   handles.  */
zzz_scm_t zzz_stdin_fdport;
zzz_scm_t zzz_stdout_fdport;
zzz_scm_t zzz_stderr_fdport;

/* This is the size of the read/write buffers for file descriptor
   ports.  */
#define FDPORT_DEFAULT_BUFSIZE 2048

/* Forward declarations.  */
static void fdport_flush (zzz_scm_t port);
static void fdport_close (zzz_scm_t port);

static void
fdport_putc (int ch, zzz_scm_t port)
{
  if (fdport_wptr (port) >= fdport_wbuf (port) + fdport_wbufsize (port))
    {
      fdport_flush (port);
    }
  *fdport_wptr (port) = ch;
  fdport_wptr (port)++;
}

static int
fdport_getc (zzz_scm_t port)
{
  int ch;
  int count;

  if (fdport_rptr (port) >= fdport_rend (port))
    {
      /* If the input file is a tty, we print out a prompt before
         reading, so that the user sees when to input expressions.  */
      if (port_tty_p (port))
	{
	  fprintf (stdout, "zzz: ");
	  fflush (stdout);
	}
	  
/*        fprintf (stderr, "refilling input\n"); */
      count = read (fdport_fd (port), fdport_rbuf (port),
	    fdport_rbufsize (port));
      fdport_rptr (port) = fdport_rbuf (port);
      fdport_rend (port) = fdport_rbuf (port) + count;
      if (count == 0)
	{
/*  	  fprintf (stderr, "end of file in input\n"); */
	  return EOF;
	}
    }
  ch = *fdport_rptr (port);
/*    fprintf (stderr, "Reading `%c'\n", ch); */
  fdport_rptr (port)++;
  return ch;
}

static void
fdport_ungetc (int ch, zzz_scm_t port)
{
  if (fdport_rptr (port) > fdport_rbuf (port))
    {
      fdport_rptr (port)--;
      *fdport_rptr (port) = ch;
    }
}

static void
fdport_close (zzz_scm_t port)
{
  int fd = fdport_fd (port);

  if (fd != 0 && fd != 1 && fd != 2)
    close (fd);
}

/* FIXME: seek and tell do not work because of buffering.  */
static long
fdport_seek (zzz_scm_t port, long ofs, int whence)
{
  return lseek (fdport_fd (port), ofs, whence);
}

static long
fdport_tell (zzz_scm_t port)
{
  return lseek (fdport_fd (port), 0, SEEK_CUR);
}

static void
fdport_flush (zzz_scm_t port)
{
  write (fdport_fd (port), fdport_wbuf (port), 
	 fdport_wptr (port) - fdport_wbuf (port));
  fdport_wptr (port) = fdport_wbuf (port);
}

static int
fdport_char_ready_p (zzz_scm_t port)
{
#ifdef __MINGW32__

  HANDLE std = INVALID_HANDLE_VALUE;

  /* FIXME: This here works for pipe/file and std handles
     but not for anything else... if i knew about the argument
     being a socket we could select() it... */
  switch (fdport_fd (port))
    {
    case 0: std = GetStdHandle (STD_INPUT_HANDLE); break;
    case 1: std = GetStdHandle (STD_OUTPUT_HANDLE); break;
    case 2: std = GetStdHandle (STD_ERROR_HANDLE); break;
    default:
      std = (HANDLE) fdport_fd (port);
    }
  if (WaitForSingleObject (std, 0) == WAIT_OBJECT_0)
    return 1;
  
  return 0;

#else /* !__MINGW32__ */

  int fd = fdport_fd (port);
  int res;
  fd_set rfds;
  struct timeval timeout;

  if (fdport_rbuf (port) != NULL)
    {
      if (fdport_rptr (port) < fdport_rend (port))
	return 1;
    }
  timeout.tv_sec = 0;
  timeout.tv_usec = 0;
  FD_ZERO (&rfds);
  FD_SET (fd, &rfds);
  res = select (fd + 1, &rfds, NULL, NULL, &timeout);
  if (res <= 0)
    return 0;
  return FD_ISSET (fd, &rfds);
#endif /* !__MINGW32 */
}

static int
fdport_free (zzz_scm_t port)
{
  fdport_info_t info = port_info (port);
  if (info->rbuffer != NULL)
    zzz_free (info->rbuffer, info->rbuf_size);
  if (info->wbuffer != NULL)
    zzz_free (info->wbuffer, info->wbuf_size);
  zzz_free (info, sizeof (struct fdport_info));
  return 0;
}

zzz_scm_t
zzz_make_fdport (int fd, int read, int write)
{
  zzz_scm_t port;
  fdport_info_t info;

  info = zzz_malloc (sizeof (struct fdport_info));
  info->fd = fd;
  info->rbuf_size = FDPORT_DEFAULT_BUFSIZE;
  info->rbuffer = zzz_malloc (info->rbuf_size);
  info->rbuf_ptr = info->rbuffer;
  info->rbuf_end =  info->rbuffer;
  info->wbuf_size = FDPORT_DEFAULT_BUFSIZE;
  info->wbuffer = zzz_malloc (info->wbuf_size);
  info->wbuf_ptr = info->wbuffer;
  info->wbuf_end =  info->wbuffer;
  port = zzz_make_port (zzz_fdport_port_type, info);
  port_read_p (port) = read;
  port_write_p (port) = write;
  port_tty_p (port) = isatty (fd);
  return port;
}

result_t
zzz_init_fdport (void)
{
  /* Register the file descriptor port type.  */
  zzz_fdport_port_type = zzz_define_port_type (fdport_getc, fdport_ungetc,
					       fdport_putc, NULL,
					       fdport_flush, fdport_close,
					       fdport_seek, fdport_tell,
					       fdport_char_ready_p,
					       fdport_free, NULL);

  /* Create and initialize standard ports. */
  zzz_protect_global (&zzz_stdin_fdport);
  zzz_protect_global (&zzz_stdout_fdport);
  zzz_protect_global (&zzz_stderr_fdport);
  zzz_stdin_fdport = zzz_make_fdport (0, 1, 0);
  zzz_stdout_fdport = zzz_make_fdport (1, 0, 1);
  zzz_stderr_fdport = zzz_make_fdport (2, 0, 1);
  port_flags (zzz_stdin_fdport) |= PORT_FLAGS_LINEBUF;
  port_flags (zzz_stdout_fdport) |= PORT_FLAGS_LINEBUF;
  port_flags (zzz_stderr_fdport) |= PORT_FLAGS_LINEBUF;

  /*:doc
    standard-input-fdport
    Variable
    An fdport object connected to the standard input.
    doc:*/
  zzz_define_variable ("standard-input-fdport", zzz_stdin_fdport);
  /*:doc
    standard-output-fdport
    Variable
    An fdport object connected to the standard output.
    doc:*/
  zzz_define_variable ("standard-output-fdport", zzz_stdout_fdport);
  /*:doc
    standard-error-fdport
    Variable
    An fdport object connected to the standard error output.
    doc:*/
  zzz_define_variable ("standard-error-fdport", zzz_stderr_fdport);

  return RESULT_SUCCESS;
}

