/*
 * besm6_punch.c: BESM-6 punchtape devices
 *
 * Copyright (c) 2009, Leonid Broukhis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * SERGE VAKULENKO OR LEONID BROUKHIS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
 * OR OTHER DEALINGS IN THE SOFTWARE.

 * Except as contained in this notice, the name of Leonid Broukhis or
 * Serge Vakulenko shall not be used in advertising or otherwise to promote
 * the sale, use or other dealings in this Software without prior written
 * authorization from Leonid Broukhis and Serge Vakulenko.
 */
#include "besm6_defs.h"

t_stat fs_event (UNIT *u);

UNIT fs_unit [] = {
    { UDATA (fs_event, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (fs_event, UNIT_SEQ+UNIT_ATTABLE, 0) },
};

int curchar[2], feed[2], isfifo[2];
char line[2][128];

#define FS1_READY (1<<15)
#define FS2_READY (1<<14)

/* #define NEGATIVE_RDY */

#ifndef NEGATIVE_RDY
#   define ENB_RDY      SET_RDY
#   define DIS_RDY      CLR_RDY
#   define IS_RDY       ISSET_RDY
#else
#   define ENB_RDY      CLR_RDY
#   define DIS_RDY      SET_RDY
#   define IS_RDY       ISCLR_RDY
#endif

#define SET_RDY(x)      do READY |= x; while (0)
#define CLR_RDY(x)      do READY &= ~(x); while (0)
#define ISSET_RDY(x)    ((READY & (x)) != 0)
#define ISCLR_RDY(x)    ((READY & (x)) == 0)

#define FS_RATE         1000*MSEC/1500

unsigned char FS[2];

REG fs_reg[] = {
    { REGDATA ( "Готов", READY, 2,  2, 14, 1, NULL, NULL, 0, 0, 0) },
    { ORDATA  ( "ФС1500-1", FS[0], 8) },
    { ORDATA  ( "ФС1500-2", FS[2], 8) },
    { 0 }
};

MTAB fs_mod[] = {
    { 0 }
};

t_stat fs_reset (DEVICE *dptr);
t_stat fs_attach (UNIT *uptr, CONST char *cptr);
t_stat fs_detach (UNIT *uptr);

DEVICE fs_dev = {
    "FS", fs_unit, fs_reg, fs_mod,
    2, 8, 19, 1, 8, 50,
    NULL, NULL, &fs_reset, NULL, &fs_attach, &fs_detach,
    NULL, DEV_DISABLE | DEV_DEBUG
};

#define CARD_LEN 120
enum {
    FS_IDLE,
    FS_STARTING,
    FS_BINARY,
    FS_RUNNING,
    FS_IMAGE,
    FS_IMAGE_LAST = FS_IMAGE + CARD_LEN - 1,
    FS_TOOLONG,
    FS_FILLUP,
    FS_FILLUP_LAST = FS_FILLUP + CARD_LEN - 1,
    FS_ENDA3,
    FS_ENDA3_LAST = FS_ENDA3 + CARD_LEN - 1,
    FS_TAIL,
} fs_state[2];

int fs_textmode[2];

/*
 * Reset routine
 */
t_stat fs_reset (DEVICE *dptr)
{
    sim_cancel (&fs_unit[0]);
    sim_cancel (&fs_unit[1]);
    fs_state[0] = fs_state[1] = FS_IDLE;
    DIS_RDY(FS1_READY | FS2_READY);
    if (fs_unit[0].flags & UNIT_ATT)
        ENB_RDY(FS1_READY);
    if (fs_unit[1].flags & UNIT_ATT)
        ENB_RDY(FS2_READY);
    return SCPE_OK;
}

/*
 * Attaches a raw binary file by default,
 * with a -t switch attaches a prepared text file in UTF-8.
 */
t_stat fs_attach (UNIT *u, CONST char *cptr)
{
    t_stat s;
    int num = u - fs_unit;
    fs_textmode[num] = sim_switches & SWMASK('T');
    sim_switches &= ~SWMASK('T');
    s = attach_unit (u, cptr);
    if (s != SCPE_OK)
        return s;
    isfifo[num] = (0 == sim_set_fifo_nonblock (u->fileref));
    ENB_RDY(FS1_READY >> num);
    return SCPE_OK;
}

t_stat fs_detach (UNIT *u)
{
    int num = u - fs_unit;
    DIS_RDY(FS1_READY >> num);
    return detach_unit (u);
}

/*
 * Управление двигателем, лампой, протяжкой
 */
void fs_control (int num, uint32 cmd)
{
    UNIT *u = &fs_unit[num];

    static int bytecnt = 0;
    if (fs_dev.dctrl)
        besm6_debug("<<< ФС1500-%d команда %o", num, cmd);
    if (! IS_RDY(FS1_READY >> num)) {
        if (fs_dev.dctrl)
            besm6_debug("<<< ФС1500-%d не готово", num);
        return;
    }
    switch (cmd) {
    case 0:         /* полное выключение */
        sim_cancel (u);
        fs_state[num] = FS_IDLE;
        if (fs_dev.dctrl)
            besm6_debug("<<<ФС1500-%d ВЫКЛ..", num);
        bytecnt = 0;
        break;
    case 4:         /* двигатель без протяжки */
        fs_state[num] = FS_STARTING;
        if (fs_dev.dctrl)
            besm6_debug("<<<ФС1500-%d ВКЛ.", num);
        sim_cancel (u);
        break;
    case 5:         /* протяжка */
        if (fs_state[num] == FS_IDLE)
            besm6_debug("<<< ФС1500-%d протяжка без мотора", num);
        else if (fs_state[num] != FS_TAIL) {
            sim_activate (u, FS_RATE);
            bytecnt++;
        } else {
            if (! isfifo[num]) {
                fs_detach(u);
                fs_state[num] = FS_IDLE;
            }
        }
        break;
    default:
        besm6_debug ("<<< ФС1500-%d неизвестная команда %o", num, cmd);
    }
    if (cmd && fs_dev.dctrl) {
        besm6_debug("<<<ФС1500-%d: %d симв.", num, bytecnt);
    }
}

unsigned char unicode_to_gost (unsigned short val);

/*
 * The UPP code is the GOST 10859 code with odd parity.
 * UPP stood for "unit for preparation of punchards".
 */
static unsigned char unicode_to_upp (unsigned short ch) {
    unsigned char ret;
    ch = ret = unicode_to_gost (ch);
    ch = (ch & 0x55) + ((ch >> 1) & 0x55);
    ch = (ch & 0x33) + ((ch >> 2) & 0x33);
    ch = (ch & 0x0F) + ((ch >> 4) & 0x0F);
    return (ch & 1) ? ret : ret | 0x80;
}

int utf8_getc (FILE *fin);

/*
 * Событие: читаем очередной символ с перфоленты в регистр.
 * Устанавливаем флаг прерывания.
 */
t_stat fs_event (UNIT *u)
{
    int num = u - fs_unit;
  again:
    if (fs_state[num] == FS_STARTING) {
        /* The first interrupt after starting the motor is dummy,
         * no need to read anything from the attached file.
         */
        FS[num] = 0;
        fs_state[num] = fs_textmode[num] ? FS_RUNNING : FS_BINARY;
    } else if (fs_state[num] == FS_BINARY) {
        int ch = getc (u->fileref);
        if (ch < 0) {
            FS[num] = 0;
            fs_state[num] = FS_TAIL;
        } else {
            FS[num] = ch;
        }
    } else if (fs_state[num] == FS_RUNNING) {
        int ch;
        /* Line separators are ignored in running text mode */
        do ch = utf8_getc (u->fileref); while (ch == '\n' || ch == '\r');
        if (ch < 0) {
            /* the tail end of the tape has no holes */
            FS[num] = 0;
            fs_state[num] = FS_TAIL;
        } else if (ch == (']' & 037)) {
            /* Switching from running text mode to "virtual punchcard" mode and back
             * is done with an ASCII GS (group separator) symbol ctrl-].
             */
            fs_state[num] = FS_IMAGE;
            goto again;
        } else {
            FS[num] = unicode_to_upp (ch);
        }
    } else if (FS_IMAGE <= fs_state[num] && fs_state[num] <= FS_IMAGE_LAST) {
        int ch = utf8_getc (u->fileref);
        if (ch < 0) {
            /* premature end of tape */
            FS[num] = 0;
            fs_state[num] = FS_TAIL;
        } else if (ch == '\r') {
            /* always ignored */
            goto again;
        } else if (ch == '\n') {
            /* Start returning zero bytes up to the end of the current "virtual punchard" */
            fs_state[num] = FS_FILLUP + (fs_state[num] - FS_IMAGE);
            goto again;
        } else if (ch == (']' & 037)) {
            if (fs_state[num] != FS_IMAGE)
                besm6_debug("<<< ENDA3 requested mid-card?");
            fs_state[num] = FS_ENDA3;
            goto again;
        } else {
            FS[num] = unicode_to_upp (ch);
            if (++fs_state[num] == FS_TOOLONG) {
                /* If a line is too long (> 120 chars), start the next "virtual punchcard" */
                fs_state[num] = FS_IMAGE;
            }
        }
    } else if (FS_FILLUP <= fs_state[num] && fs_state[num] <= FS_FILLUP_LAST) {
        FS[num] = 0;
        if (++fs_state[num] == FS_ENDA3) {
            fs_state[num] = FS_IMAGE;
        }
    } else if (FS_ENDA3 <= fs_state[num] && fs_state[num] <= FS_ENDA3_LAST) {
        if ((fs_state[num] - FS_ENDA3) % 5 == 0)
            FS[num] = 0200;
        else
            FS[num] = 0;
        if (++fs_state[num] == FS_TAIL) {
            fs_state[num] = FS_RUNNING;
        }
    } else if (fs_state[num] == FS_IDLE || fs_state[num] == FS_TAIL) {
        FS[num] = 0;
    }
    GRP |= GRP_FS1_SYNC >> num;
    return SCPE_OK;
}

int fs_read(int num) {
    if (fs_dev.dctrl)
        besm6_debug("<<< ФС1500-%d: байт %03o", num, FS[num]);

    return FS[num];
}

/*
 * Unlike the OS which uses GOST overline (approximated by ^) as a line separator
 * in running text mode, the BESM-ALGOL programming system used a nonprintable
 * character (0174) from the unused part of the codetable to allow compressing multiple
 * source lines on a punchcard. To specify that character,
 * we use ASCII RS (record separator) symbol ctrl-^.
 */
unsigned char
unicode_to_gost (unsigned short val)
{
    static const unsigned char tab0 [256] = {
        /* 00 - 07 */   017,    017,    017,    017,    017,    017,    017,    017,
        /* 08 - 0f */   017,    017,    0214,   017,    017,    017,    017,    017,
        /* 10 - 17 */   017,    017,    017,    017,    017,    017,    017,    017,
        /* 18 - 1f */   017,    017,    017,    017,    017,    017,    0174,   017,
        /*  !"#$%&' */  0017,   0133,   0134,   0034,   0127,   0126,   0121,   0033,
        /* ()*+,-./ */  0022,   0023,   0031,   0012,   0015,   0013,   0016,   0014,
        /* 01234567 */  0000,   0001,   0002,   0003,   0004,   0005,   0006,   0007,
        /* 89:;<=>? */  0010,   0011,   0037,   0026,   0035,   0025,   0036,   0136,
        /* @ABCDEFG */  0021,   0040,   0042,   0061,   0077,   0045,   0100,   0101,
        /* HIJKLMNO */  0055,   0102,   0103,   0052,   0104,   0054,   0105,   0056,
        /* PQRSTUVW */  0060,   0106,   0107,   0110,   0062,   0111,   0112,   0113,
        /* XYZ[\]^_ */  0065,   0063,   0114,   0027,   017,    0030,   0115,   0132,
        /* `abcdefg */  0032,   0040,   0042,   0061,   0077,   0045,   0100,   0101,
        /* hijklmno */  0055,   0102,   0103,   0052,   0104,   0054,   0105,   0056,
        /* pqrstuvw */  0060,   0106,   0107,   0110,   0062,   0111,   0112,   0113,
        /* xyz{|}~  */  0065,   0063,   0114,   017,    0130,   017,    0123,   017,
        /* 80 - 87 */   017,    017,    017,    017,    017,    017,    017,    017,
        /* 88 - 8f */   017,    017,    017,    017,    017,    017,    017,    017,
        /* 90 - 97 */   017,    017,    017,    017,    017,    017,    017,    017,
        /* 98 - 9f */   017,    017,    017,    017,    017,    017,    017,    017,
        /* a0 - a7 */   017,    017,    017,    017,    017,    017,    017,    017,
        /* a8 - af */   017,    017,    017,    017,    0123,   017,    017,    017,
        /* b0 - b7 */   0136,   017,    017,    017,    017,    017,    017,    017,
        /* b8 - bf */   017,    017,    017,    017,    017,    017,    017,    017,
        /* c0 - c7 */   017,    017,    017,    017,    017,    017,    017,    017,
        /* c8 - cf */   017,    017,    017,    017,    017,    017,    017,    017,
        /* d0 - d7 */   017,    017,    017,    017,    017,    017,    017,    0024,
        /* d8 - df */   017,    017,    017,    017,    017,    017,    017,    017,
        /* e0 - e7 */   017,    017,    017,    017,    017,    017,    017,    017,
        /* e8 - ef */   017,    017,    017,    017,    017,    017,    017,    017,
        /* f0 - f7 */   017,    017,    017,    017,    017,    017,    017,    0124,
        /* f8 - ff */   017,    017,    017,    017,    017,    017,    017,    017,
    };
    switch (val >> 8) {
    case 0x00:
        return tab0 [val];
    case 0x04:
        switch ((unsigned char) val) {
        case 0x10: return 0040;
        case 0x11: return 0041;
        case 0x12: return 0042;
        case 0x13: return 0043;
        case 0x14: return 0044;
        case 0x15: return 0045;
        case 0x16: return 0046;
        case 0x17: return 0047;
        case 0x18: return 0050;
        case 0x19: return 0051;
        case 0x1a: return 0052;
        case 0x1b: return 0053;
        case 0x1c: return 0054;
        case 0x1d: return 0055;
        case 0x1e: return 0056;
        case 0x1f: return 0057;
        case 0x20: return 0060;
        case 0x21: return 0061;
        case 0x22: return 0062;
        case 0x23: return 0063;
        case 0x24: return 0064;
        case 0x25: return 0065;
        case 0x26: return 0066;
        case 0x27: return 0067;
        case 0x28: return 0070;
        case 0x29: return 0071;
        case 0x2a: return 0135;
        case 0x2b: return 0072;
        case 0x2c: return 0073;
        case 0x2d: return 0074;
        case 0x2e: return 0075;
        case 0x2f: return 0076;
        case 0x30: return 0040;
        case 0x31: return 0041;
        case 0x32: return 0042;
        case 0x33: return 0043;
        case 0x34: return 0044;
        case 0x35: return 0045;
        case 0x36: return 0046;
        case 0x37: return 0047;
        case 0x38: return 0050;
        case 0x39: return 0051;
        case 0x3a: return 0052;
        case 0x3b: return 0053;
        case 0x3c: return 0054;
        case 0x3d: return 0055;
        case 0x3e: return 0056;
        case 0x3f: return 0057;
        case 0x40: return 0060;
        case 0x41: return 0061;
        case 0x42: return 0062;
        case 0x43: return 0063;
        case 0x44: return 0064;
        case 0x45: return 0065;
        case 0x46: return 0066;
        case 0x47: return 0067;
        case 0x48: return 0070;
        case 0x49: return 0071;
        case 0x4a: return 0135;
        case 0x4b: return 0072;
        case 0x4c: return 0073;
        case 0x4d: return 0074;
        case 0x4e: return 0075;
        case 0x4f: return 0076;
        }
        break;
    case 0x20:
        switch ((unsigned char) val) {
        case 0x15: return 0131;
        case 0x18: return 0032;
        case 0x19: return 0033;
        case 0x32: return 0137;
        case 0x3e: return 0115;
        }
        break;
    case 0x21:
        switch ((unsigned char) val) {
        case 0x2f: return 0020;
        case 0x91: return 0021;
        }
        break;
    case 0x22:
        switch ((unsigned char) val) {
        case 0x27: return 0121;
        case 0x28: return 0120;
        case 0x60: return 0034;
        case 0x61: return 0125;
        case 0x64: return 0116;
        case 0x65: return 0117;
        case 0x83: return 0122;
        }
        break;
    case 0x23:
        switch ((unsigned char) val) {
        case 0xe8: return 0020;
        }
        break;
    case 0x25:
        switch ((unsigned char) val) {
        case 0xc7: return 0127;
        case 0xca: return 0127;
        }
        break;
    }
    return 017;
}

/*
 * Read Unicode symbol from file.
 * Convert from UTF-8 encoding.
 */
int
utf8_getc (FILE *fin)
{
    int c1, c2, c3;
  again:
    c1 = getc (fin);
    if (c1 < 0 || ! (c1 & 0x80))
        return c1;
    c2 = getc (fin);
    if (! (c1 & 0x20))
        return (c1 & 0x1f) << 6 | (c2 & 0x3f);
    c3 = getc (fin);
    if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) {
        /* Skip zero width no-break space. */
        goto again;
    }
    return (c1 & 0x0f) << 12 | (c2 & 0x3f) << 6 | (c3 & 0x3f);
}
