/*
 * OS/2 VIO (character-mode) console routines.
 * Modified from a really old version of "borland.c" (before the VIO
 * stuff went in there.)
 *
 * $Header: /usr/build/vile/vile/RCS/os2vio.c,v 1.29 2001/06/13 09:57:31 tom Exp $
 */

#include "estruct.h"
#include "edef.h"

#if !SYS_OS2
#error Configuration error:  OS/2 is required for VIO support.
#endif

#define INCL_WIN
#define INCL_BASE
#define INCL_MOU
#define INCL_VIO
#define INCL_DOSPROCESS
#define INCL_NOPMAPI
#include <os2.h>

#include <conio.h>

#if DISP_VIO

#define SCROLLCODE        1

/* The code to control the cursor shape isn't complete.  No big loss. */
#define CURSOR_SHAPE 0

#define NROW	60		/* Max Screen size.             */
#define NCOL    80		/* Edit if you want to.         */
#define	NPAUSE	200		/* # times thru update to pause */
#define	SPACE	32		/* space character              */

#define	AttrColor(b, f)	((((UINT)ctrans[b] & 7) << 4) | ((UINT)ctrans[f] & 15))

int cfcolor = -1;		/* current foreground color */
int cbcolor = -1;		/* current background color */

static const char *initpalettestr = "0 4 2 6 1 5 3 7 8 12 10 14 9 13 11 15";

#include "os2keys.h"

#define	TEXT_BUFFER_SIZE		256

static BYTE BlankCell[2] =
{SPACE, 0};
static BYTE TextAttr;
static HMOU hmou;
static char TextBuf[TEXT_BUFFER_SIZE];
static char *TextOut;
static int CursorRow, CursorColumn;
static int MaxRows, MaxColumns;
static int TextFree;
static int TextRow, TextColumn;

#define blank_cell()	( BlankCell[1] = TextAttr, BlankCell )

#define flush_if_necessary() \
	{ if (TextOut > TextBuf) vio_flush(); }

static int
scinit(int rows)
{				/* initialize the screen head pointers */
    VIOMODEINFO vinfo;

    vinfo.cb = sizeof(vinfo);
    (void) VioGetMode(&vinfo, 0);

    MaxRows = vinfo.row;
    MaxColumns = vinfo.col;
    newscreensize(MaxRows, MaxColumns);

    return TRUE;
}

static void
vio_flush(void)
{
    int length = TextOut - TextBuf;

    if (length > 0) {
	(void) VioWrtCharStrAtt(TextBuf, length, TextRow, TextColumn,
				&TextAttr, 0);
	TextColumn += length;
    }
    TextOut = TextBuf;
    TextFree = TEXT_BUFFER_SIZE;

    /* Make the cursor 'catch up', if necessary. */
    if (TextRow >= MaxRows)
	TextRow = MaxRows - 1;
    if (TextColumn >= MaxColumns)
	TextColumn = MaxColumns - 1;
    if (CursorColumn != TextColumn || CursorRow != TextRow) {
	(void) VioSetCurPos(TextRow, TextColumn, 0);
	CursorRow = TextRow;
	CursorColumn = TextColumn;
    }
}

#if CURSOR_SHAPE
static void
set_cursor(int cmode)
{
    VIOCURSORINFO cinfo;

    cinfo.cx = 0;		/* Width of character cell */
    cinfo.cEnd = -100;		/* Cursor ends at bottom of cell */
    cinfo.attr = 0;		/* Visible cursor */

    switch (cmode) {
    case 0:
	/* Hidden cursor. */
	cinfo.attr = -1;
	break;

    case 1:
	/* Block cursor. */
	cinfo.yStart = 0;
	break;

    case 2:
	/* 'Normal' cursor. */
	cinfo.yStart = -75;
	break;
    }

    (void) VioSetCurType(&cinfo, 0);
}
#endif /* CURSOR_SHAPE */

#if OPT_COLOR
/*
 * Set the current foreground colour.  'color' specifies an ANSI colour
 * index.
 */
static void
vio_fcol(int color)
{
    if (color < 0)
	color = C_WHITE;
    /* Flush any old text that needs to be written using the old colour. */
    flush_if_necessary();

    cfcolor = color;
    TextAttr = AttrColor(cbcolor, cfcolor);
}

/*
 * Set the current background colour.  'color' specifies an ANSI colour
 * index.
 */
static void
vio_bcol(int color)
{
    if (color < 0)
	color = C_BLACK;
    /* Flush any old text that needs to be written using the old colour. */
    flush_if_necessary();

    cbcolor = color;
    TextAttr = AttrColor(cbcolor, cfcolor);
}
#else
#define	vio_fcol   nullterm_setfore
#define	vio_bcol   nullterm_setback
#define vio_spal   nullterm_setpal
#endif

static void
vio_move(int row, int col)
{
    flush_if_necessary();

    TextRow = row;
    TextColumn = col;
}

/* erase to the end of the line */
static void
vio_eeol(void)
{
    int length;

    flush_if_necessary();

    if ((length = MaxColumns - TextColumn) > 0) {
	(void) VioWrtNCell(blank_cell(), length, TextRow, TextColumn, 0);
    }
}

static int
decode_mouse_state(USHORT state)
{
    int button = 0;
    if ((state & MOUSE_MOTION_WITH_BN1_DOWN) != 0
	|| (state & MOUSE_BN1_DOWN) != 0) {
	button = 1;
    }
    if ((state & MOUSE_MOTION_WITH_BN2_DOWN) != 0
	|| (state & MOUSE_BN2_DOWN) != 0) {
	button = 2;
    }
    if ((state & MOUSE_MOTION_WITH_BN3_DOWN) != 0
	|| (state & MOUSE_BN3_DOWN) != 0) {
	button = 3;
    }
    return button;
}

/* If we have a mouse event, process it.  Otherwise, read a character.
 * If we do this, the DosSleep() used to limit CPU usage kills the keyboard
 * auto-repeat as a side-effect.  So we make the mouse code optional...
 */
static int
vio_getc(void)
{
    if (global_g_val(GMDMOUSE)) {
	MOUEVENTINFO mouev;
	USHORT nowait = MOU_NOWAIT;
	USHORT rc;
	int button = 0;

	do {
	    rc = MouReadEventQue(&mouev, &nowait, hmou);
	    if (rc == 0 && mouev.fs != 0) {
		if (button) {
		    int last = button;
		    button = decode_mouse_state(mouev.fs);
		    if (button) {
			if (last == button) {
			    if (!setcursor(mouev.row, mouev.col))
				continue;
			    if (!sel_extend(TRUE, TRUE))
				continue;
			} else {
			    sel_release();
			}
			(void) update(TRUE);
		    } else {
			sel_yank(0);
		    }
		} else {
		    button = decode_mouse_state(mouev.fs);
		    if (button == 1) {
			if (!setcursor(mouev.row, mouev.col))
			    continue;
			(void) sel_begin();
			(void) update(TRUE);
		    }
		}
	    } else {
		DosSleep(1);
	    }
	} while (!kbhit());
    }

    return getch();
}

/* put a character at the current position in the current colors */
static void
vio_putc(int ch)
{
    BYTE b;

    if (TextFree <= 0)
	vio_flush();

    /*
     * Control character kludges.  Sigh.
     */
    switch (ch) {
    case '\b':
	flush_if_necessary();
	TextColumn--;
	break;

    case '\n':
    case '\r':
	flush_if_necessary();
	b = ch;
	VioWrtTTY(&b, 1, 0);
	break;

    default:
	*TextOut++ = ch;
	TextFree--;
	break;
    }
}

static void
vio_eeop(void)
{
    int length;

    flush_if_necessary();

    length = (MaxRows * MaxColumns)
	- (TextRow * MaxColumns + TextColumn);
    if (length > 0)
	(void) VioWrtNCell(blank_cell(), length, TextRow, TextColumn, 0);
}

#if OPT_VIDEO_ATTRS
static void
vio_attr(UINT attr)
{
    attr = VATTRIB(attr);
    attr &= ~(VAML | VAMLFOC);

    flush_if_necessary();

#if OPT_COLOR
    if (attr & VACOLOR)
	vio_fcol(VCOLORNUM(attr));
    else
	vio_fcol(gfcolor);
#endif
    if (attr & (VAREV | VASEL))
	TextAttr = AttrColor(cfcolor, cbcolor);
    else
	TextAttr = AttrColor(cbcolor, cfcolor);

    if (attr & VABOLD)
	TextAttr |= 0x8;
}

#else /* highlighting is a minimum attribute */

/*
 * Choose the text attributes for reverse or normal video.  Reverse video
 * is selected if 'reverse' is TRUE, and normal video otherwise.
 */
static void
vio_rev(UINT reverse)
{
    flush_if_necessary();

    if (reverse)
	TextAttr = AttrColor(cfcolor, cbcolor);
    else
	TextAttr = AttrColor(cbcolor, cfcolor);
}
#endif /* OPT_VIDEO_ATTRS */

static int
vio_cres(const char *res)
{				/* change screen resolution */
    /* resolution to change to */
    return scinit(-1);
}

#if	OPT_FLASH
static void
flash_display(void)
{
    VIOMODEINFO data;
    UINT cellsize;
    BYTE *buf1;
    BYTE *buf2;
    USHORT got, length;

    data.cb = sizeof(data);
    VioGetMode(&data, 0);
    cellsize = 1 + data.attrib;

    buf1 = malloc(data.full_length);
    buf2 = malloc(data.full_length);

    length = data.full_length;
    VioReadCellStr(buf1, &length, 0, 0, 0);

    length = data.full_length;
    VioReadCellStr(buf2, &length, 0, 0, 0);

    for (got = 0; got < data.full_length; got += cellsize) {
	buf2[got + 1] ^= 8;
    }
    VioWrtCellStr(buf2, data.full_length, 0, 0, 0);
    DosSleep(200);
    VioWrtCellStr(buf1, data.full_length, 0, 0, 0);

    free(buf1);
    free(buf2);
}
#endif

static void
vio_beep()
{
#if	OPT_FLASH
    if (global_g_val(GMDFLASH)) {
	flash_display();
	return;
    }
#endif
    /* A nice, brief beep. */
    DosBeep(440, 50);
}

static void
vio_open(void)
{
    int i;

    /* Initialize output buffer. */
    TextRow = 0;
    TextColumn = 0;
    TextOut = TextBuf;
    TextFree = TEXT_BUFFER_SIZE;

    for (i = 0; i < sizeof(VIO_KeyMap) / sizeof(VIO_KeyMap[0]); i++) {
	addtosysmap(VIO_KeyMap[i].seq, 2, VIO_KeyMap[i].code);
    }

#if OPT_COLOR
    {
	VIOINTENSITY intense;
	intense.cb = sizeof(intense);
	intense.type = 2;
	intense.fs = 1;		/* ask for bright colors, not blink */
	VioSetState(&intense, 0);
    }
    set_palette(initpalettestr);
    vio_fcol(gfcolor);
    vio_bcol(gbcolor);
#endif

#if CURSOR_SHAPE
    set_cursor(1);
#endif

    if (!vio_cres(current_res_name))
	(void) scinit(-1);
    fprintf(stderr, "Press 'Q' to exit");
    fflush(stderr);
}

static void
vio_close(void)
{
#if CURSOR_SHAPE
    set_cursor(2);
#endif
    vio_move(MaxRows - 1, 0);
    term.eeop();
}

static void
vio_kopen()
{				/* open the keyboard */
    MouOpen(NULL, &hmou);
    MouDrawPtr(hmou);
    return;
}

static void
vio_kclose()
{				/* close the keyboard */
    MouClose(hmou);
    return;
}

#if SCROLLCODE
/*
 * Move 'n' lines starting at 'from' to 'to'
 *
 * OPT_PRETTIER_SCROLL is prettier but slower -- it scrolls a line at a time
 * instead of all at once.
 */

static void
vio_scroll(int from, int to, int n)
{
    /* Ignore single-line scrolling regions. */
    if (to == from)
	return;

#if OPT_PRETTIER_SCROLL
    /* Easier for the eye to follow, but slower. */
    if (absol(from - to) > 1) {
	vio_scroll(from, (from < to) ? to - 1 : to + 1, n);
	if (from < to)
	    from = to - 1;
	else
	    from = to + 1;
    }
#endif

    if (to < from) {
	(void) VioScrollUp(to, 0, from + n - 1, MaxColumns - 1,
			   from - to, blank_cell(), 0);
    } else {
	(void) VioScrollDn(from, 0, to + n - 1, MaxColumns - 1,
			   to - from, blank_cell(), 0);
    }
}

#else
#define	vio_scroll nullterm_scroll
#endif /* SCROLLCODE */

#if OPT_TITLE
/*
 * This sets the title in the window-list, but doesn't work for the
 * actual window title.  It's adapted from a standalone code example
 * that uses
 *	WinSetWindowText(hwndActive, swcntrl.szSwtitle);
 * before
 *	WinChangeSwitchEntry(hSwitch, &swcntrl);
 *
 * The standalone example is linked for PM versus this program's VIO mode.
 */
static void
vio_title(char *title)
{				/* set the current window title */
    HAB hab = WinInitialize(0);
    HMQ hmq = WinCreateMsgQueue(hab, 0);
    HWND hwndActive = WinQueryActiveWindow(HWND_DESKTOP);
    HWND hwndFrame = WinQueryWindow(hwndActive, QW_PARENT);
    char temp[24];
    BOOL ok;
    LONG nn;

    WinQuerySessionTitle(hab, 0, temp, sizeof(temp));

    if (strlen(title)) {
	HSWITCH hSwitch;
	SWCNTRL swcntrl;

	if ((hSwitch = WinQuerySwitchHandle(hwndActive, (PID) 0L)) != 0) {
	    if (!WinQuerySwitchEntry(hSwitch, &swcntrl)) {
		strncpy(swcntrl.szSwtitle, title, MAXNAMEL);
		swcntrl.szSwtitle[MAXNAMEL] = '\0';
#if 0
		WinSetWindowText(hwndActive, swcntrl.szSwtitle);
#endif
		WinChangeSwitchEntry(hSwitch, &swcntrl);
	    }
	}
    }

    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);
}
#endif /* OPT_TITLE */

/*
 * Standard terminal interface dispatch table. Most of the fields point into
 * "termio" code.
 */
TERM term =
{
    NROW - 1,
    NROW - 1,
    NCOL,
    NCOL,
    NPAUSE,
    vio_open,
    vio_close,
    vio_kopen,
    vio_kclose,
    vio_getc,
    vio_putc,
    tttypahead,
    vio_flush,
    vio_move,
    vio_eeol,
    vio_eeop,
    vio_beep,
#if OPT_VIDEO_ATTRS
    vio_attr,
#else
    vio_rev,
#endif				/* OPT_VIDEO_ATTRS */
    vio_cres,
    vio_fcol,
    vio_bcol,
    set_ctrans,
    nullterm_setccol,
    vio_scroll,
    nullterm_pflush,
    nullterm_icursor,
#if OPT_TITLE
    vio_title,
#else
    nullterm_settitle,
#endif
    nullterm_watchfd,
    nullterm_unwatchfd,
    nullterm_cursorvis,
};

#endif /* DISP_VIO */
