/*
    Copyright (C) 1995-1997
        Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld
        email: rainer@mathematik.uni-bielefeld.de

    All rights reserved
*/

#include <rsxnt.h>

#define CHARLOWER(arg) (char)(int)CharLower((char *)(int)(arg))

/*
 *  Sourcecode based on Linux v1.1
 *
 *  linux/kernel/tty_io.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
*/

#define _O_FLAG(tty,X)  ((tty)->termios->c_oflag & (unsigned long)(X))
#define _C_FLAG(tty,X)  ((tty)->termios->c_cflag & (unsigned long)(X))
#define _L_FLAG(tty,X)  ((tty)->termios->c_lflag & (unsigned long)(X))
#define _I_FLAG(tty,X)  ((tty)->termios->c_iflag & (unsigned long)(X))

#define L_ISIG(tty)     _L_FLAG((tty),ISIG)
#define L_ICANON(tty)   _L_FLAG((tty),ICANON)
#define L_XCASE(tty)    _L_FLAG((tty),XCASE)
#define L_ECHO(tty)     _L_FLAG((tty),ECHO)
#define L_ECHOE(tty)    _L_FLAG((tty),ECHOE)
#define L_ECHOK(tty)    _L_FLAG((tty),ECHOK)
#define L_ECHONL(tty)   _L_FLAG((tty),ECHONL)
#define L_NOFLSH(tty)   _L_FLAG((tty),NOFLSH)
#define L_IDEFAULT      _L_FLAG((tty),IDEFAULT)

#define I_IUCLC(tty)    _I_FLAG((tty),IUCLC)
#define I_INLCR(tty)    _I_FLAG((tty),INLCR)
#define I_ICRNL(tty)    _I_FLAG((tty),ICRNL)
#define I_IGNCR(tty)    _I_FLAG((tty),IGNCR)
#define I_IXON(tty)     _I_FLAG((tty),IXON)
#define I_IXANY(tty)    _I_FLAG((tty),IXANY)
#define I_ISTRIP(tty)   _I_FLAG((tty),ISTRIP)
#define I_IDELETE(tty)  _I_FLAG((tty),IDELETE)

#define INTR_CHAR(tty)  ((tty)->termios->c_cc[VINTR])
#define QUIT_CHAR(tty)  ((tty)->termios->c_cc[VQUIT])
#define ERASE_CHAR(tty) ((tty)->termios->c_cc[VERASE])
#define KILL_CHAR(tty)  ((tty)->termios->c_cc[VKILL])
#define EOF_CHAR(tty)   ((tty)->termios->c_cc[VEOF])
#define EOL_CHAR(tty)   ((tty)->termios->c_cc[VEOL])
#define __DISABLED_CHAR '\0'

#define INC(a) ((a) = ((a)+1) & (TTY_BUF_SIZE-1))
#define DEC(a) ((a) = ((a)-1) & (TTY_BUF_SIZE-1))
#define EMPTY(a) ((a)->head == (a)->tail)
#define LEFT(a) (((a)->tail-(a)->head-1)&(TTY_BUF_SIZE-1))
#define LAST(a) ((a)->buf[(TTY_BUF_SIZE-1)&((a)->head-1)])
#define FULL(a) (!LEFT(a))
#define CHARS(a) (((a)->head-(a)->tail)&(TTY_BUF_SIZE-1))

void _rsxnt_init_termio(EMXPROCESS *proc)
{
    memset (& proc->tty, 0, sizeof(struct tty_struct));
    proc->tty.termios = & proc->termio;

    proc->termio.c_iflag = BRKINT | ICRNL | IXON | IXANY;
    proc->termio.c_oflag = 0;
    proc->termio.c_cflag = B9600 | CS8 | CREAD | HUPCL;
    proc->termio.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | IDEFAULT;
    proc->termio.c_line = 0;
    proc->termio.c_cc[VINTR ] = 003;
    proc->termio.c_cc[VQUIT ] = 034;
    proc->termio.c_cc[VERASE] = 010;
    proc->termio.c_cc[VKILL ] = 025;
    proc->termio.c_cc[VEOF  ] = 004;
    proc->termio.c_cc[VEOL  ] = 000;
    proc->termio.c_cc[VMIN  ] = 006;
    proc->termio.c_cc[VTIME ] = 001;
}

/* read a key ; extended = 0,next call scan-code */
static int keyboard_read(struct tty_struct * tty)
{
    int key;

    key = __read_kbd(0, 0, 0);

    if (key == -1) {
        if (tty->timeout)
            if (tty->timeout < GetTickCount())
                tty->timeout = 0;
    }
    return key;
}

static int set_bit(int nr, void *vaddr)
{
    long mask;
    int retval;
    unsigned long *addr = vaddr;

    addr += nr >> 5;
    mask = 1L << (nr & 0x1f);
    retval = (mask & *addr) != 0;
    *addr |= mask;
    return retval;
}

static int clear_bit(int nr, void *vaddr)
{
    long mask;
    int retval;
    unsigned long *addr = vaddr;

    addr += nr >> 5;
    mask = 1L << (nr & 0x1f);
    retval = (mask & *addr) != 0;
    *addr &= ~mask;
    return retval;
}

static void put_tty_queue(unsigned char c, struct tty_queue * queue)
{
    int head = (queue->head + 1) & (TTY_BUF_SIZE - 1);

    if (head != queue->tail) {
        queue->buf[queue->head] = c;
        queue->head = head;
    }
}

#if 0
static int get_tty_queue(struct tty_queue * queue)
{
    int result = -1;

    if (queue->tail != queue->head) {
        result = queue->buf[queue->tail];
        INC(queue->tail);
    }
    return result;
}
#endif

static void flush_input(struct tty_queue * queue)
{
    FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));
    queue->head = queue->tail = 0;
}

#define TTY_READ_FLUSH(tty) copy_to_cooked(tty)

static int opost(unsigned char c, struct tty_struct * tty)
{
    static unsigned char next_line[2] = "\r\n";
    EMXPROCESS *proc = _rsxnt_get_process_ptr();
    DWORD n;
    HANDLE hOut = proc->file[1].f_handle;

    if ((c == 10) && (L_ECHO(tty) || (L_ICANON(tty) && L_ECHONL(tty))))
        WriteFile(hOut, next_line, 2, &n, NULL);
    else if (L_ECHO(tty))
        WriteFile(hOut, &c, 1, &n, NULL);
    return 1;
}

#define is_cntrl(c) ((c) < 0x20 || (c) == 0x7F)

static void echo_char(unsigned char c, struct tty_struct * tty)
{
    if (is_cntrl(c) && c != '\t') {
        opost('^', tty);
        opost((unsigned char)(c ^ 0100), tty);
    } else
        opost((unsigned char) c, tty);
}

static void eraser(unsigned char c, struct tty_struct * tty)
{
    enum {
        ERASE, WERASE, KILL
    } kill_type;
    int seen_alnums;

    if (tty->secondary.head == tty->canon_head) {
        /* opost('\a', tty); *//* what do you think? */
        return;
    }
    if (c == ERASE_CHAR(tty))
        kill_type = ERASE;
    else {
        if (!L_ECHO(tty)) {
            tty->secondary.head = tty->canon_head;
            return;
        }
        if (!L_ECHOK(tty)) {
            tty->secondary.head = tty->canon_head;
            if (tty->erasing) {
                opost('/', tty);
                tty->erasing = 0;
            }
            echo_char(KILL_CHAR(tty), tty);
            /* Add a newline if ECHOK is on */
            if (L_ECHOK(tty))
                opost('\n', tty);
            return;
        }
        kill_type = KILL;
    }

    seen_alnums = 0;
    while (tty->secondary.head != tty->canon_head) {
        c = LAST(&tty->secondary);
        DEC(tty->secondary.head);
        if (L_ECHO(tty)) {
            if (!L_ECHOE(tty)) {
                echo_char(ERASE_CHAR(tty), tty);
            }
        }
        if (kill_type == ERASE)
            break;
    }
    if (tty->erasing && tty->secondary.head == tty->canon_head) {
        opost('/', tty);
        tty->erasing = 0;
    }
}

static void copy_to_cooked(struct tty_struct * tty)
{
    int k;
    unsigned char c;

    for (;;) {
        if (! LEFT(&tty->secondary))
            break;

        k = keyboard_read(tty);
        if (k == -1)
            break;
        else
            c = (unsigned char) k;

        if (I_ISTRIP(tty))
            c &= 0x7f;

        if (!tty->lnext) {
            if (c == '\r') {
                if (I_IGNCR(tty))
                    continue;
                if (I_ICRNL(tty))
                    c = '\n';
            } else if (c == '\n' && I_INLCR(tty))
                c = '\r';
        }
        if (I_IUCLC(tty))
            c = CHARLOWER(c);

        if (c == __DISABLED_CHAR)
            tty->lnext = 1;
        if (L_ICANON(tty) && !tty->lnext) {
            if ((unsigned char)c == ERASE_CHAR(tty) ||
                 (unsigned char)c == KILL_CHAR(tty)) {
                eraser(c, tty);
                continue;
            }
        }

        if (L_ISIG(tty) && !tty->lnext) {
            EMXPROCESS *proc = _rsxnt_get_process_ptr();

            if ((unsigned char)c == INTR_CHAR(tty)) {
                _rsxnt_send_signal(proc, SIGINT);
                flush_input(&tty->secondary);
                return;
            }
            if ((unsigned char) c == QUIT_CHAR(tty)) {
                _rsxnt_send_signal(proc, SIGQUIT);
                flush_input(&tty->secondary);
                return;
            }
        }
        if (tty->erasing) {
            opost('/', tty);
            tty->erasing = 0;
        }
        if (c == '\n' && !tty->lnext) {
            if (L_ECHO(tty) || (L_ICANON(tty) && L_ECHONL(tty)))
                opost('\n', tty);
        } else if (L_ECHO(tty)) {
            /* Don't echo the EOF char in canonical mode.  Sun
               handles this differently by echoing the char and
               then backspacing, but that's a hack. */
            if ((unsigned char) c != EOF_CHAR(tty) || !L_ICANON(tty) ||
                tty->lnext) {
                echo_char(c, tty);
            }
        }
        if (c == (unsigned char) '\377' && (c != (unsigned char)EOF_CHAR(tty) || !L_ICANON(tty) || tty->lnext))
            put_tty_queue(c, &tty->secondary);

        if (L_ICANON(tty) && !tty->lnext &&
            (c == '\n' || c == (char)EOF_CHAR(tty) ||
            c == (char)EOL_CHAR(tty) )) {
            if (c == (char)EOF_CHAR(tty))
                c = __DISABLED_CHAR;
            set_bit(tty->secondary.head, &tty->secondary_flags);
            put_tty_queue(c, &tty->secondary);
            tty->canon_head = tty->secondary.head;
            tty->canon_data++;
        } else
            put_tty_queue(c, &tty->secondary);
        tty->lnext = 0;
    }
}

static int input_available_p(struct tty_struct * tty)
{
    /* Avoid calling TTY_READ_FLUSH unnecessarily. */
    if (L_ICANON(tty) ? tty->canon_data : !EMPTY(&tty->secondary))
        return 1;

    /* Shuffle any pending data down the queues. */
    TTY_READ_FLUSH(tty);

    if (L_ICANON(tty) ? tty->canon_data : !EMPTY(&tty->secondary))
        return 1;
    return 0;
}

static int read_chan(struct tty_struct * tty, void *buf, int nr)
{
    EMXPROCESS *proc = _rsxnt_get_process_ptr();
    int c;
    char *b = buf;
    int minimum, time;
    int retval = 0;
    unsigned long *timeout = & (proc->tty.timeout);

    if (L_ICANON(tty)) {
        minimum = time = 0;
        *timeout = 0xFFFFFFFF;
    } else {
        time = tty->termios->c_cc[VTIME] * 100;     /* millisec */
        minimum = tty->termios->c_cc[VMIN];
        if (minimum)
            *timeout = 0xffffffff;
        else {
            if (time)
                *timeout = time + GetTickCount(); /* millisec ?? */
            else
                *timeout = 0;
            time = 0;
            minimum = 1;
        }
    }

    for (;;) {
        if (!input_available_p(tty)) {
            if (!*timeout)
                break;
            if (proc->file[0].f_flags & O_NDELAY) {
                retval = -EAGAIN;
                break;
            }
            continue;
        }
        for (;;) {
            int eol;

            if (EMPTY(&tty->secondary))
                break;

            eol = clear_bit(tty->secondary.tail, &tty->secondary_flags);
            c = tty->secondary.buf[tty->secondary.tail];

            if (!nr) {
                /* Gobble up an immediately following EOF if
                   there is no more room in buf (this can
                   happen if the user "pushes" some characters
                   using ^D).  This prevents the next read()
                   from falsely returning EOF. */
                if (eol) {
                    if (c == __DISABLED_CHAR) {
                        tty->canon_data--;
                        INC(tty->secondary.tail);
                    } else {
                        set_bit(tty->secondary.tail,
                                &tty->secondary_flags);
                    }
                }
                break;
            }
            INC(tty->secondary.tail);
            if (eol) {
                if (--tty->canon_data < 0) {
                    MessageBox (NULL,
                        "internal error: read_chan: canon_data < 0",
                        "RSXNT", MB_OK|MB_ICONERROR|MB_APPLMODAL|MB_SETFOREGROUND);
                    tty->canon_data = 0;
                }
                if (c == __DISABLED_CHAR)
                    break;
                *b = c;
                b++;
                nr--;
                break;
            }
            *b++ = c;
            nr--;
        }
        if (((int)b - (int)buf >= minimum) || !nr)
            break;
        if (time)
            *timeout = time + GetTickCount();
    }
    *timeout = 0;
    return (((int)b - (int)buf) ? ((int)b - (int)buf) : retval);
}

/*
** ----------- public functions ----------------
*/

int _rsxnt_termio_read(EMXPROCESS *proc, void *buf, int count)
{
    DWORD mode;
    struct tty_struct *tty = & proc->tty;
    HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);

    /* check keyboard handle 0 */
    if (hInput == INVALID_HANDLE_VALUE)
        return -EBADF;
    else if (proc->file[0].f_handle != hInput)
        return -EBADF;
    else if (GetConsoleMode(hInput, &mode) == FALSE)
        return -EBADF;

    if (!tty)
        return -EIO;
    else
        return read_chan(tty, buf, count);
}

int _rsxnt_termio_ioctl(EMXPROCESS *proc, unsigned cmd, int arg)
{
    DWORD mode;
    struct tty_struct *tty = & proc->tty;
    int *termio_arg = (int *) arg;
    HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);

    /* check keyboard handle 0 */
    if (hInput == INVALID_HANDLE_VALUE)
        return -EBADF;
    else if (proc->file[0].f_handle != hInput)
        return -EBADF;
    else if (GetConsoleMode(hInput, &mode) == FALSE)
        return -EBADF;

    switch (cmd) {

        case TCGETA:
            memcpy(termio_arg, tty->termios, sizeof(struct termio));
            return 0;

        case TCSETAF:
            flush_input(&tty->secondary);
            /* fall through */

        case TCSETAW:
        case TCSETA:
            memcpy(tty->termios, termio_arg, sizeof(struct termio));
            // flush_input(&tty->secondary);
            if (!L_IDEFAULT)
                proc->p_flags |= PF_TERMIO;   /* enable termio */
            else {
                proc->p_flags &= ~PF_TERMIO;
                _rsxnt_init_termio(proc);
                flush_input(&tty->secondary);
            }
            return 0;

        case TCFLSH:
            /* if (termio_arg == 0) */
            flush_input(&tty->secondary);
            return 0;

        case FIONREAD:
            TTY_READ_FLUSH(tty);
            *termio_arg = (CHARS(&tty->secondary));
            return 0;

        case FGETHTYPE:
            *termio_arg = HT_DEV_CON;
            return 0;

        default:
            return -EINVAL;
    }
}
