/*
 * Main program.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "sel.h"
#include "pty.h"
#include "telnet.h"

int signalpipe[2];

sel *asel;
sel_rfd *netr, *ptyr, *sigr;
int ptyfd;
sel_wfd *netw, *ptyw;
Telnet telnet;

#define BUF 65536

void sigchld(int signum)
{
    write(signalpipe[1], "C", 1);
}

void fatal(const char *fmt, ...)
{
    va_list ap;
    fprintf(stderr, "FIXME: ");
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    exit(1);
}

void net_readdata(sel_rfd *rfd, void *data, size_t len)
{
    if (len == 0)
	exit(0);		       /* EOF on network - client went away */
    telnet_from_net(telnet, data, len);
    if (sel_write(netw, NULL, 0) > BUF)
	sel_rfd_freeze(ptyr);
    if (sel_write(ptyw, NULL, 0) > BUF)
	sel_rfd_freeze(netr);
}

void net_readerr(sel_rfd *rfd, int error)
{
    fprintf(stderr, "standard input: read: %s\n", strerror(errno));
    exit(1);
}

void net_written(sel_wfd *wfd, size_t bufsize)
{
    if (bufsize < BUF)
	sel_rfd_unfreeze(ptyr);
}

void net_writeerr(sel_wfd *wfd, int error)
{
    fprintf(stderr, "standard input: write: %s\n", strerror(errno));
    exit(1);
}

void pty_readdata(sel_rfd *rfd, void *data, size_t len)
{
    if (len == 0)
	exit(0);		       /* EOF on pty */
    telnet_from_pty(telnet, data, len);
    if (sel_write(netw, NULL, 0) > BUF)
	sel_rfd_freeze(ptyr);
    if (sel_write(ptyw, NULL, 0) > BUF)
	sel_rfd_freeze(netr);
}

void pty_readerr(sel_rfd *rfd, int error)
{
    if (error == EIO)		       /* means EOF, on a pty */
	exit(0);
    fprintf(stderr, "pty: read: %s\n", strerror(errno));
    exit(1);
}

void pty_written(sel_wfd *wfd, size_t bufsize)
{
    if (bufsize < BUF)
	sel_rfd_unfreeze(netr);
}

void pty_writeerr(sel_wfd *wfd, int error)
{
    fprintf(stderr, "pty: write: %s\n", strerror(errno));
    exit(1);
}

void sig_readdata(sel_rfd *rfd, void *data, size_t len)
{
    char *p = data;

    while (len > 0) {
	if (*p == 'C') {
	    int status;
	    pid_t pid = waitpid(-1, &status, WNOHANG);
	    if (WIFEXITED(status) || WIFSIGNALED(status))
		exit(0);	       /* child process vanished */
	}
    }
}

void sig_readerr(sel_rfd *rfd, int error)
{
    fprintf(stderr, "signal pipe: read: %s\n", strerror(errno));
    exit(1);
}

int main(int argc, char **argv)
{
    int ret;
    int shell_started = 0;
    char *directory = NULL;
    char **program_args = NULL;

    if (argc > 1 && argv[1][0]) {
        directory = argv[1];
        argc--, argv++;
    }
    if (argc > 1) {
        program_args = argv + 1;
    }

    pty_preinit();

    asel = sel_new(NULL);
    netr = sel_rfd_add(asel, 0, net_readdata, net_readerr, NULL);
    netw = sel_wfd_add(asel, 1, net_written, net_writeerr, NULL);
    ptyr = sel_rfd_add(asel, -1, pty_readdata, pty_readerr, NULL);
    ptyw = sel_wfd_add(asel, -1, pty_written, pty_writeerr, NULL);

    telnet = telnet_new(netw, ptyw);

    if (pipe(signalpipe) < 0) {
	perror("pipe");
	return 1;
    }
    sigr = sel_rfd_add(asel, signalpipe[0], sig_readdata,
		       sig_readerr, NULL);

    signal(SIGCHLD, sigchld);

    do {
	struct shell_data shdata;

	ret = sel_iterate(asel, -1);
	if (!shell_started && telnet_shell_ok(telnet, &shdata)) {
	    ptyfd = run_program_in_pty(&shdata, directory, program_args);
	    sel_rfd_setfd(ptyr, ptyfd);
	    sel_wfd_setfd(ptyw, ptyfd);
	    shell_started = 1;
	}
    } while (ret == 0);

    return 0;
}