/* ibm1130_stddev.c: IBM 1130 standard I/O devices simulator

   Based on the SIMH simulator package written by Robert M Supnik

   Brian Knittel

   Revision History:

   2004.10.22 - Removed stub for xio_1134_papertape as it's now a supported device

   2003.11.23 - Fixed bug in new routine "quotefix" that made sim crash
   			    for all non-Windows builds :(

   2003.06.15 - added output translation code to accomodate APL font
   				added input translation feature to assist emulation of 1130 console keyboard for APL
   				changes to console input and output IO emulation, fixed bugs exposed by APL interpreter

   2002.09.13 - pulled 1132 printer out of this file into ibm1130_prt.c

 * (C) Copyright 2002, Brian Knittel.
 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
 * usual yada-yada. Please keep this notice and the copyright in any distributions
 * or modifications.
 *
 * This is not a supported product, but I welcome bug reports and fixes.
 * Mail to simh@ibm1130.org
 *
 * Notes about overstrike mapping:
 * The 1130 console printer used a Selectric typewriter element. The APL interpreter
 * used overprinting to construct some APL symbols, for example, a round O overstruck]
 * with | to get the greek phi. This doesn't accomodate a glass terminal! Instead,
 * modern APL fonts have separate character codes for the complex characters. 
 * To have APL\1130 output appear correctly, we have to do three things:
 *
 *		use simh's telnet feature to connect to the 1130 console stream
 *		have the telnet program use an APL font
 *		detect combinations of overstruck symbols, and generate the approrpiate alternate codes.
 *
 * There is a built-in table of font mappings and overstrike mappings, for the APLPLUS.TTF
 * truetype font widely available on the Internet. An font descriptor file can be used
 * to specify alternate mappings.
 *
 * The APL font codes and overstrike mapping can be enabled with the simh command
 *
 *		set tto apl
 *
 * and disabled with
 *
 *		set tto ascii  			(this is the default)
 *
 * APL also uses the red and black ribbon selection. The emulator will output
 * ansi red/black foreground commands with the setting
 *
 *		set tto ansi
 *
 * The codes can be disabled with
 *
 *		set tto noansi 			(this is the default)
 *
 * Finally, when APL mode is active, the emulator does some input key translations
 * to let the standard ASCII keyboard more closely match the physical layout of the
 * 1130 console keyboard. The numeric and punctuation key don't have their
 * traditional meaning under APL. The input mapping lets you use the APL keyboard
 * layout shown in the APL documentation.
 *
 * The translations are:
 * FROM
 * ASCII	Position on keyboard		To	1130 Key	APL interpretation
 * ------------------------------------	--------------------------------
 *	[		(key to right of P)			\r	Enter		left arrow
 *	;		(1st key to right of L)		\b	Backspace	[
 *	'		(2nd key to right of L)		^U	Erase Fld	]
 *	2		(key above Q)				@ 	@			up shift
 *	3		(key above W)				% 	%			up right shift
 *	4		(key above E)				*	*			+
 *	5		(key above R)				<	<			multiply
 *	8		(key above U)				-	-			Return
 *	9		(key above I)				/	/			Backspace
 *	-		(key above P)				^Q	INT REQ		ATTN
 *	Enter								-	-			Return 
 *	backsp								/	/			Backspace
 */

#include "ibm1130_defs.h"
#include <memory.h>

/* #define DEBUG_CONSOLE */

/* ---------------------------------------------------------------------------- */

static void badio (char *dev)
{
/* the real 1130 just ignores attempts to use uninstalled devices. They get tested
 * at times, so it's best to just be quiet about this
 * printf("%s I/O is not yet supported", dev);
 */
}

void xio_1231_optical	(int32 addr, int32 func, int32 modify)			{badio("optical mark");}
void xio_system7		(int32 addr, int32 func, int32 modify)			{badio("System 7");}

/* ---------------------------------------------------------------------------- */

#define MAX_OUTPUT_COLUMNS 100				/* width of 1130 console printer */
#define MAX_OS_CHARS 	     4				/* maximum number of overstruck characters that can be mapped */
#define MAX_OS_MAPPINGS	   100				/* maximum number of overstrike mappings */

typedef struct tag_os_map {					/* os_map = overstrike mapping */
	int ch;									/* ch = output character */
	int nin;								/* nin = number of overstruck characters */
	unsigned char inlist[MAX_OS_CHARS];		/* inlist = overstruck ASCII characters, sorted. NOT NULL TERMINATED */
} OS_MAP;

extern UNIT *sim_clock_queue;
extern int cgi;

static int32 tti_dsw = 0;					/* device status words */
static int32 tto_dsw = 0;
       int32 con_dsw = 0;
					
static unsigned char conout_map[256];		/* 1130 console code to ASCII translation. 0 = undefined, 0xFF = IGNR_ = no output */
static unsigned char conin_map[256];		/* input mapping */
static int  curcol = 0;						/* current typewriter element column, leftmost = 0 */
static int  maxcol = 0;						/* highest curcol seen in this output line         */
static unsigned char black_ribbon[30];		/* output escape sequence for black ribbon shift   */
static unsigned char red_ribbon[30];		/* output escape sequence for red ribbon shift     */

static OS_MAP os_buf[MAX_OUTPUT_COLUMNS];			/* current typewriter output line, holds character struck in each column */
static OS_MAP os_map[MAX_OS_MAPPINGS];				/* overstrike mapping entries */
static int n_os_mappings;							/* number of overstrike mappings */

static t_stat tti_svc(UNIT *uptr);
static t_stat tto_svc(UNIT *uptr);
static t_stat tti_reset(DEVICE *dptr);
static t_stat tto_reset(DEVICE *dptr);

static t_stat emit_conout_character(int ch);
static t_stat map_conout_character(int ch);
static void   reset_mapping (void);
static void   set_conout_mapping(int32 flags);
static t_stat validate_conout_mapping(UNIT *uptr, int32 match, char *cvptr, void *desc);
static void   set_default_mapping(int32 flags);
static void   finish_conout_mapping(int32 flags);
static void   strsort (int n, unsigned char *s);		/* sorts an array of n characters */
static int    os_map_comp (OS_MAP *a, OS_MAP *b);		/* compares two mapping entries */
static t_stat font_cmd(int32 flag, char *cptr);			/* handles font command */
static void   read_map_file(FILE *fd);					/* reads a font map file */
static t_bool str_match(char *str, char *keyword);		/* keyword/string comparison */
static char * handle_map_ansi_definition(char **pc);	/* input line parsers for map file sections */
static char * handle_map_input_definition(char **pc);
static char * handle_map_output_definition(char **pc);
static char * handle_map_overstrike_definition(char **pc);

extern t_stat sim_poll_kbd(void);
extern t_stat sim_wait_kbd(void);
extern t_stat sim_putchar(int32 out);

#define UNIT_V_CSET		(UNIT_V_UF + 0)		/* user flag: character set */
#define UNIT_V_LOCKED	(UNIT_V_UF + 2)		/* user flag: keyboard locked */
#define UNIT_V_ANSI		(UNIT_V_UF + 3)

#define CSET_ASCII		(0u << UNIT_V_CSET)
#define CSET_1130		(1u << UNIT_V_CSET)
#define CSET_APL		(2u << UNIT_V_CSET)	
#define CSET_MASK		(3u << UNIT_V_CSET)
#define ENABLE_ANSI		(1u << UNIT_V_ANSI)

#define KEYBOARD_LOCKED	(1u << UNIT_V_LOCKED)

#define IRQ_KEY				0x11			/* ctrl-Q */
#define PROGRAM_STOP_KEY	0x10			/* ctrl-P */

#include "ibm1130_conout.h"					/* conout_to_ascii table */
#include "ibm1130_conin.h"					/* ascii_to_conin  table */

/* TTI data structures

   tti_dev	TTI device descriptor
   tti_unit	TTI unit descriptor
   tti_reg	TTI register list
*/

UNIT tti_unit = { UDATA (&tti_svc, 0, 0), KBD_POLL_WAIT };

REG tti_reg[] = {
	{ ORDATA (BUF,   tti_unit.buf,  16) },
	{ ORDATA (DSW,   tti_dsw,       16) },
	{ DRDATA (POS,   tti_unit.pos,  31), PV_LEFT },
	{ DRDATA (STIME, tti_unit.wait, 24), REG_NZ + PV_LEFT },
	{ NULL }  };

MTAB tti_mod[] = {
	{ CSET_MASK,  CSET_ASCII,  "ASCII", "ASCII",  NULL},
	{ CSET_MASK,  CSET_1130,   "1130",  "1130",   NULL},
	{ 0 }  };

DEVICE tti_dev = {
	"KEYBOARD", &tti_unit, tti_reg, tti_mod,
	1, 10, 31, 1, 8, 8,
	NULL, NULL, &tti_reset,
	NULL, basic_attach, NULL };

/* TTO data structures

   tto_dev	TTO device descriptor
   tto_unit	TTO unit descriptor
   tto_reg	TTO register list
*/

		/* 14-Nov-03 -- the wait time was SERIAL_OUT_WAIT, but recent versions of SIMH reduced
		 * this to 100, and wouldn't you know it, APL\1130 has about 120 instructions between the XIO WRITE
		 * to the console and the associated WAIT.
		 */

UNIT tto_unit = { UDATA (&tto_svc, 0, 0), 200 };

REG tto_reg[] = {
	{ ORDATA (BUF, tto_unit.buf, 16) },
	{ ORDATA (DSW, tto_dsw, 16) },
	{ DRDATA (POS, tto_unit.pos, 31), PV_LEFT },
	{ DRDATA (STIME, tto_unit.wait, 24), PV_LEFT },
	{ NULL }  };

MTAB tto_mod[] = {
	{ CSET_MASK,  CSET_ASCII,  "ASCII",  "ASCII",  validate_conout_mapping, NULL, NULL},
	{ CSET_MASK,  CSET_1130,   "1130",   "1130",   validate_conout_mapping, NULL, NULL},
	{ CSET_MASK,  CSET_APL,    "APL",    "APL",    validate_conout_mapping, NULL, NULL},
	{ ENABLE_ANSI,0,		   "NOANSI", "NOANSI", NULL},
	{ ENABLE_ANSI,ENABLE_ANSI, "ANSI",   "ANSI",   NULL},
	{ 0 }  };

DEVICE tto_dev = {
	"TTO", &tto_unit, tto_reg, tto_mod,
	1, 10, 31, 1, 8, 8,
	NULL, NULL, &tto_reset,
	NULL, basic_attach, NULL };

/* Terminal input routines

   tti_svc		process event (character ready)
   tti_reset	process reset
   tto_svc		process event (print character)
   tto_reset	process reset
*/

#define TT_DSW_PRINTER_RESPONSE			0x8000
#define TT_DSW_KEYBOARD_RESPONSE		0x4000
#define TT_DSW_INTERRUPT_REQUEST		0x2000
#define TT_DSW_KEYBOARD_CONSOLE			0x1000
#define TT_DSW_PRINTER_BUSY				0x0800
#define TT_DSW_PRINTER_NOT_READY		0x0400
#define TT_DSW_KEYBOARD_BUSY			0x0200

void xio_1131_console (int32 iocc_addr, int32 func, int32 modify)
{
	int ch;
	char msg[80];

	switch (func) {
		case XIO_CONTROL:
			SETBIT(tti_dsw, TT_DSW_KEYBOARD_BUSY);		/* select and unlock the keyboard */
			keyboard_selected(TRUE);
			CLRBIT(tti_unit.flags, KEYBOARD_LOCKED);
			tti_unit.buf = 0;							/* no key character yet */
			break;

		case XIO_READ:							   	
			WriteW(iocc_addr, tti_unit.buf);			/* return keycode */
			CLRBIT(tti_dsw, TT_DSW_KEYBOARD_BUSY);		/* this ends selected mode */
			keyboard_selected(FALSE);
			SETBIT(tti_unit.flags, KEYBOARD_LOCKED);	/* keyboard is locked when not selected */
			tti_unit.buf = 0;							/* subsequent reads will return zero */
			break;

		case XIO_WRITE:
			ch = (ReadW(iocc_addr) >> 8) & 0xFF;		/* get character to write */
			tto_unit.buf = emit_conout_character(ch);	/* output character and save write status */

/*			fprintf(stderr, "[CONOUT] %02x\n", ch); */

			SETBIT(tto_dsw, TT_DSW_PRINTER_BUSY);
			sim_activate(&tto_unit, tto_unit.wait);		/* schedule interrupt */
			break;

		case XIO_SENSE_DEV:
			ACC = tto_dsw | tti_dsw;
			if (modify & 0x01) {						/* reset interrupts */
				CLRBIT(tto_dsw, TT_DSW_PRINTER_RESPONSE);
				CLRBIT(tti_dsw, TT_DSW_KEYBOARD_RESPONSE);
				CLRBIT(tti_dsw, TT_DSW_INTERRUPT_REQUEST);
				CLRBIT(ILSW[4], ILSW_4_CONSOLE);
			}
			break;

		default:
			sprintf(msg, "Invalid console XIO function %x", func);
			xio_error(msg);
	}

/*	fprintf(stderr, "After XIO             %04x %04x\n", tti_dsw, tto_dsw); */
}

/* emit_conout_character - write character with 1130 console code 'ch' */

t_stat emit_conout_character (int ch)
{
	t_stat status;

#ifdef DEBUG_CONSOLE
	printf("{%02x}", ch); 
#endif

	if ((tto_unit.flags & CSET_MASK) == CSET_1130)	/* 1130 (binary) mode, write the raw 8-bit value */
		return sim_putchar(ch);

	if (ch & COUT_IS_CTRL) {
		/* red/black shift can be combined with another control */
		/* if present, emit the color shift characters alone */

		if (ch & COUT_CTRL_BLACK) {				
			if ((status = map_conout_character(COUT_IS_CTRL|COUT_CTRL_BLACK)) != SCPE_OK)
				return status;
		}
		else if (ch & COUT_CTRL_RED) {
			if ((status = map_conout_character(COUT_IS_CTRL|COUT_CTRL_RED)) != SCPE_OK)
				return status;
		}

		ch &= ~(COUT_CTRL_BLACK|COUT_CTRL_RED);	/* remove the ribbon shift bits */

		if (ch & ~COUT_IS_CTRL)	{				/* if another control remains, emit it */
			if ((status = map_conout_character(ch)) != SCPE_OK)
				return status;
		}

		return SCPE_OK;
	}

	return map_conout_character(ch);
}

static void Beep (void)			/* notify user keyboard was locked or key was bad */
{
	sim_putchar(7);
}

/* tti_svc - keyboard polling (never stops) */

static t_stat tti_svc (UNIT *uptr)
{
	int32 temp;

	if (cgi)										/* if running in CGI mode, no keyboard and no keyboard polling! */
		return SCPE_OK;
													/* otherwise, so ^E can interrupt the simulator, */
	sim_activate(&tti_unit, tti_unit.wait);			/* always continue polling keyboard */

	assert(sim_clock_queue != NULL);

	temp = sim_poll_kbd();

	if (temp < SCPE_KFLAG)
		return temp;								/* no char or error? */

	temp &= 0xFF;									/* remove SCPE_KFLAG */

	if ((tti_unit.flags & CSET_MASK) == CSET_ASCII)
		temp = conin_map[temp] & 0xFF;				/* perform input translation */

	if (temp == IRQ_KEY) {							/* INT REQ (interrupt request) key */
		SETBIT(tti_dsw, TT_DSW_INTERRUPT_REQUEST);	/* queue interrupt */
		SETBIT(ILSW[4], ILSW_4_CONSOLE);
		calc_ints();

		CLRBIT(tti_unit.flags, KEYBOARD_LOCKED);	/* keyboard restore, according to func. char. manual */

#ifdef DEBUG_CONSOLE
		printf("[*IRQ*]");
#endif
		tti_unit.buf = 0;							/* subsequent reads need to return 0 (required by APL\1130) */
		return SCPE_OK;
	}

	if (temp == PROGRAM_STOP_KEY) {					/* simulate the program stop button */
		SETBIT(con_dsw, CPU_DSW_PROGRAM_STOP);
		SETBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);
		calc_ints();

#ifdef DEBUG_CONSOLE
		printf("[*PSTOP*]");
#endif

		return SCPE_OK;
	}

	if ((tti_unit.flags & KEYBOARD_LOCKED) || ! (tti_dsw & TT_DSW_KEYBOARD_BUSY)) {
		Beep();
		return SCPE_OK;
	}

	if ((tti_unit.flags & CSET_MASK) == CSET_ASCII) 
		temp = ascii_to_conin[temp];

	if (temp == 0)	{							/* ignore invalid characters */
		Beep();
		calc_ints();
		return SCPE_OK;
	}

	tti_unit.buf = temp & 0xFFFE;				/* save keystroke except last bit (not defined) */
	tti_unit.pos = tti_unit.pos + 1;			/* but it lets us distinguish 0 from no punch ' ' */

#ifdef DEBUG_CONSOLE
	printf("[%04x]", tti_unit.buf & 0xFFFF);
#endif

	SETBIT(tti_unit.flags, KEYBOARD_LOCKED);	/* prevent further keystrokes */

	SETBIT(tti_dsw, TT_DSW_KEYBOARD_RESPONSE);	/* queue interrupt */
	SETBIT(ILSW[4], ILSW_4_CONSOLE);
	calc_ints();

/*	fprintf(stderr, "TTI interrupt svc SET %04x %04x\n", tti_dsw, tto_dsw); */

	return SCPE_OK;
}

static t_stat tti_reset (DEVICE *dptr)
{
	tti_unit.buf = 0;
	tti_dsw = 0;

	CLRBIT(ILSW[4], ILSW_4_CONSOLE);
	calc_ints();
	keyboard_selected(FALSE);

	SETBIT(tti_unit.flags, KEYBOARD_LOCKED);

	if (cgi)
		sim_cancel(&tti_unit);					/* in cgi mode, never poll keyboard */
	else
		sim_activate(&tti_unit, tti_unit.wait);	/* otherwise, always poll keyboard */

	return SCPE_OK;
}

/* basic_attach - fix quotes in filename, then call standard unit attach routine */

t_stat basic_attach (UNIT *uptr, char *cptr)
{
	return attach_unit(uptr, quotefix(cptr));	/* fix quotes in filenames & attach */
}

/* quotefix - strip off quotes around filename, if present */

char * quotefix (char * cptr)
{
#ifdef WIN32									/* do this only for Windows builds, for the time being */
	char *c;
	int quote;

	if (*cptr == '"' || *cptr == '\'') {
		quote = *cptr++;						/* remember quote and skip over it */

		for (c = cptr; *c && *c != quote; c++)
			;									/* find closing quote, or end of string */

		if (*c)									/* terminate string at closing quote */
			*c = '\0';
	}

#endif
	return cptr;								/* return pointer to cleaned-up name */
}

t_bool keyboard_is_busy (void)					/* return TRUE if keyboard is not expecting a character */
{
	return (tti_dsw & TT_DSW_KEYBOARD_BUSY);
}

static t_stat tto_svc (UNIT *uptr)
{
	CLRBIT(tto_dsw, TT_DSW_PRINTER_BUSY);
	SETBIT(tto_dsw, TT_DSW_PRINTER_RESPONSE);

	SETBIT(ILSW[4], ILSW_4_CONSOLE);
	calc_ints();

/*	fprintf(stderr, "TTO interrupt svc SET %04x %04x\n", tti_dsw, tto_dsw); */

	return (t_stat) tto_unit.buf;				/* return status saved during output conversion */
}

static t_stat tto_reset (DEVICE *dptr)
{
	tto_unit.buf = 0;
	tto_dsw = 0;

	CLRBIT(ILSW[4], ILSW_4_CONSOLE);
	calc_ints();

	sim_cancel(&tto_unit);				/* deactivate unit */

	set_conout_mapping(tto_unit.flags);	/* initialize the overstrike mappings */
										/* register the font-mapping command */
	register_cmd("FONT", font_cmd, 0, "font MAPFILE             use font mapping definitions in MAPFILE\n");

	return SCPE_OK;
}

#ifdef _MSC_VER
#  pragma warning(disable:4245)			/* enable int->char demotion warning caused by characters with high-bit set */
#endif

static struct {							/* default input mapping for APL */
	unsigned char in;
	unsigned char out;
} conin_to_APL[] =
{										/* these map input keys to those in like positions on 1130 keyboard */
	'[',	'\r',						/* enter (EOF) is APL left arrow */
	';',	'\b',						/* backspace is APL [ */
	'\'',	'\x15',						/* ctrl-U, erase field, is APL ]*/
	'2',	'@',						/* APL upshift */
	'3',	'%',						/* APL rightshift */
	'4',	'*',						/* APL + and - */
	'5',	'<',						/* APL x and divide */
	'8',	'-',						/* APL return */
	'9',	'/',						/* APL backspace */
	'-',	IRQ_KEY,					/* ctrl-q (INT REQ), APL ATTN */
	'\r',	'-',						/* APL return */
	'\b',	'/'							/* APL backspace */
};

#define NCONIN_TO_APL (sizeof(conin_to_APL)/sizeof(conin_to_APL[0]))

static struct {							/* default output mapping for APLPLUS font */
	unsigned char in;
	unsigned char out;
} conout_to_APL[] =
{
	'\x01', IGNR_,						/* controls */
	'\x03', '\n',
	'\x05', IGNR_,						/* (black and red are handled by ansi sequences) */
	'\x09', IGNR_,
	'\x11', '\b',
	'\x21', ' ',
	'\x41', '\t',
	'\x81', CRLF_,

	'\xC4', '\x30',						/* (if you're curious, order here is position on APL typeball) */
	'\xE4', '\x38',
	'\xD4', '\x37',
	'\xF4', '\x35',
	'\xDC', '\x33',
	'\xFC', '\x31',
	'\xC2', '\x29',
	'\xE2', '\x9F',
	'\xD2', '\x89',
	'\xF2', '\x88',
	'\xDA', '\xAF',
	'\xC6', '\x5E',
	'\xE6', '\xAC',
	'\xD6', '\x3E',
	'\xF6', '\x3D',
	'\xDE', '\x3C',
	'\xFE', '\xA8',
	'\xC0', '\x5D',
	'\xE0', '\x39',
	'\xD0', '\x36',
	'\xF0', '\x34',
	'\xD8', '\x32',

	'\x84', '\x84',
	'\xA4', '\x59',
	'\x94', '\x58',
	'\xB4', '\x56',
	'\x9C', '\x54',
	'\xBC', '\x2F',
	'\x82', '\x3B',
	'\xA2', '\x9B',
	'\x92', '\xBE',
	'\xB2', '\x87',
	'\x9A', '\x97',
	'\x86', '\x85',
	'\xA6', '\x86',
	'\x96', '\x9C',
	'\xB6', '\x9E',
	'\x9E', '\x7E',
	'\xBE', '\x5C',
	'\x80', '\x2C',
	'\xA0', '\x5A',
	'\x90', '\x57',
	'\xB0', '\x55',
	'\x98', '\x53',

	'\x44', '\x2B',
	'\x64', '\x51',
	'\x54', '\x50',
	'\x74', '\x4E',
	'\x5C', '\x4C',
	'\x7C', '\x4A',
	'\x42', '\x28',
	'\x62', '\xBD',
	'\x52', '\xB1',
	'\x72', '\x7C',
	'\x5A', '\x27',
	'\x46', '\x2D',
	'\x66', '\x3F',
	'\x56', '\x2A',
	'\x76', '\x82',
	'\x5E', '\x8C',
	'\x7E', '\xB0',
	'\x40', '\x5B',
	'\x60', '\x52',
	'\x50', '\x4F',
	'\x70', '\x4D',
	'\x58', '\x4B',

	'\x04', '\xD7',
	'\x24', '\x48',
	'\x14', '\x47',
	'\x34', '\x45',
	'\x1C', '\x43',
	'\x3C', '\x41',
	'\x02', '\x3A',
	'\x22', '\xBC',
	'\x12', '\x5F',
	'\x32', '\x98',
	'\x1A', '\x83',
	'\x06', '\xF7',
	'\x26', '\x91',
	'\x16', '\x92',
	'\x36', '\xB9',
	'\x1E', '\x9D',
	'\x3E', '\xB8',
	'\x00', '\x2E',
	'\x20', '\x49',
	'\x10', '\x46',
	'\x30', '\x44',
	'\x18', '\x42',
};

#define NCONOUT_TO_APL (sizeof(conout_to_APL)/sizeof(conout_to_APL[0]))

static OS_MAP default_os_map[] =			/* overstrike mapping for APLPLUS font */
{
	'\x8a',	2,	"\x5e\x7e",
	'\x8b',	2,	"\x9f\x7e",
	'\x8d',	2,	"\x8c\x27",
	'\x8e',	3,	"\x8c\x2d\x3a",
	'\x8f',	2,	"\x91\x5f",
	'\x90',	2,	"\x92\x7e",
	'\x93',	2,	"\x91\x7c",
	'\x94',	2,	"\x92\x7c",
	'\x95',	2,	"\xb0\x82",
	'\x96',	2,	"\xb0\x83",
	'\x99',	2,	"\x2d\x5c",
	'\x9a',	2,	"\x2d\x2f",
	'\xae',	2,	"\x2c\x2d",
	'\xb2',	2,	"\xb1\x7c",
	'\xb3',	2,	"\xb1\x5c",
	'\xb4',	2,	"\xb1\x2d",
	'\xb5',	2,	"\xb1\x2a",
	'\xba',	2,	"\xb9\x5f",
	'\xd0',	2,	"\x30\x7e",
	'\xd8',	2,	"\x4f\x2f",
	'\x21', 2,  "\x27\x2e",
	'\xa4',	2,	"\xb0\xb1",		/*  map degree in circle to circle cross (APL uses this as character error symbol) */
	'\xf0',	2,	"\xb0\xa8",
	'\xfe',	2,	"\x3a\xa8",
};

#ifdef _MSC_VER
#  pragma warning(default:4245)						/* enable int->char demotion warning */
#endif

/* os_map_comp - compare to OS_MAP entries */

static int os_map_comp (OS_MAP *a, OS_MAP *b)
{
	unsigned char *sa, *sb;
	int i;

	if (a->nin > b->nin)
		return +1;

	if (a->nin < b->nin)
		return -1;

	sa = a->inlist;
	sb = b->inlist;

	for (i = a->nin; --i >= 0;) {
		if (*sa > *sb)
			return +1;

		if (*sa < *sb)
			return -1;

		sa++;
		sb++;
	}

	return 0;
}

/* strsort - sorts the n characters of array 's' using insertion sort */

static void strsort (int n, unsigned char *s)
{
	unsigned char temp;
	int i, big;

	while (--n > 0) {				/* repeatedly */
		big = 0;					/* find largest value of s[0]...s[n] */
		for (i = 1; i <= n; i++)
			if (s[i] > s[big]) big = i;

		temp   = s[n];				/* put largest value at end of array */
		s[n]   = s[big];
		s[big] = temp;
	}	
}

/* file format:

[font XXX]			 font named XXX
OUT					 failure character
OUT IN				 single character mapping
OUT IN IN ...		 overstrike mapping

*/

static void set_conout_mapping (int32 flags)
{
	curcol = 0;
	maxcol = 0;

	/* set the default mappings. We may later override them with settings from an ini file */

	set_default_mapping(flags);
}

/* finish_conout_mapping - sort the finalized overstrike mapping */

static void finish_conout_mapping (int32 flags)
{
	int i, n, big;
	OS_MAP temp;

	for (i = 0; i < n_os_mappings; i++)		/* sort the inlist strings individually */
		strsort(os_map[i].nin, os_map[i].inlist);

	for (n = n_os_mappings; --n > 0; ) {	/* then sort the os_map array itself with insertion sort */
		big = 0;							/* find largest value of s[0]...s[n] */
		for (i = 1; i <= n; i++)
			if (os_map_comp(os_map+i, os_map+big) > 0) big = i;

		if (big != n) {
			temp        = os_map[n];		/* put largest value at end of array */
			os_map[n]   = os_map[big];
			os_map[big] = temp;
		}
	}
}

/* validate_conout_mapping - called when set command gets a new value */

static t_stat validate_conout_mapping (UNIT *uptr, int32 match, char *cvptr, void *desc)
{
	set_conout_mapping(match);
	return SCPE_OK;
}

static void reset_mapping (void)
{
	int i;

	black_ribbon[0] = '\0';							/* erase the ribbon sequences */
	red_ribbon[0]   = '\0';

	memset(conout_map, 0, sizeof(conout_map));		/* erase output mapping */

	n_os_mappings = 0;								/* erase overstrike mapping */

	for (i = (sizeof(conin_map)/sizeof(conin_map[0])); --i >= 0; )
		conin_map[i] = (unsigned char) i;			/* default conin_map is identity map */
}

/* set_default_mapping - create standard font and overstrike map */

static void set_default_mapping (int32 flags)
{
	int i;

	reset_mapping();

	strcpy((char *) black_ribbon, "\033[30m");
	strcpy((char *) red_ribbon,	 "\033[31m");

	switch (flags & CSET_MASK) {
		case CSET_1130:
			break;

		case CSET_ASCII:
			memcpy(conout_map, conout_to_ascii, sizeof(conout_to_ascii));
			break;

		case CSET_APL:
			for (i = NCONOUT_TO_APL; --i >= 0; )
				conout_map[conout_to_APL[i].in] = conout_to_APL[i].out;

			for (i = NCONIN_TO_APL; --i >= 0; )
				conin_map[conin_to_APL[i].in] = conin_to_APL[i].out;

			memcpy(os_map, default_os_map, sizeof(default_os_map));			
			n_os_mappings = (sizeof(default_os_map) / sizeof(default_os_map[0]));
			break;
	}

	finish_conout_mapping(flags);				/* sort conout mapping if necessary */
}

/* sim_putstr - write a string to the console */

t_stat sim_putstr (char *s)
{
	t_stat status;

	while (*s) {
		if ((status = sim_putchar(*s)) != SCPE_OK)
			return status;

		s++;
	}

	return SCPE_OK;
}

/* map_conout_character - translate and write a single character */

static t_stat map_conout_character (int ch)
{
	t_stat status;
	int i, cmp;

	if (ch == (COUT_IS_CTRL|COUT_CTRL_BLACK))
		return (tto_unit.flags & ENABLE_ANSI) ? sim_putstr((char *) black_ribbon) : SCPE_OK;

	if (ch == (COUT_IS_CTRL|COUT_CTRL_RED))
		return (tto_unit.flags & ENABLE_ANSI) ? sim_putstr((char *) red_ribbon)   : SCPE_OK;

	if ((ch = conout_map[ch & 0xFF]) == 0)
		ch = '?';						/* unknown character? print ? */

	if (ch == '\n') {					/* newline: reset overstrike buffer */
		curcol = 0;
		maxcol = -1;
	}
	else if (ch == '\r') {				/* carriage return: rewind to column 0 */
		curcol = 0;
		maxcol = -1;					/* assume it advances paper too */
	}
	else if (ch == '\b') {				/* backspace: back up one character */
		if (curcol > 0)
			curcol--;
	}
	else if (n_os_mappings && ch != (unsigned char) IGNR_) {
		if (curcol >= MAX_OUTPUT_COLUMNS)
			map_conout_character('\x81');		/* precede with automatic carriage return/line feed, I guess */
		
		if (curcol > maxcol) {					/* first time in this column, no overstrike possible yet */
			os_buf[curcol].nin = 0;
			maxcol = curcol;
		}

		if (ch != ' ' && ch != 0) {				/* (if it's not a blank or unknown) */
			os_buf[curcol].inlist[os_buf[curcol].nin] = (unsigned char) ch;
			strsort(++os_buf[curcol].nin, os_buf[curcol].inlist);
		}

		if (os_buf[curcol].nin == 0)			/* if nothing but blanks seen, */
			ch = ' ';							/* output is a blank */
		else if (os_buf[curcol].nin == 1) {		/* if only one printing character seen, display it */
			ch = os_buf[curcol].inlist[0];
		}
		else {									/* otherwise look up mapping */
			ch = '?';

			for (i = 0; i < n_os_mappings; i++) {
				cmp = os_map_comp(&os_buf[curcol], &os_map[i]);
				if (cmp == 0) {					/* a hit */
					ch = os_map[i].ch;
					break;
				}
				else if (cmp < 0)				/* not found */
					break;
			}
		}

		if (curcol < MAX_OUTPUT_COLUMNS)		/* this should now never happen, as we automatically return */
			curcol++;
	}

	switch (ch) {
		case IGNR_:
			break;

		case CRLF_:
			if (! cgi) {
				if ((status = sim_putchar('\r')) != SCPE_OK)
					return status;

				tto_unit.pos++;
			}

			if ((status = sim_putchar('\n')) != SCPE_OK)
				return status;

			tto_unit.pos++;						/* hmm, why do we count these? */
			break;

		default:
			if ((status = sim_putchar(ch)) != SCPE_OK)
				return status;

			tto_unit.pos++;
			break;
	}

	return SCPE_OK;
}

/* font_cmd - parse a font mapping file. Sets input and output translations */

static t_stat font_cmd (int32 flag, char *cptr)
{
	char *fname, quote;
	FILE *fd;

	while (*cptr && (*cptr <= ' ')) cptr++;			/* skip blanks */
	if (! *cptr) return SCPE_2FARG;					/* argument missing */

	fname = cptr;									/* save start */
    if (*cptr == '\'' || *cptr == '"') {			/* quoted string */
		quote = *cptr++;							/* remember quote character */
		fname++;									/* skip the quote */

		while (*cptr && (*cptr != quote))			/* find closing quote */
			cptr++;
	}
	else {
		while (*cptr && (*cptr > ' '))				/* find terminating blank */
	    	cptr++; 
	}
	*cptr = '\0';									/* terminate name */

	if ((fd = fopen(fname, "r")) == NULL)
		return SCPE_OPENERR;

	reset_mapping();								/* remove all default mappings */

	read_map_file(fd);
	fclose(fd);

	finish_conout_mapping(tto_unit.flags);
	return SCPE_OK;
}

/* str_match - compare the string str to the keyword, case insensitive */

static t_bool str_match (char *str, char *keyword)
{
	char kch, sch;

	while (*keyword) {							/* see if str matches the keyword... */
		kch = *keyword++;						/* get pair of characters */
		sch = *str++;

		if (BETWEEN(kch, 'A', 'Z')) kch += 32;	/* change upper to lower case */
		if (BETWEEN(sch, 'A', 'Z')) sch += 32;

		if (kch != sch)							/* characters must match; if not, quit */
			return FALSE;
	}

	return *str <= ' ' || *str == ';';			/* success if the input string ended or is in whitespace or comment */ 
}

/* read_map_file - process definition lines in opened mapping file */

static void read_map_file (FILE *fd)
{
	char str[256], *c, *errmsg;
	int lineno = 0;
	enum {SECT_UNDEFINED, SECT_DEFAULT, SECT_ANSI, SECT_INPUT, SECT_OUTPUT, SECT_OVERSTRIKE}
		section = SECT_UNDEFINED;

	while (fgets(str, sizeof(str), fd) != NULL) {
		++lineno;									/* count input lines */

		if ((c = strchr(str, '\n')) != NULL)		/* terminate at newline */
			*c = '\0';

		for (c = str; *c && *c <= ' '; c++)			/* skip blanks */
			;

		if (c[0] == '\0' || c[0] == ';')			/* ignore blank lines and lines starting with ; */
			continue;

		if (*c == '[') {
			if (str_match(c, "[default]")) {		/* check for section separators */
				set_default_mapping(tto_unit.flags);
				section = SECT_UNDEFINED;
				continue;
			}
			if (str_match(c, "[ansi]")) {
				section = SECT_ANSI;
				continue;
			}
			if (str_match(c, "[input]")) {
				section = SECT_INPUT;
				continue;
			}
			if (str_match(c, "[output]")) {
				section = SECT_OUTPUT;
				continue;
			}
			if (str_match(c, "[overstrike]")) {
				section = SECT_OVERSTRIKE;
				continue;
			}
		}

		switch (section) {							/* if we get here, we have a definition line */
			case SECT_ANSI:
				errmsg = handle_map_ansi_definition(&c);
				break;
			case SECT_INPUT:
				errmsg = handle_map_input_definition(&c);
				break;
			case SECT_OUTPUT:
				errmsg = handle_map_output_definition(&c);
				break;
			case SECT_OVERSTRIKE:
				errmsg = handle_map_overstrike_definition(&c);
				break;
			default:
				errmsg = "line occurs before valid [section]";
				break;
		}

		if (errmsg == NULL) {						/* if no other error detected, */
			while (*c && *c <= ' ')					/* skip past any whitespace */
				c++;

			if (*c && *c != ';')					/* if line doesn't end or run into a comment, complain */
				errmsg = "too much stuff on input line";
		}

		if (errmsg != NULL)	{						/* print error message and offending line */
			printf("* Warning: %s", errmsg);

			switch (section) {						/* add section name if possible */
				case SECT_ANSI:			errmsg = "ansi";		break;
				case SECT_INPUT:		errmsg = "input";		break;
				case SECT_OUTPUT:		errmsg = "output";		break;
				case SECT_OVERSTRIKE:	errmsg = "overstrike";	break;
				default:				errmsg = NULL;			break;
			}
			if (errmsg != NULL)
				printf(" in [%s] section", errmsg);

			printf(", line %d\n%s\n", lineno, str);
		}
	}
}

/* get_num_char - read an octal or hex character specification of exactly 'ndigits' digits
 * the input pointers is left pointing to the last character of the number, so that it
 * may be incremented by the caller
 */

static char * get_num_char (char **pc, unsigned char *out, int ndigits, int base, char *errmsg)
{
	int ch = 0, digit;
	char *c = *pc;

	while (--ndigits >= 0) {			/* collect specified number of digits */
		if (BETWEEN(*c, '0', '9'))
			digit = *c - '0';
		else if (BETWEEN(*c, 'A', 'F'))
			digit = *c - 'A' + 10;
		else if (BETWEEN(*c, 'a', 'f'))
			digit = *c - 'a' + 10;
		else
			digit = base;

		if (digit >= base)				/* bad digit */
			return errmsg;

		ch = ch * base + digit;			/* accumulate digit */
		c++;
	}

	*out = (unsigned char) ch;			/* return parsed character */
	*pc  = c-1;							/* make input pointer point to last character seen */
	return NULL;						/* no error */
}

/* get_characters - read character specification(s) from input string pointed to 
 * by *pc. Results stored in outstr; up to nmax characters parsed. Actual number
 * found returned in *nout. Returns NULL on success or error message if syntax
 * error encountered. *pc is advanced to next whitespace or whatever followed input.
 */

static char * get_characters (char **pc, unsigned char *outstr, int nmax, int *nout)
{
	char *c = *pc, *errstr;
	unsigned char *out = outstr;

	while (*c && *c <= ' ')					/* skip leading whitespace */
		c++;

	while (--nmax >= 0) {					/* get up to maximum number of characters */
		if (*c == ';' || *c <= ' ')			/* we ran into a comment, whitespace or end of string: we're done */
			break;

		if (*c == '\\') {					/* backslash escape of some sort */
			switch (*++c) {
				case 'b':					/* backspace */
				case 'B':
					*out++ = '\b';
					break;

				case 'e':					/* ascii ESCAPE */
				case 'E':
					*out++ = '\033';
					break;

				case 'f':					/* formfeed */
				case 'F':
					*out++ = '\f';
					break;

				case 'n':					/* newline */
				case 'N':
					*out++ = '\n';
					break;

				case 'r':					/* return */
				case 'R':
					*out++ = '\r';
					break;

				case 't':					/* tab */
				case 'T':
					*out++ = '\t';
					break;

				case 'x':					/* hex specification */
				case 'X':
					c++;
					if ((errstr = get_num_char(&c, out, 2, 16, "bad hex character")) != NULL)
						return errstr;

					out++;					/* advance out pointer */
					break;

				default:					/* anything else */
					if (BETWEEN(*c, '0', '7')) {	/* octal specification */
						if ((errstr = get_num_char(&c, out, 3, 8, "bad octal character")) != NULL)
							return errstr;
						
						out++;						/* advance out pointer */
					}
					else if (BETWEEN(*c, 'A', 'Z') || BETWEEN(*c, 'a', 'z'))
						return "invalid \\ escape";	/* other \x letters are bad */
					else {
						*out++ = (unsigned char) *c;/* otherwise, accept \x as literal character x */
					}
					break;
			}
		}
		else if (*c == '^') {				/* control character */
			c++;
			if (BETWEEN(*c, 'A', 'Z'))		/* convert alpha, e.g. A -> 1 */
				*out++ = (unsigned char) (*c - 'A' + 1);
			else if (BETWEEN(*c, 'a', 'z'))
				*out++ = (unsigned char) (*c - 'z' + 1);
			else							/* non alpha is bad */
				return "invalid control letter";
		}
		else if (str_match(c, "IGNORE")) {	/* magic word: a character that will never be output */
			*out++ = (unsigned char) IGNR_;
			c += 6;
		}
		else {
			*out++ = (unsigned char) *c;	/* save literal character */
		}

		c++;
	}

	if (*c && *c != ';' && *c > ' ')		/* we should be at end of string, whitespace or comment */
		return "too many characters specified";

	*pc = c;								/* save advanced pointer */
	*nout = out-outstr;						/* save number of characters stored */

	return NULL;							/* no error */
}

/* handle_map_ansi_definition - process line in [ansi] section */

static char * handle_map_ansi_definition (char **pc)
{
	unsigned char *outstr;
	char *errmsg;
	int n;

	if (str_match(*pc, "black")) {							/* find which string we're setting */
		outstr = black_ribbon;								/* this is where we'll save the output string */
		*pc += 5;											/* skip over the token */
	}
	else if (str_match(*pc, "red")) {
		outstr = red_ribbon;
		*pc += 3;
	}
	else
		return "invalid variable name";
															/* get list of characters */
	if ((errmsg = get_characters(pc, outstr, sizeof(black_ribbon)-1, &n)) != NULL)
		return errmsg;

	outstr[n] = '\0';										/* null terminate the string */

	return (n > 0) ? NULL : "missing output string";		/* NULL if OK, error msg if no characters */
}

/* handle_map_input_definition - process line in [input] section */

static char * handle_map_input_definition (char **pc)
{
	unsigned char cin, cout;
	char *errmsg;
	int n;

	if ((errmsg = get_characters(pc, &cin, 1, &n)) != NULL)	/* get input character */
		return errmsg;

	if (n != 1)
		return "missing input character";

	if ((errmsg = get_characters(pc, &cout, 1, &n)) != NULL)	/* get output character */
		return errmsg;

	if (n != 1)
		return "missing output character";

	conin_map[cin] = cout;									/* set the mapping */
	return NULL;
}

/* handle_map_output_definition - process line in [output] section */

static char * handle_map_output_definition (char **pc)
{
	unsigned char cin, cout;
	char *errmsg;
	int n;

	if ((errmsg = get_characters(pc, &cin, 1, &n)) != NULL)	/* get input character */
		return errmsg;

	if (n != 1)
		return "missing input character";

	if ((errmsg = get_characters(pc, &cout, 1, &n)) != NULL)	/* get output character */
		return errmsg;

	if (n != 1)
		return "missing output character";

	conout_map[cin] = cout;									/* set the mapping */
	return NULL;
}

/* handle_map_overstrike_definition - process line in [overstrike] section */

static char * handle_map_overstrike_definition (char **pc)
{
	unsigned char ch, inlist[MAX_OS_CHARS];
	char *errmsg;
	int nin;

	if (n_os_mappings >= MAX_OS_MAPPINGS)					/* os_map is full, no more room */
		return "too many overstrike mappings";
															/* get output character */
	if ((errmsg = get_characters(pc, &ch, 1, &nin)) != NULL)
		return errmsg;

	if (nin != 1)
		return "missing output character";
															/* get input list */
	if ((errmsg = get_characters(pc, inlist, MAX_OS_CHARS, &nin)) != NULL)
		return errmsg;

	if (nin < 2)											/* expect at least two characters overprinted */
		return "missing input list";

	os_map[n_os_mappings].ch  = ch;							/* save in next os_map slot */
	os_map[n_os_mappings].nin = nin;
	memmove(os_map[n_os_mappings].inlist, inlist, nin);

	n_os_mappings++;
	return NULL;
}
