/* 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 (const 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 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, CONST 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, CONST char *cptr);           /* handles font command */
static void   read_map_file(FILE *fd);                  /* reads a font map file */
static t_bool str_match(const char *str, const char *keyword);/* keyword/string comparison */
static const char * handle_map_ansi_definition(char **pc);  /* input line parsers for map file sections */
static const char * handle_map_input_definition(char **pc);
static const char * handle_map_output_definition(char **pc);
static const char * handle_map_overstrike_definition(char **pc);

#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' */

static 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 SendBeep (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 */

    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 -- process this even if no keyboard input request pending */
        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;
    }
                                                    // keyboard is locked or no active input request?
    if ((tti_unit.flags & KEYBOARD_LOCKED) || ! (tti_dsw & TT_DSW_KEYBOARD_BUSY)) {
        SendBeep();
        calc_ints();
        return SCPE_OK;
    }

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

    if (temp == 0)  {                           /* ignore invalid characters (no mapping to 1130 input code) */
        SendBeep();
        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, CONST char *cptr)
{
    char gbuf[2*CBUFSIZE];

    return attach_unit(uptr, quotefix(cptr, gbuf)); /* fix quotes in filenames & attach */
}

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

CONST char * quotefix (CONST char *cptr, char * buf)
{
    const char *c;
    int quote;

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

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

        if (*c)                                 /* terminate string at closing quote */
            *buf = '\0';
        }
    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)         /* disable int->char demotion warning caused by characters with high-bit set */
#endif

#ifdef __SUNPRO_C
#  pragma error_messages (off, E_INIT_DOES_NOT_FIT)     /* disable 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 __SUNPRO_C
#  pragma error_messages (default, E_INIT_DOES_NOT_FIT)     /* enable int->char demotion warning caused by characters with high-bit set */
#endif

#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, CONST 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, CONST char *iptr)
{
    char *fname, quote;
        char gbuf[4*CBUFSIZE], *cptr = gbuf;
    FILE *fd;

    gbuf[sizeof(gbuf)-1] = '\0';
    strncpy(gbuf, iptr, sizeof(gbuf)-1);
    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 (const char *str, const 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;
    const char *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 const char * get_num_char (char **pc, unsigned char *out, int ndigits, int base, const 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 const char * get_characters (char **pc, unsigned char *outstr, int nmax, int *nout)
{
    char *c = *pc;
    const char *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 const char * handle_map_ansi_definition (char **pc)
{
    unsigned char *outstr;
    const 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 const char * handle_map_input_definition (char **pc)
{
    unsigned char cin, cout;
    const 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 const char * handle_map_output_definition (char **pc)
{
    unsigned char cin, cout;
    const 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 const char * handle_map_overstrike_definition (char **pc)
{
    unsigned char ch, inlist[MAX_OS_CHARS];
    const 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;
}
