/*
 * 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, "cygtermd: ");
    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;
            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;
}