#ifdef linux
#include <ncurses.h>
#else
#include <curses.h>
#endif
#include <term.h>
#undef FALSE
#undef TRUE
  
#define SCREEN_INTERNAL
#include "hardware.h"
  
#include "proc.h"
#include "socket.h"
#undef tputs
  
#include "tty.h"
  
extern int Numrows, Numcols;
  
static WINDOW *curwin, *curss;
static int in_ss;
  
struct keytrie
{
    enum {KT_DEF, KT_TRIE, KT_VAL} kt_type;
    union
    {
        struct keytrie *ktu_trie;
#define kt_trie kt_u.ktu_trie
        int ktu_val;
#define kt_val kt_u.ktu_val
    } kt_u;
};
  
static struct keytrie *keys;
  
static void
key_init()
{
    int i;
  
    keys = mallocw(256 * sizeof *keys);
    for (i = 256; i--; )
    {
        keys[i].kt_type = KT_DEF;
        keys[i].kt_val = i;
    }
}
  
static void
key_add(str, val)
const char *str;
int val;
{
    const char *old;
    struct keytrie *t;
    int c, i;
  
    /*
     * Follow the trie until we get to the right subtrie for the string, then
     * add the value.  If we hit a KT_DEF, expand the trie.  If we hit a KT_VAL
     * we have a conflict; drop the key on the floor.  (We can't use timeouts.)
     * (Well, actually, we could, but I'll be darned if *I* want to write it!)
     */
    if (!str || !*str)
        return;         /* no key to define */
    old = str;
    t = keys;
    while (str[1])
    {
        c = uchar(*str++);
        if (t[c].kt_type == KT_DEF)
        {
            t[c].kt_type = KT_TRIE;
            t[c].kt_trie = mallocw(256 * sizeof (struct keytrie));
            for (i = 256; i--; )
            {
                t[c].kt_trie[i].kt_type = KT_DEF;
                t[c].kt_trie[i].kt_val = i;
            }
        }
        else if (t[c].kt_type != KT_TRIE)
            return;
        t = t[c].kt_trie;
    }
    c = uchar(*str);
    if (t[c].kt_type == KT_TRIE)
        return;
    t[c].kt_type = KT_VAL;
    t[c].kt_val = val;
}
  
void
ioinit()
{
    initscr();
    nonl();
    raw();
    noecho();
    Numrows = LINES;
    Numcols = COLS;
    init_sys();
    /* build a trie of function keys */
    key_init();
    key_add(key_down, DNARROW);
    key_add(key_f0, -3);
    key_add(key_f1, -4);
    key_add(key_f2, -5);
    key_add(key_f3, -6);
    key_add(key_f4, -7);
    key_add(key_f5, -8);
    key_add(key_f6, -9);
    key_add(key_f7, -10);
    key_add(key_f8, -11);
    key_add(key_f9, -2);    /* F10 traditionally is NOS escape */
    key_add(key_left, LTARROW);
    key_add(key_right, RTARROW);
    key_add(key_up, UPARROW);
    key_add("\177", '\b');  /* so DEL behaves as BS */
}
  
void
iostop()
{
    deinit_sys();
    endwin();
}
  
void
swapscreen(old, new)
struct session *old, *new;
{
    if (old == new)
        return;
    if (old != NULLSESSION && old->screen != NULLSCREEN)
        old->screen->in_ss = in_ss;
    if (new != NULLSESSION && new->screen != NULLSCREEN)
    {
        curwin = new->screen->win;
        clearok(curwin, TRUE);
        touchwin(curwin);
        wnoutrefresh(curwin);
        if (!new->split || !new->screen->sswin)
            curss = 0;
        else
        {
            curss = new->screen->sswin;
            touchwin(curss);
            wnoutrefresh(curss);
        }
        in_ss = new->screen->in_ss;
    }
}
  
static int
in_split(s)
struct session *s;
{
    return s->screen->in_ss;
}
  
void
putch(c)
int c;
{
    WINDOW *w;
    int x, y;
  
    if (in_ss && curss)
        w = curss;
    else
        w = curwin;
    if (c == 7)
        write(1, "\7", 1);
    else if (c == '\r' || c == '\t' || c == '\b')
        waddch(w, c);
    else if (c == '\n')
    {
    /* waddch() does clrtoeol() implicitly.  This breaks us. */
        getyx(w, y, x);
        if (++y > w->_regbottom)
        {
            y--;
            scroll(w);
        }
        wmove(w, y, 0);
    }
    else if (c < 32)
        return;
    else if (c > 126)
        return;
    else
        waddch(w, c);
}
  
void
sputch(s, c)
struct session *s;
int c;
{
    WINDOW *w;
    int x, y;
  
    if (in_split(s) && s->screen->sswin)
        w = s->screen->sswin;
    else
        w = s->screen->win;
    if (c == 7)
        write(1, "\7", 1);
    else if (c == '\r' || c == '\t' || c == '\b')
        waddch(w, c);
    else if (c == '\n')
    {
    /* waddch() does clrtoeol() implicitly.  This breaks us. */
        getyx(w, y, x);
        if (++y > w->_regbottom)
        {
            y--;
            scroll(w);
        }
        wmove(w, y, 0);
    }
    else if (c < 32)
        return;
    else if (c > 126)
        return;
    else
        waddch(w, c);
}
  
void
cputs(s)
char *s;
{
    if (in_ss && curss)
        waddstr(curss, s);
    else
        waddstr(curwin, s);
}
  
void
scputs(ses, s)
struct session *ses;
char *s;
{
    if (in_split(ses) && ses->screen->sswin)
        waddstr(ses->screen->sswin, s);
    else
        waddstr(ses->screen->win, s);
}
  
void
clreol()
{
    if (in_ss && curss)
        wclrtoeol(curss);
    else
        wclrtoeol(curwin);
}
  
void
sclreol(s)
struct session *s;
{
    if (in_split(s) && s->screen->sswin)
        wclrtoeol(s->screen->sswin);
    else
        wclrtoeol(s->screen->win);
}
  
/*
 * The display process.  Under *ix, all output goes to curses windows; since
 * I/O is buffered until a wrefresh(), we don't need to limit ourselves to the
 * current window and block other windows' I/O.  (A window sleeping on a More?
 * is the exception; but it's blocking on the keyboard, not the display.)
 * This does mean that you might lose information if you don't have "more"
 * enabled on a window; I don't think this matters much.
 *
 * Note that the real work is done by rflush().  The Display process no longer
 * exists.
 */
  
void
rflush()
{
    struct session *sp;
    int i, c, cnt;
  
    for (sp = Sessions, i = 0; i < Nsessions; sp++, i++)
    {
        if (sp->type == FREE)
            continue;
        if (!sp->screen || !sp->screen->win) /* "shouldn't happen" */
            continue;
        if (sp->morewait == 1) /* More prompt, no key yet */
            continue;
        if (sp->morewait == 2)
        {
            sp->morewait = 0;
            sputch(sp, '\r');
            sclreol(sp);
        }
        cnt = 0;
        while (socklen(sp->output, 0) > 0)
        {
            if ((c = rrecvchar(sp->output)) == -1)
                continue;
            cnt++;
            if (!sp->split || c != 0x0a)
                sputch(sp, c);
            else
            {
                scputs(sp, Eol);
                sclreol(sp);
            }
            if (sp->record != NULLFILE)
            {
                if (c == '\r' || c == '\n')
                    fflush(sp->record);
                if (c != '\r' || sockmode(sp->output, -1) != SOCK_ASCII)
                    putc(c, sp->record);
            }
            if (sp->flowmode && c == '\n' && --sp->row <= 0)
            {
                scputs(sp, "--More--");
                sp->morewait = 1;
                break;
            }
        }
    }
    if (curwin)
        wnoutrefresh(curwin);
    if (curss)
        wnoutrefresh(curss);
    doupdate();
}
  
void
j_newscreen(sp)
struct session *sp;
{
    if (sp == NULLSESSION)
        return;
    sp->screen = mallocw(sizeof *sp->screen);
    curwin = sp->screen->win = newwin(LINES - (sp->split? 2: 0), COLS, 0, 0);
    scrollok(curwin, TRUE);
    in_ss = 0;
    sp->screen->in_ss = 0;
    if (sp->split)
    {
        sp->screen->sswin = newwin(2, COLS, LINES - 2, 0);
        curss = sp->screen->sswin;
        scrollok(curss, TRUE);
    }
    else
    {
        sp->screen->sswin = 0;
        curss = 0;
    }
}
  
void
freescreen(sp)
struct session *sp;
{
    if (sp == NULLSESSION || sp->screen == NULLSCREEN)
        return;
    delwin(sp->screen->win);
    if (sp->screen->sswin)
        delwin(sp->screen->sswin);
    j_free(sp->screen);
}
  
/* Now we come to the emulation of the PC console as seen by Turbo C (bleah) */
  
void
clrscr()
{
    wclear(curwin);
    if (curss)
        werase(curss);
}
  
int
wherex()
{
    int x, y;
  
    if (in_ss && curss)
        getyx(curss, y, x);
    else
        getyx(curwin, y, x);
    return x + 1;
}
  
int
wherey()
{
    int x, y;
  
    if (in_ss && curss)
    {
        getyx(curss, y, x);
        y += LINES - 2;
    }
    else
        getyx(curwin, y, x);
    return y + 1;
}
  
void
window(x, y, w, h)
int x, y, w, h;
{
    in_ss = (y == LINES - 1);
}
  
void
gotoxy(x, y)
int x, y;
{
    if (in_ss && curss)
        wmove(curss, y - LINES + 1, x - 1);
    else
        wmove(curwin, y - 1, x - 1);
}
  
void
highvideo()
{
    if (in_ss && curss)
        wstandout(curss);
    else
        wstandout(curwin);
}
  
void
normvideo()
{
    if (in_ss && curss)
        wstandend(curss);
    else
        wstandend(curwin);
}
  
void
_setcursortype(t)
int t;
{
    curs_set(t);
}
  
/*
 * Read from the keyboard, "blocking" via the NOS scheduler.
 * We handle function keys ourselves:  curses wants to use alarms to do it,
 * which is somewhat dangerous in this case because it leaves us waiting on
 * a tick from a nonexistent timer (curses doesn't bother checking to see if
 * someone's got an alarm pending).
 */
  
static int
kbchar()
{
    extern int Keyboard;
    unsigned char ch;
    int i;
  
    do
    {
        pwait(&Keyboard);
    }
    while ((i = read(0, &ch, 1)) == 0 || (i == -1 && errno == EWOULDBLOCK));
    if (i < 0)
    {
        tprintf("NOS PANIC: Lost keyboard\n");
        where_outta_here(1,0);
    }
    return ch;
}
  
int
kbread()
{
    static int ungets[10];
    struct keytrie *t;
    static int unget;
    int ungetc[10];
    int c, i, u;
  
    i = 0;
    u = 0;
    if (unget > i)
        c = ungets[i++];
    else
        c = kbchar();
    ungetc[u++] = c;
    t = keys;
    while (t[c].kt_type == KT_TRIE)
    {
        t = t[c].kt_trie;
        if (unget > i)
            c = ungets[i++];
        else
            c = kbchar();
        ungetc[u++] = c;
    }
    if (t[c].kt_type == KT_VAL)
    {
        u = 0;
        c = t[c].kt_val;
    }
    while (i < unget)
        ungetc[u++] = ungets[i++];
    if (u)
    {
        c = ungetc[0];
        for (i = u; i; i--)
            ungets[i - 1] = ungetc[i];
        unget = u - 1;
    }
    return c;
}
