/*
 * Jeffrey Friedl
 * Omron Corporation			オムロン（株）
 * Nagaokakyoshi, Japan			〒617長岡京下海印寺
 *
 * jfriedl@nff.ncl.omron.co.jp
 *
 * This work is placed under the terms of the GNU General Purpose License
 * (the "GNU Copyleft").
 */
#include "config.h"

#if USE_LOCAL_INPUT

#include "system.h"
#if defined(_HAVE_SYS_FCNTL_H_)
# include <sys/fcntl.h>
#elif defined(_HAVE_FCNTL_H_)
# include <fcntl.h>
#endif

#include <errno.h>
#include <signal.h>
#include "assert.h"
#include "output.h"
#include "input.h"
extern int errno;


static unsigned char input_buffer[INPUT_BUF_SIZE];
#define eptr (&input_buffer[INPUT_BUF_SIZE])

static unsigned char *incoming = input_buffer;
static unsigned char *outgoing = input_buffer;

int preread_input_pending = 0;

void (*input_inactivity_function)() = 0;

#define have_preread_input() (outgoing < incoming)

static volatile int is_reading = 0;
static volatile int blocking = 1;
static volatile int alarm_triggered = 0;

#if defined(O_NDELAY)
# define DELAY (O_NDELAY)
#elif defined(FNDELAY)
# define DELAY (FNDELAY)
#else
# error "Ack, don't know how to fcntl(delay)"
#endif

static void set_blocking(int want_blocking)
{
    int flags;
    kibishii_assert(want_blocking != blocking);
    flags = fcntl(STDIN, F_GETFL, /*dummy*/0);
    if (flags < 0)
	die("[bad fcntl1: %n]\n");
    if (want_blocking)
	flags &= ~DELAY;
    else
	flags |= DELAY;
    if (fcntl(STDIN, F_SETFL, flags) < 0)
	die("[bad fcntl2: %n]\n");
    #ifdef KIBISHII_DEBUG
	flags = fcntl(STDIN, F_GETFL, /*dummy*/0);
	if (flags < 0)
	    die("[bad fcntl3: %n]\n");
	kibishii_assert(!(flags & DELAY) == want_blocking);
    #endif
    blocking = want_blocking;
}

/*
 * For outside use, to ensure we're in blocking mode.
 */
void ensure_blocking_input(void)
{
    if (!blocking)
	set_blocking(1);
}

static int alarm_server(int num)
{
    if (is_reading)
    {
	kibishii_assert(input_inactivity_function);
	alarm_triggered = 1;
	if (blocking)
	    set_blocking(0);
    }
    signal(SIGALRM, alarm_server); /* set again for later alarms */
    return 0; /* to make someone happy */
}

static void read_more_input(void)
{
    int ret;
    kibishii_assert(incoming >= input_buffer);
    kibishii_assert(outgoing >= input_buffer);
    kibishii_assert(incoming < eptr);
    kibishii_assert(outgoing <= incoming);

    /*
     * keep trying to read until we don't die due to an alarm.
     */
    alarm_triggered = 0;
    for (;;)
    {
	is_reading = 1;

     #define _DOREAD_ ret = read(STDIN, incoming, eptr - incoming)

        #ifndef EINTR
	    _DOREAD_;
        #else
	    /* read some input -- if we exit merely due to an interrupted
	       system call, just try again */
	    while (_DOREAD_, ret < 0 && errno == EINTR)
		;
        #endif
     #undef _DOREAD_

	is_reading = 0;
	if (ret >= 0 || !alarm_triggered)
	    break;

	kibishii_assert(input_inactivity_function);
	/* the alarm was triggered since this read_more_input called */
	(*input_inactivity_function)();

	/* input_inactivity_function could have deregistered itself */
	if (input_inactivity_function == 0) {
	    /* had de-registered itself.... can now go back to blocking */
	    if (!blocking)
		set_blocking(1);
	    alarm_triggered = 0;
	}
    }
    if (input_inactivity_function && !alarm_triggered)
	sleep(0); /* alarm was unused... unset it */

    if (ret > 0)
	incoming += ret;
    else if (ret < 0 && errno != EWOULDBLOCK)
    {
	int prev = output_pager_transparent(1);
	warn("[read error: %n]\n");
	if (prev == 0)
	    output_pager_transparent(0);
    } else if (ret == 0 && blocking) {
	*incoming++ = 0; /* Mmmm, must be EOF */
    }
    preread_input_pending = have_preread_input();
}


int __input_pending__(void)
{
    if (preread_input_pending)
	return 1;
    if (blocking)
	set_blocking(0);
    read_more_input();
    return preread_input_pending;
}


unsigned char next_raw_input_byte(void)
{
    unsigned char c;

    kibishii_assert(incoming >= input_buffer);
    kibishii_assert(outgoing >= input_buffer);
    kibishii_assert(incoming < eptr);
    kibishii_assert(outgoing <= incoming);
    kibishii_assert(have_preread_input() == preread_input_pending);

    if (!preread_input_pending)
    {
	/* had none there... wait for it */
	if (!blocking)
	    set_blocking(1);

	if (input_inactivity_function)
	{
	    static int alarm_server_registered = 0;
	    if (!alarm_server_registered) {
		signal(SIGALRM, alarm_server); /* reset */
		alarm_server_registered = 1;
	    }
	    alarm(2);
	}

	read_more_input();

	/* expect to have gotten something*/
	kibishii_assert(outgoing < incoming);
    }
    kibishii_assert(outgoing < incoming);

    c = *outgoing++;
    if (outgoing == incoming)
    {
	incoming = outgoing = input_buffer;
	preread_input_pending = 0;
    }
    kibishii_assert(outgoing <= incoming);
    return c;
}

unsigned char next_cooked_input_byte(void)
{
    unsigned char c;
    kibishii_assert(have_preread_input() == preread_input_pending);

    if (preread_input_pending)
	c = next_raw_input_byte();
    else {
	/* GRAB THE TTY */
	set_tty_state_to_cbreak();
	c = next_raw_input_byte();
	reset_tty_state();
    }
    return c;
}

int flush_pending_input(void)
{
    int had = input_pending();
    kibishii_assert(have_preread_input() == preread_input_pending);
    while (input_pending())
    {
	incoming = outgoing = input_buffer;
	preread_input_pending = 0;
    }
    return had;
}


#endif /* USE_LOCAL_INPUT */
