/*
 * OpenComal -- a free Comal implementation
 *
 * This file is part of the OpenComal package.
 * (c) Copyright 1992-2024 Jos Visser <josvisser66@gmail.com>
 *
 * The OpenComal package is covered by the GNU General Public
 * License. See doc/LICENSE for more information.
 */

/* PDCOMAL SYS routines for MSDOS, Turbo C */

#include "pdcglob.h"
#include "pdcmisc.h"

#include <alloc.h>
#include <dos.h>
#include <mem.h>
#include <ctype.h>
#include <time.h>		/* For randomize() */
#include <math.h>		/* For matherr() */


#define NCOLS			80
#define NROWS			25

#define MONO			7
#define COLOUR			3

#define NORMAL			7
#define UNDERLINE		1
#define HI_UNDERLINE		9
#define HI			15
#define	REVERSE			0x70
#define BLINKING		0x87
#define BLINKING_HI		0x8f
#define BLINKING_REVERSE	0xf0

#define CTRL_C			3
#define ESCAPE			27
#define RETURN			13
#define BACKSPACE		8
#define INS			0x8052
#define DEL			0x8053
#define CSR_LEFT		0x804b
#define CSR_RIGHT		0x804d
#define END			0x804f
#define HOME			0x8047
#define CTRL_LEFT		0x8073
#define CTRL_RIGHT		0x8074

typedef struct {
    char kar;
    char attr;
} CELL;

typedef CELL SCREEN[NROWS][NCOLS];

PRIVATE SCREEN *video;
PRIVATE void interrupt(*old_cbreak_handler) ();
PRIVATE int monitor;
PRIVATE int escape = 0;
PRIVATE int curx, cury;

/* Disable stack overflow check & register allocation for interrupt func */

#pragma option -N- -r-

PRIVATE void interrupt cbreak()
{
    escape = 1;
}

#pragma option -N+ -r+


PRIVATE void set_textmode(mode)
int mode;
{
    union REGS r;

    r.h.al = mode;
    r.h.ah = 0;
    int86(0x10, &r, &r);

    r.x.cx = 0x001f;
    r.h.ah = 1;
    int86(0x10, &r, &r);	/* Set full block cursor */

    curx = cury = 0;
}


PRIVATE void scroll_up(no)
int no;
{
    int x, y;

    memmove(video, &(*video)[no][0],
	    sizeof(SCREEN) - no * NCOLS * sizeof(CELL));

    for (y = NROWS - no; y < NROWS; y++)
	for (x = 0; x < NCOLS; x++) {
	    (*video)[y][x].attr = 7;
	    (*video)[y][x].kar = ' ';
	}
}


PRIVATE void gotoxy(x, y)
{
    union REGS r;

    r.h.ah = 2;
    r.h.dh = y;
    r.h.dl = x;
    r.h.bh = 0;
    int86(0x10, &r, &r);
    curx = x;
    cury = y;
}


PRIVATE unsigned getkey()
{
    union REGS r;

    r.h.ah = 0;
    int86(0x16, &r, &r);

    if (r.h.al == 0)
	return 0x8000 + r.h.ah;

    return r.h.al;
}


PRIVATE void adjust(x, y)
int *x;
int *y;
{
    if (*x >= NCOLS) {
	*y = *y + *x / NCOLS;
	*x = *x % NCOLS;
    } else if (*x < 0) {
	*y = *y + *x / NCOLS - 1;
	*x = NCOLS - (-*x) % NCOLS;
    }
}


PRIVATE void beep()
{
    union REGS r;

    r.h.ah = 0x0e;
    r.h.al = 7;
    int86(0x10, &r, &r);
}


PUBLIC void sys_init()
{
    union REGS r;

    randomize();

    old_cbreak_handler = getvect(0x1b);
    setvect(0x1b, cbreak);

    int86(0x11, &r, &r);

    if (((r.x.ax & 0x30) >> 4) == 3)
	monitor = MONO;
    else
	monitor = COLOUR;

    if (monitor == COLOUR)
	video = MK_FP(0xb800, 0);
    else
	video = MK_FP(0xb000, 0);

    set_textmode(monitor);
    ext_init();
}


PUBLIC void sys_tini()
{
    setvect(0x1b, old_cbreak_handler);
    ext_tini();
}

#pragma exit sys_tini


PUBLIC double sys_rand(arg)
long arg;
{
    long n = rand();

    if (arg == 0)
	if (n == 0)
	    return 0L;
	else
	    return (double) n / RAND_MAX;

    return (n * (arg + 1)) / RAND_MAX;
}


PRIVATE void flush_kbd()
{
    int *biosdata = MK_FP(0x40, 0);

    *(biosdata + 0xd) = 0x1e;
    *(biosdata + 0xe) = 0x1e;
}


PUBLIC int sys_escape()
{
    if (escape) {
	escape = 0;
	flush_kbd();
	return 1;
    }

    return 0;
}


PUBLIC void sys_nl(stream)
int stream;
{
    curx = 0;
    cury++;

    if (cury == NROWS) {
	scroll_up(1);
	cury = NROWS - 1;
    }

    gotoxy(curx, cury);
}


PUBLIC void sys_put(stream, buf, len)
int stream;
char *buf;
long len;
{
    char attr = (stream == MSG_ERROR) ? REVERSE : NORMAL;
    CELL *p = &(*video)[cury][curx];

    while (*buf) {
	p->kar = *buf;
	p->attr = attr;
	buf++;
	p++;
	curx++;

	if (curx == NCOLS) {
	    curx = 0;
	    cury++;

	    if (cury == NROWS) {
		scroll_up(1);
		cury = NROWS - 1;
		p = &(*video)[NROWS - 1][0];
	    }
	}
    }

    gotoxy(curx, cury);
}


PUBLIC void sys_page(f)
FILE *f;
{
    if (!f)
	set_textmode(monitor);
    else
	fputc(12, f);
}


PUBLIC void sys_cursor(f, x, y)
FILE *f;
long x;
long y;
{
    if (f)
	return;

    y--;
    x--;

    if (y >= NROWS || y < 0 || x >= NCOLS || x < 0)
	run_error(CURSOR_ERR, "CURSOR x,y out of bounds");

    gotoxy((int) x, (int) y);
}


PUBLIC void sys_screen_readjust()
{
    union REGS r;

    r.h.ah = 3;
    r.h.bh = 0;			/* Video page */
    int86(0x10, &r, &r);
    curx = r.h.dl;
    cury = r.h.dh;
}


PUBLIC int sys_yn(stream, title)
int stream;
char *title;
{
    char s[2];

    sys_put(stream, title);
    s[1] = '\0';

    do {
	s[0] = getkey();

	if (sys_escape())
	    s[0] = 'N';

	if (islower(s[0]))
	    s[0] = toupper(s[0]);
    }
    while (s[0] != 'Y' && s[0] != 'N');

    sys_put(stream, s, -1L);
    sys_nl(stream);

    return s[0] == 'Y';
}


PRIVATE int do_get(stream, line, maxlen, prompt, cursor)
int stream;
char line[];
int maxlen;
char *prompt;
int cursor;
{
    int startx, starty;
    int endx, endy;
    int x, y;
    int len = strlen(line);
    unsigned c;
    int i;
    int insert = 0;
    CELL *cell;

    cursor--;			/* Computer scientists count from 0 onwards */
    sys_put(MSG_DIALOG, prompt, -1L);
    sys_put(MSG_DIALOG, line, (long) len);
    sys_put(MSG_DIALOG, " ", 1L);
    startx = curx - len - 1;
    starty = cury;
    adjust(&startx, &starty);

    if (cursor > len)
	cursor = len;

    y = starty;
    x = startx + cursor;

    while (1 == 1) {
	adjust(&x, &y);
	gotoxy(x, y);
	c = getkey();

	if (sys_escape())
	    c = ESCAPE;

	switch (c) {
	case RETURN:
	    sys_nl(stream);
	    return 0;

	case CTRL_C:
	case ESCAPE:
	    sys_nl(stream);
	    line[0] = '\0';
	    return 1;

	case HOME:
	    cursor = 0;
	    x = startx;
	    y = starty;
	    break;

	case END:
	    cursor = len;
	    x = startx + len;
	    y = starty;
	    break;

	case CSR_LEFT:
	    if (cursor > 0) {
		cursor--;
		x--;
	    } else
		beep();

	    break;

	case CSR_RIGHT:
	    if (cursor < len) {
		cursor++;
		x++;
	    } else
		beep();

	    break;

	case INS:
	    insert = !insert;
	    break;

	case BACKSPACE:
	    if (cursor <= 0) {
		beep();
		break;
	    }

	    cursor--;
	    x--;
	    /* Fall through */

	case DEL:
	    if (cursor < len) {
		cell = &(*video)[y][x];

		for (i = cursor; i < len; i++, cell++) {
		    cell->kar = line[i + 1] ? line[i + 1] : ' ';
		    line[i] = line[i + 1];
		}

		len--;
		line[len] = '\0';
	    } else
		beep();

	    break;

	case CTRL_RIGHT:
	    i = cursor;

	    while (line[i] != ' ' && i < len)
		i++, x++;

	    while (line[i] == ' ' && i < len)
		i++, x++;

	    cursor = i;
	    break;

	case CTRL_LEFT:
	    i = cursor;

	    if (i > 0 && line[i] != ' ' && line[i - 1] == ' ')
		i--, x--;

	    while (line[i] == ' ' && i)
		i--, x--;

	    while (line[i] != ' ' && i)
		i--, x--;

	    if (i)
		i++, x++;

	    cursor = i;
	    break;

	default:
	    if (insert && len == maxlen)
		beep();
	    else if (c >= ' ' && c < 256) {
		cell = &(*video)[y][x];

		if (insert) {
		    endy = starty;
		    endx = startx + len;
		    adjust(&endx, &endy);

		    if (endy == NROWS - 1 && endx == NCOLS - 1) {
			scroll_up(1);
			starty--;
			y--;
			cell -= NCOLS;
		    }

		    for (i = len - 1; i >= cursor; i--)
			line[i + 1] = line[i];

		    line[cursor] = c;
		    len++;
		    line[len] = '\0';

		    cell->kar = c;
		    cell++;

		    for (i = cursor + 1; i < len; i++, cell++)
			cell->kar = line[i];

		    cursor++;
		    x++;
		} else if (cursor == len && len == maxlen)
		    beep();
		else {
		    line[cursor] = c;

		    if (cursor == len) {
			len++;
			line[len] = '\0';
		    }

		    cell->kar = c;
		    cursor++;
		    x++;

		    if (x == NCOLS && y == NROWS - 1) {
			scroll_up(1);
			starty--;
			y--;
		    }
		}
	    } else
		beep();
	}
    }
}


PUBLIC int sys_get(stream, line, maxlen, prompt)
int stream;
char *line;
int maxlen;
char *prompt;
{
    if (ext_get(stream, line, maxlen, prompt))
	return 0;
    else
	return do_get(stream, line, maxlen, prompt, 1);
}


PUBLIC int sys_edit(stream, line, maxlen, cursor)
int stream;
char line[];
int maxlen;
int cursor;
{
    return do_get(stream, line, maxlen, "E ", cursor);
}


PUBLIC void *sys_alloc(size)
long size;
{
    return farcalloc(1, size);
}


PUBLIC void *sys_realloc(block, newsize)
void *block;
long newsize;
{
    return farrealloc(block, newsize);
}


PUBLIC void sys_free(p)
void *p;
{
    farfree(p);
}


PUBLIC int sys_call_scan(id, exproot, errtext)
struct id_rec *id;
struct exp_list *exproot;
char *errtext;
{
    int rc = ext_call_scan(id, exproot, errtext);

    if (rc == -1) {
	sprintf(errtext, "PROCedure %s not found", id->name);
	return 0;
    }

    return rc;
}


PUBLIC int sys_call(id, exproot, calltype, result, type)
struct id_rec *id;
struct exp_list *exproot;
int calltype;
void **result;
enum VAL_TYPE *type;
{
    return ext_call(id, exproot, calltype, result, type);
}


PUBLIC void sys_sys_exp(exproot, result, type)
struct exp_list *exproot;
void **result;
enum VAL_TYPE *type;
{
    if (ext_sys_exp(exproot, result, type) == -1)
	run_error(SYS_ERR, "Unknown SYS() command");
}


PUBLIC void sys_syss_exp(exproot, result, type)
struct exp_list *exproot;
char **result;
enum VAL_TYPE *type;
{
    if (ext_syss_exp(exproot, result, type) == -1)
	run_error(SYS_ERR, "Unknown SYS$() command");
}


PUBLIC int sys_sys_stat(exproot)
struct exp_list *exproot;
{
    int rc = ext_sys_stat(exproot);

    if (rc == -1)
	run_error(SYS_ERR, "Unknown SYS statement command");

    return rc;
}


PUBLIC int matherr(e)
struct exception *e;
{
    char *err;

    switch (e->type) {
    case DOMAIN:
	err = "Domain";
	break;
    case SING:
	err = "Singularity";
	break;
    case OVERFLOW:
	err = "Overflow";
	break;
    case UNDERFLOW:
	err = "Underflow";
	break;
    case PLOSS:
	err = "Partial loss of significance";
	break;
    case TLOSS:
	err = "Total loss of significance";
	break;
    default:
	err = "Unknown";
    }

    run_error(MATH_ERR, "Math err: %s error when executing %s", err,
	      e->name);

    return 0;
}
