
/*
 *  curses.c
 *
 *  Written by Max Khon <fjoe@iclub.nsu.ru> et al and released to the public
 *  domain.
 *
 *  Screen definitions & routines using [n]curses.
 */

#ifdef USE_CURSES

#include <sys/types.h>
#include <sys/time.h>
#include <ctype.h>
#include <ncurses.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "winsys.h"
#include "unused.h"
#include "keys.h"
#include "readtc.h"
#include "specch.h"

int color;
int vrow, vcol;
int cur_start = 0;
int cur_end = 0;
static unsigned char *allowed_special_characters = NULL;

#ifdef UNIX
volatile
#endif
TERM term =
{
    80,
    24,
    0
};

static int tcflags = 0;         /* what we want to extract from termcap */

#define EBUFSZ 100
static EVT EVent[EBUFSZ];	/* event circular queue */
static int ebufin = 0;		/* event in */
static int ebufout = 0;		/* event out */

static int kbhit(void);

static int norefresh = 0;

static void ttrefresh(void)
{
    if (!norefresh)
    {
        refresh();
    }
}

void TTBeginOutput(void)
{
    norefresh++;
}

void TTEndOutput(void)
{
    if (norefresh)
    {
        norefresh--;
        ttrefresh();
    }
}
    

/* This maps PC colors to monochrome attributes for w/o color support */
static int mono_colors[128]= 
{
    A_NORMAL,    A_NORMAL,    A_NORMAL,   A_NORMAL,
    A_NORMAL,    A_NORMAL,    A_NORMAL,   A_NORMAL,
    A_BOLD,      A_BOLD,      A_BOLD,     A_BOLD,
    A_BOLD,      A_BOLD,      A_BOLD,     A_BOLD,
    
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,  
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,
    
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,  
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,
    
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,  
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,
    
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,  
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,
    
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,  
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,
    
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,  
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,
    
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,  
    A_REVERSE,   A_REVERSE,   A_REVERSE,  A_REVERSE,
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  
    A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,  A_BOLD | A_REVERSE,
};

int TTScolor(unsigned int newcolor)
{
    int attr = 0;

    color = newcolor;
    if (!has_colors())
    {
        attr = mono_colors[color & 0x7F];
    }
    else
    {
	if (color & 0x08)
            attr |= A_DIM | A_BOLD;
	if (color & 0x80)
            attr |= A_BLINK;
	attr |= COLOR_PAIR(((color & 0x07) | ((color & 0x70) >> 1)));
    }

#ifdef A_ALTCHARSET
    if (color & F_ALTERNATE)
    {
        attr |= A_ALTCHARSET;
    }
#endif

    attrset(attr);
    bkgdset(attr & (~A_ALTCHARSET));
    
    return 1;
}

int TTCurSet(int st)
{
    if (st)
        curs_set(1);
    else
        curs_set(0);
    return 0;
}

int TTgotoxy(int row, int col)
{
    move(vrow = row, vcol = col);
    ttrefresh();
    return 1;
}

int TTgetxy(int* row, int* col)
{
    *row = vrow;
    *col = vcol;
    return 1;
}

int TTPutChr(unsigned int ch)
{
    move(vrow, vcol);
    addch(ch);
    ttrefresh();
    return 1;
}

int TTWriteStr(unsigned long *b, int len, int row, int col)
{
    int oldcol = color;
    int cut = 0;

    move(row, col);
    for (; len; len--, b++)
    {
        int ch = (int)  (*b & 0x000000ffUL);
        int col = (int)((*b & 0xffff0000UL) >> 16);
        int y, i;

        /* control chars are written in ^X notation */
        if (ch < ' ')
        {
            switch (ch)
            {
            case '\t': 
                getyx(stdscr, y, i);
                i = ((y >> 3) + 1) << 3;
                cut += i-y;
                len -= i-y;
                break;
			
            case '\n':
            case '\r':
                len += cut;
                cut = 0;
                break;

            case '\b':
                if (cut > 0)
                {
                    len++;
                    cut--;
                }
                break;
		
            default:
                len--;	
                cut++;
            }
        }

        if (color != col)
            TTScolor(col);
        addch(ch);
    }

    if (color != oldcol)
        TTScolor(oldcol);
    move(vrow, vcol);
    
    ttrefresh();
    
    return 1;
}

int TTStrWr(unsigned char *s, int row, int col, int len)
{
    int cut = 0;
    int y,i;
    
    if (s == NULL)
        return 1;

    if (len < 0)
        len = strlen((const char *)s);

    if (len == 0)
        return 1;
    
    move(row, col);
    for ( ; len; len--, s++)
    {
        /* control chars are written in ^X notation */
        if (*s < ' ')
        {
            switch (*s)
            {
            case '\t': 
                getyx(stdscr, y, i);
                i = ((y >> 3) + 1) << 3;
                cut += i-y;
                len -= i-y;
                break;
		
            case '\n':
            case '\r':
                len += cut;
                cut = 0;
                break;

            case '\b':
                if (cut > 0)
                {
                    len++;
                    cut--;
                }
                break;

            default:
                len--;	
                cut++;
            }
        }
        
        addch(*s);
    }
    
    move(vrow, vcol);
    ttrefresh();
    return 1;
}

int TTReadStr(unsigned long *b, int len, int row, int col)
{
    while(len--)
    {
        int ch;
        unsigned long cell;
        
        ch = mvinch(row, col);
        cell = ch & A_CHARTEXT;
        if (ch & (A_DIM | A_BOLD))
            cell |= 0x080000;
        if (ch & A_BLINK)
            cell |= 0x800000;
        if (ch & A_ALTCHARSET)
            cell |= (((unsigned long)F_ALTERNATE) << 16);
        cell |= (((ch & 0x0700) | ((ch & 0x7000) << 1))) << 8;
        
        *b++ = cell;
        col++;
    }
    move(vrow, vcol);
    ttrefresh();
    return 1;
}

int TTScroll(int x1, int y1, int x2, int y2, int lines, int dir)
{
#if 0
    /* 
     *  XXX  If you can make this work - mail me
     *  XXX  (Seems that TTClear doesn't work properly too)
     */
    int height = y2-y1+1;
    WINDOW* win;
    
    win = subwin(stdscr, height, x2-x1+1, y1, x1);
    if (win == NULL)
        return 0;
    scrollok(win, TRUE);
    wscrl(win, dir ? lines : -lines);
    touchline(stdscr, y1, height);
    wttrefresh(win);
    delwin(win);
#else
    int y;
    int width = x2 - x1 + 1;
    unsigned long* buf;
    
    if (lines <= 0)
        return 0;
    
    buf = malloc(width*sizeof(unsigned long));
    if (buf == NULL)
        return 0;
    
    if (dir)
    {
        for (y = y1+lines; y <= y2; y++)
        {
            TTReadStr(buf, width, y, x1);
            TTWriteStr(buf, width, y-lines, x1);
        }
        
        /* fill new lines with spaces */
        TTClear(x1, y2-lines+1, x2, y2);
    } 
    else
    {
        for (y = y2-lines; y >= y1; y--)
        {
            TTReadStr(buf, width, y, x1);
            TTWriteStr(buf, width, y+lines, x1);
        }
        
        /* fill new lines with spaces */
        TTClear(x1, y1, x2, y1+lines-1);
    }
    free(buf);
    move(vrow, vcol);
    ttrefresh();
#endif
    return 1;
}

int TTClear(int x1, int y1, int x2, int y2)
{
#if 0
    int height = y2 - y1 + 1;
    WINDOW* win;

    win = subwin(stdscr, height, x2 - x1 + 1, y1, x1);
    if (win == NULL)
        return 0;
    werase(win);
    touchline(stdscr, y1, height);
    wttrefresh(win);
    delwin(win);
#else
    int y;
    
    for (y = y1; y <= y2; y++)
    {
        int x;
        move(y, x1);
        for (x = x1; x <= x2; x++)
            addch(' ');
    }
    move(vrow, vcol);
    ttrefresh();
#endif
    return 1;
}

int TTEeol(void)
{
    move(vrow, vcol);
    clrtoeol();
    move(vrow, vcol);
    ttrefresh();
    return 1;
}

int TTdelay(int mil)
{
    unused(mil);
    return 0;
}

static unsigned meta_alphas[] =
{
    Key_A_A, Key_A_B, Key_A_C, Key_A_D, Key_A_E, Key_A_F, Key_A_G,
    Key_A_H, Key_A_I, Key_A_J, Key_A_K, Key_A_L, Key_A_M, Key_A_N,
    Key_A_O, Key_A_P, Key_A_Q, Key_A_R, Key_A_S, Key_A_T, Key_A_U,
    Key_A_V, Key_A_W, Key_A_X, Key_A_Y, Key_A_Z
};

static unsigned meta_digits[] =
{
    Key_A_0, Key_A_1, Key_A_2, Key_A_3, Key_A_4,
    Key_A_5, Key_A_6, Key_A_7, Key_A_8, Key_A_9
};

void TTSendMsg(int msg, int x, int y, int msgtype);

unsigned int TTGetKey(void)
{
    int ch;

    ch = getch();
    switch (ch)
    {
    case KEY_RESIZE:
        term.NRow = getmaxy(stdscr);
	term.NCol = getmaxx(stdscr);
        TTSendMsg(1, 0, 0, WND_WM_RESIZE);
	return -1;
    case KEY_LEFT:
        return Key_Lft;
    case KEY_RIGHT:
        return Key_Rgt;
    case KEY_UP:
        return Key_Up;
    case KEY_DOWN:
        return Key_Dwn;
    case KEY_BACKSPACE:
        return Key_BS;
    case KEY_NPAGE:
        return Key_PgDn;
    case KEY_PPAGE:
        return Key_PgUp;
    case KEY_HOME:
        return Key_Home;
    case KEY_END:
        return Key_End;
    case KEY_F(1):
        return Key_F1;
    case KEY_F(2):
        return Key_F2;
    case KEY_F(3):
        return Key_F3;
    case KEY_F(4):
        return Key_F4;
    case KEY_F(5):
        return Key_F5;
    case KEY_F(6):
        return Key_F6;
    case KEY_F(7):
        return Key_F7;
    case KEY_F(8):
        return Key_F8;
    case KEY_F(9):
        return Key_F9;
    case KEY_F(10):
        return Key_F10;
    case KEY_F(11):
        return 0x1b; /* DEC mode ... <grin> */
    case 0x1b:
        halfdelay(1);
        ch = getch();
        nocbreak();
        cbreak();
        switch (tolower(ch))
        {
        case ERR:
        case 0x1b:
            return 0x1b;
        case 'a':
            return Key_A_A;
        case 'b':
            return Key_A_B;
        case 'c':
            return Key_A_C;
        case 'd':
            return Key_A_D;
        case 'e':
            return Key_A_E;
        case 'f':
            return Key_A_F;
        case 'g':
            return Key_A_G;
        case 'h':
            return Key_A_H;
        case 'i':
            return Key_A_I;
        case 'j':
            return Key_A_J;
        case 'k':
            return Key_A_K;
        case 'l':
            return Key_A_L;
        case 'm':
            return Key_A_M;
        case 'n':
            return Key_A_N;
        case 'o':
            return Key_A_O;
        case 'p':
            return Key_A_P;
        case 'q':
            return Key_A_Q;
        case 'r':
            return Key_A_R;
        case 's':
            return Key_A_S;
        case 't':
            return Key_A_T;
        case 'u':
            return Key_A_U;
        case 'v':
            return Key_A_V;
        case 'w':
            return Key_A_W;
        case 'x':
            return Key_A_X;
        case 'y':
            return Key_A_Y;
        case 'z':
            return Key_A_Z;
        case '1':
            return Key_F1;
        case '2':
            return Key_F2;
        case '3':
            return Key_F3;
        case '4':
            return Key_F4;
        case '5':
            return Key_F5;
        case '6':
            return Key_F6;
        case '7':
            return Key_F7;
        case '8':
            return Key_F8;
        case '9':
            return Key_F9;
        case '0':
            return Key_F10;
        }
	break;
    }

    if (ch >= 127)    /* Treat special characters */
    {
        int assume_meta_key = 1;
 
                      /* if the character has not been explicitly */
                      /* enabled by the user, we check if it is a */
                      /* Meta keystroke.                          */
         
        if (allowed_special_characters != NULL)
        {
            if (strchr((char*)allowed_special_characters, ch) != NULL)
            {
                assume_meta_key = 0;
            }
        }

        if (assume_meta_key)
        {
            if (ch == 127)
            {
                if (wnd_bs_127)
                {
                    ch = Key_BS;
                }
                else
                {
                    ch = Key_Del;
                }
            }
            else if (isalpha(ch - 128))
            {
                ch = meta_alphas[tolower(ch - 128) - 'a'];
            }
            else if (isdigit(ch - 128))
            {
                ch = meta_digits[ch - 128 - '0'];
            }
        }
    }

    return ch;
}

void TTSendMsg(int msg, int x, int y, int msgtype)
{
    if (((ebufin + 1) % EBUFSZ) != ebufout)
    {
        EVent[ebufin].msg = msg;
        EVent[ebufin].x = x;
        EVent[ebufin].y = y;
        EVent[ebufin].msgtype = msgtype;
        ebufin = (ebufin + 1) % EBUFSZ;
    }
}

int TTkopen(void)
{
    nonl();
    noecho();
    cbreak();
    nodelay(stdscr, FALSE);
    keypad(stdscr, TRUE);
    meta(stdscr, TRUE);
    intrflush(stdscr, FALSE);
    raw();
    query_termcap(tcflags);
    return 0;
}

int TTkclose(void)
{
    return 0;
}

void MouseOFF(void)
{
}

void MouseON(void)
{
}

void MouseInit(void)
{
}

int GetMouInfo(int *x, int *y)
{
    unused(x);
    unused(y);
    return 0;
}

static void
collect_events(void)
{
    int ch = TTGetKey();
    if (ch < 0)
	return;

    TTSendMsg(ch, 0, 0, WND_WM_CHAR);
}

int TTGetMsg(EVT * e)
{
    while (ebufin == ebufout)
	collect_events();

    e->msg = EVent[ebufout].msg;
    e->x = EVent[ebufout].x;
    e->y = EVent[ebufout].y;
    e->msgtype = EVent[ebufout].msgtype;
    e->id = 0;
    ebufout = (ebufout + 1) % EBUFSZ;
    return e->msg;
}

int TTPeekQue(void)
{
    if (kbhit())
	collect_events();
    return ebufin != ebufout;
}

void TTClearQue(void)
{
    ebufin = ebufout;
}

int TTGetChr(void)
{
    EVT e;
    TTGetMsg(&e);
    return e.msg;
}

static char ansi2curses[8] = {
    COLOR_BLACK,
    COLOR_BLUE,
    COLOR_GREEN,
    COLOR_CYAN,
    COLOR_RED,
    COLOR_MAGENTA,
    COLOR_YELLOW,
    COLOR_WHITE
};

int TTopen(void)
{
    int i;
    
    initscr();
    
    color = 0x07;
    vrow = vcol = 0;
    term.NRow = getmaxy(stdscr);
    term.NCol = getmaxx(stdscr);
    
    if (has_colors())
    {
        start_color();
        
        for (i = 0; i < COLOR_PAIRS; i++)
            init_pair(i, ansi2curses[i & 0x07],
                      ansi2curses[(i & 0x38) >> 3]);
        init_color(COLOR_RED, 0, 0, 1000);
        init_color(COLOR_BLUE, 1000, 0, 0);
    }

    TTkopen();
    return 1;
}

/*
 * Configure the terminal. Configuration is retained even after TTclose.
 *
 * The ANSI/VT100 terminal accepts the following configuration keywords:
 *
 * keyword        possible values
 *
 * highascii      A string of high ascii bytes that, if read from the key-
 *                board shall be reported verbatim to the calling application
 *                instead of being interpreted as combination of the Meta key
 *                with a low ASCII key. You will need to enable high ascii
 *                alphabet characters (like umlauts or cyrillics) with this.
 *                An empty string is the deafault.
 *
 * pseudographics Not processed here - currently to psg support for curses.
 *
 */

int TTconfigure(const char *keyword, const char *value)
{
    size_t l;
    
    if (!strcmp(keyword,"highascii"))
    {
        if (allowed_special_characters != NULL)
        {
            free(allowed_special_characters);
        }
        allowed_special_characters =
            (unsigned char *) malloc(l = (strlen(value) + 1));
        memcpy(allowed_special_characters, value, l);
    }
    else if (!strcmp(keyword,"pseudographics"))
    {
        if (atoi(value))
        {
            tcflags |= QUERY_ALTCHARSET;
        }
        else
        {
            tcflags &= ~QUERY_ALTCHARSET;
        }
        query_termcap(tcflags);
    }
    else
    {
        return 0;
    }
    return 1;
}

int TTclose(void)
{
    TTkclose();
    endwin();
    return 1;
}

static int kbhit(void)
{
    fd_set select_set;
    struct timeval timeout;
    FD_ZERO(&select_set);
    FD_SET(0, &select_set);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    select(FD_SETSIZE, &select_set, 0, 0, &timeout);
    return FD_ISSET(0, &select_set);
}

int dv_running(void)
{
    return 0;
}

#endif
