/*
 * besm6_sys.c: BESM-6 simulator interface
 *
 * Copyright (c) 2009, Serge Vakulenko
 * 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.
 *
 * This file implements three essential functions:
 *
 * sim_load()   - loading and dumping memory and CPU state
 *                in a way, specific for BESM-6 architecture
 * fprint_sym() - print a machune instruction using
 *                opcode mnemonic or in a digital format
 * parse_sym()  - scan a string and build an instruction
 *                word from it
 */
#include "besm6_defs.h"
#include <math.h>

const char *opname_short_bemsh [64] = {
    "зп",  "зпм", "рег", "счм", "сл",  "вч",  "вчоб","вчаб",
    "сч",  "и",   "нтж", "слц", "знак","или", "дел", "умн",
    "сбр", "рзб", "чед", "нед", "слп", "вчп", "сд",  "рж",
    "счрж","счмр","э32", "увв", "слпа","вчпа","сда", "ржа",
    "уи",  "уим", "счи", "счим","уии", "сли", "э46", "э47",
    "э50", "э51", "э52", "э53", "э54", "э55", "э56", "э57",
    "э60", "э61", "э62", "э63", "э64", "э65", "э66", "э67",
    "э70", "э71", "э72", "э73", "э74", "э75", "э76", "э77",
};

static const char *opname_long_bemsh [16] = {
    "э20", "э21", "мода","мод", "уиа", "слиа","по",  "пе",
    "пб",  "пв",  "выпр","стоп","пио", "пино","э36", "цикл",
};

const char *opname_short_madlen [64] = {
    "atx",  "stx",  "mod",  "xts",  "a+x",  "a-x",  "x-a",  "amx",
    "xta",  "aax",  "aex",  "arx",  "avx",  "aox",  "a/x",  "a*x",
    "apx",  "aux",  "acx",  "anx",  "e+x",  "e-x",  "asx",  "xtr",
    "rte",  "yta",  "*32",  "ext",  "e+n",  "e-n",  "asn",  "ntr",
    "ati",  "sti",  "ita",  "its",  "mtj",  "j+m",  "*46",  "*47",
    "*50",  "*51",  "*52",  "*53",  "*54",  "*55",  "*56",  "*57",
    "*60",  "*61",  "*62",  "*63",  "*64",  "*65",  "*66",  "*67",
    "*70",  "*71",  "*72",  "*73",  "*74",  "*75",  "*76",  "*77",
};

static const char *opname_long_madlen [16] = {
    "*20",  "*21",  "utc",  "wtc",  "vtm",  "utm",  "uza",  "u1a",
    "uj",   "vjm",  "ij",   "stop", "vzm",  "v1m",  "*36",  "vlm",
};

/*
 * Выдача мнемоники по коду инструкции.
 * Код должен быть в диапазоне 000..077 или 0200..0370.
 */
const char *besm6_opname (int opcode)
{
    if (sim_switches & SWMASK ('L')) {
        /* Latin mnemonics. */
        if (opcode & 0200)
            return opname_long_madlen [(opcode >> 3) & 017];
        return opname_short_madlen [opcode];
    }
    if (opcode & 0200)
        return opname_long_bemsh [(opcode >> 3) & 017];
    return opname_short_bemsh [opcode];
}

/*
 * Выдача кода инструкции по мнемонике (UTF-8).
 */
int besm6_opcode (char *instr)
{
    int i;

    for (i=0; i<64; ++i)
        if (strcmp (opname_short_bemsh[i], instr) == 0 ||
            strcmp (opname_short_madlen[i], instr) == 0)
            return i;
    for (i=0; i<16; ++i)
        if (strcmp (opname_long_bemsh[i], instr) == 0 ||
            strcmp (opname_long_madlen[i], instr) == 0)
            return (i << 3) | 0200;
    return -1;
}

/*
 * Выдача на консоль и в файл протокола.
 * Если первый символ формата - подчерк, на консоль не печатаем.
 * Добавляет перевод строки.
 */
void besm6_log (const char *fmt, ...)
{
    va_list args;

    if (*fmt == '_')
        ++fmt;
    else {
        va_start (args, fmt);
        vprintf (fmt, args);
        printf ("\r\n");
        va_end (args);
    }
    if (sim_log) {
        va_start (args, fmt);
        vfprintf (sim_log, fmt, args);
        if (sim_log == stdout)
            fprintf (sim_log, "\r");
        fprintf (sim_log, "\n");
        fflush (sim_log);
        va_end (args);
    }
}

/*
 * Не добавляет перевод строки.
 */
void besm6_log_cont (const char *fmt, ...)
{
    va_list args;

    if (*fmt == '_')
        ++fmt;
    else {
        va_start (args, fmt);
        vprintf (fmt, args);
        va_end (args);
    }
    if (sim_log) {
        va_start (args, fmt);
        vfprintf (sim_log, fmt, args);
        fflush (sim_log);
        va_end (args);
    }
}

/*
 * Выдача на консоль и в файл отладки: если включён режим "cpu debug".
 * Добавляет перевод строки.
 */
void besm6_debug (const char *fmt, ...)
{
    va_list args;

    va_start (args, fmt);
    vprintf (fmt, args);
    printf ("\r\n");
    va_end (args);
    if (sim_deb && sim_deb != stdout) {
        va_start (args, fmt);
        vfprintf (sim_deb, fmt, args);
        fprintf (sim_deb, "\n");
        fflush (sim_deb);
        va_end (args);
    }
}

/*
 * Преобразование вещественного числа в формат БЭСМ-6.
 *
 * Представление чисел в IEEE 754 (double):
 *      64   63———53 52————–1
 *      знак порядок мантисса
 * Старший (53-й) бит мантиссы не хранится и всегда равен 1.
 *
 * Представление чисел в БЭСМ-6:
 *      48——–42 41   40————————————————–1
 *      порядок знак мантисса в доп. коде
 */
t_value ieee_to_besm6 (double d)
{
    t_value word;
    int exponent;
    int sign;

    sign = d < 0;
    if (sign)
        d = -d;
    d = frexp (d, &exponent);
    /* 0.5 <= d < 1.0 */
    d = ldexp (d, 40);
    word = (t_value)d;
    if (d - word >= 0.5)
        word += 1;                      /* Округление. */
    if (exponent < -64)
        return 0LL;                     /* Близкое к нулю число */
    if (exponent > 63) {
        return sign ?
            0xFEFFFFFFFFFFLL :      /* Максимальное число */
            0xFF0000000000LL;       /* Минимальное число */
    }
    if (sign)
        word = 0x20000000000LL-word;    /* Знак. */
    word |= ((t_value) (exponent + 64)) << 41;
    return word;
}

double besm6_to_ieee (t_value word)
{
    double mantissa;
    int exponent;

    /* Убираем свертку */
    word &= BITS48;

    /* Сдвигаем так, чтобы знак мантиссы пришелся на знак целого;
     * таким образом, mantissa равно исходной мантиссе, умноженной на 2**63.
     */
    mantissa = (double)(((t_int64) word) << (64 - 48 + 7));

    exponent = word >> 41;

    /* Порядок смещен вверх на 64, и мантиссу нужно скорректировать */
    return ldexp (mantissa, exponent - 64 - 63);
}

/*
 * Пропуск пробелов.
 */
CONST char *skip_spaces (CONST char *p)
{
    for (;;) {
        if (*p == (char) 0xEF && p[1] == (char) 0xBB && p[2] == (char) 0xBF) {
            /* Skip zero width no-break space. */
            p += 3;
            continue;
        }
        if (*p == ' ' || *p == '\t' || *p == '\r') {
            ++p;
            continue;
        }
        return p;
    }
}

/*
 * Fetch Unicode symbol from UTF-8 string.
 * Advance string pointer.
 */
int utf8_to_unicode (CONST char **p)
{
    int c1, c2, c3;

    c1 = (unsigned char) *(*p)++;
    if (! (c1 & 0x80))
        return c1;
    c2 = (unsigned char) *(*p)++;
    if (! (c1 & 0x20))
        return (c1 & 0x1f) << 6 | (c2 & 0x3f);
    c3 = (unsigned char) *(*p)++;
    return (c1 & 0x0f) << 12 | (c2 & 0x3f) << 6 | (c3 & 0x3f);
}

char *besm6_parse_octal (const char *cptr, int *offset)
{
    char *eptr;

    *offset = strtol (cptr, &eptr, 8);
    if (eptr == cptr)
        return 0;
    return eptr;
}

static CONST char *get_alnum (CONST char *iptr, char *optr)
{
    while ((*iptr >= 'a' && *iptr<='z') ||
           (*iptr >= 'A' && *iptr<='Z') ||
           (*iptr >= '0' && *iptr<='9') || (*iptr & 0x80)) {
        *optr++ = *iptr++;
    }
    *optr = 0;
    return iptr;
}

/*
 * Parse single instruction (half word).
 * Allow mnemonics or octal code.
 */
CONST char *parse_instruction (CONST char *cptr, uint32 *val)
{
    int opcode, reg, addr, negate;
    char gbuf[CBUFSIZE];

    cptr = skip_spaces (cptr);                      /* absorb spaces */
    if (*cptr >= '0' && *cptr <= '7') {
        /* Восьмеричное представление. */
        cptr = besm6_parse_octal (cptr, &reg);  /* get register */
        if (! cptr || reg > 15) {
            /*printf ("Bad register\n");*/
            return 0;
        }
        cptr = skip_spaces (cptr);              /* absorb spaces */
        if (*cptr == '2' || *cptr == '3') {
            /* Длинная команда. */
            cptr = besm6_parse_octal (cptr, &opcode);
            if (! cptr || opcode < 020 || opcode > 037) {
                /*printf ("Bad long opcode\n");*/
                return 0;
            }
            opcode <<= 3;
        } else {
            /* Короткая команда. */
            cptr = besm6_parse_octal (cptr, &opcode);
            if (! cptr || opcode > 0177) {
                /*printf ("Bad short opcode\n");*/
                return 0;
            }
        }
        cptr = besm6_parse_octal (cptr, &addr); /* get address */
        if (! cptr || addr > BITS(15) ||
            (opcode <= 0177 && addr > BITS(12))) {
            /*printf ("Bad address\n");*/
            return 0;
        }
    } else {
        /* Мнемоническое представление команды. */
        cptr = get_alnum (cptr, gbuf);          /* get opcode */
        opcode = besm6_opcode (gbuf);
        if (opcode < 0) {
            /*printf ("Bad opname: %s\n", gbuf);*/
            return 0;
        }
        negate = 0;
        cptr = skip_spaces (cptr);              /* absorb spaces */
        if (*cptr == '-') {                     /* negative offset */
            negate = 1;
            cptr = skip_spaces (cptr + 1);  /* absorb spaces */
        }
        addr = 0;
        if (*cptr >= '0' && *cptr <= '7') {
            /* Восьмеричный адрес. */
            cptr = besm6_parse_octal (cptr, &addr);
            if (! cptr || addr > BITS(15)) {
                /*printf ("Bad address: %o\n", addr);*/
                return 0;
            }
            if (negate)
                addr = (- addr) & BITS(15);
            if (opcode <= 077 && addr > BITS(12)) {
                if (addr < 070000) {
                    /*printf ("Bad short address: %o\n", addr);*/
                    return 0;
                }
                opcode |= 0100;
                addr &= BITS(12);
            }
        }
        reg = 0;
        cptr = skip_spaces (cptr);              /* absorb spaces */
        if (*cptr == '(') {
            /* Индекс-регистр в скобках. */
            cptr = besm6_parse_octal (cptr+1, &reg);
            if (! cptr || reg > 15) {
                /*printf ("Bad register: %o\n", reg);*/
                return 0;
            }
            cptr = skip_spaces (cptr);      /* absorb spaces */
            if (*cptr != ')') {
                /*printf ("No closing brace\n");*/
                return 0;
            }
            ++cptr;
        }
    }
    *val = reg << 20 | opcode << 12 | addr;
    return cptr;
}

/*
 * Instruction parse: two commands per word.
 */
t_stat parse_instruction_word (CONST char *cptr, t_value *val)
{
    uint32 left, right;

    *val = 0;
    cptr = parse_instruction (cptr, &left);
    if (! cptr)
        return SCPE_ARG;
    right = 0;
    cptr = skip_spaces (cptr);
    if (*cptr == ',') {
        cptr = parse_instruction (cptr + 1, &right);
        if (! cptr)
            return SCPE_ARG;
    }
    cptr = skip_spaces (cptr);                      /* absorb spaces */
    if (*cptr != 0 && *cptr != ';' && *cptr != '\n' && *cptr != '\r') {
        /*printf ("Extra symbols at eoln: %s\n", cptr);*/
        return SCPE_2MARG;
    }
    *val = (t_value) left << 24 | right;
    return SCPE_OK;
}

/*
 * Печать машинной инструкции с мнемоникой.
 */
void besm6_fprint_cmd (FILE *of, uint32 cmd)
{
    int reg, opcode, addr;

    reg = (cmd >> 20) & 017;
    if (cmd & BBIT(20)) {
        opcode = (cmd >> 12) & 0370;
        addr = cmd & BITS(15);
    } else {
        opcode = (cmd >> 12) & 077;
        addr = cmd & 07777;
        if (cmd & BBIT(19))
            addr |= 070000;
    }
    fprintf (of, "%s", besm6_opname (opcode));
    if (addr) {
        fprintf (of, " ");
        if (addr >= 077700)
            fprintf (of, "-%o", (addr ^ 077777) + 1);
        else
            fprintf (of, "%o", addr);
    }
    if (reg) {
        if (! addr)
            fprintf (of, " ");
        fprintf (of, "(%o)", reg);
    }
}

/*
 * Печать машинной инструкции в восьмеричном виде.
 */
void besm6_fprint_insn (FILE *of, uint32 insn)
{
    if (insn & BBIT(20))
        fprintf (of, "%02o %02o %05o ",
                 insn >> 20, (insn >> 15) & 037, insn & BITS(15));
    else
        fprintf (of, "%02o %03o %04o ",
                 insn >> 20, (insn >> 12) & 0177, insn & 07777);
}

/*
 * Symbolic decode
 *
 * Inputs:
 *      *of     = output stream
 *      addr    = current PC
 *      *val    = pointer to data
 *      *uptr   = pointer to unit
 *      sw      = switches
 * Outputs:
 *      return  = status code
 */
t_stat fprint_sym (FILE *of, t_addr addr, t_value *val,
                   UNIT *uptr, int32 sw)
{
    t_value cmd;

    if (uptr && (uptr != &cpu_unit))                /* must be CPU */
        return SCPE_ARG;

    cmd = val[0];


    if (sw & SWMASK ('M')) {                        /* symbolic decode? */
        if (sw & SIM_SW_STOP && addr == PC && !(RUU & RUU_RIGHT_INSTR))
            fprintf (of, "-> ");
        besm6_fprint_cmd (of, (uint32)(cmd >> 24));
        if (sw & SIM_SW_STOP)                   /* stop point */
            fprintf (of, ", ");
        else
            fprintf (of, ",\n\t");
        if (sw & SIM_SW_STOP && addr == PC && (RUU & RUU_RIGHT_INSTR))
            fprintf (of, "-> ");
        besm6_fprint_cmd (of, cmd & BITS(24));

    } else if (sw & SWMASK ('I')) {
        besm6_fprint_insn (of, (cmd >> 24) & BITS(24));
        besm6_fprint_insn (of, cmd & BITS(24));
    } else if (sw & SWMASK ('F')) {
        fprintf (of, "%#.2g", besm6_to_ieee(cmd));
    } else if (sw & SWMASK ('B')) {
        fprintf (of, "%03o %03o %03o %03o %03o %03o",
                 (int) (cmd >> 40) & 0377,
                 (int) (cmd >> 32) & 0377,
                 (int) (cmd >> 24) & 0377,
                 (int) (cmd >> 16) & 0377,
                 (int) (cmd >> 8) & 0377,
                 (int) cmd & 0377);
    } else if (sw & SWMASK ('X')) {
        fprintf (of, "%013llx", cmd);
    } else
        fprintf (of, "%04o %04o %04o %04o",
                 (int) (cmd >> 36) & 07777,
                 (int) (cmd >> 24) & 07777,
                 (int) (cmd >> 12) & 07777,
                 (int) cmd & 07777);
    return SCPE_OK;
}

/*
 * Symbolic input
 *
 * Inputs:
 *      *cptr   = pointer to input string
 *      addr    = current PC
 *      *uptr   = pointer to unit
 *      *val    = pointer to output values
 *      sw      = switches
 * Outputs:
 *      status  = error status
 */
t_stat parse_sym (CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)
{
    int32 i;

    if (uptr && (uptr != &cpu_unit))                /* must be CPU */
        return SCPE_ARG;
    if (! parse_instruction_word (cptr, val))       /* symbolic parse? */
        return SCPE_OK;

    val[0] = 0;
    for (i=0; i<16; i++) {
        if (*cptr < '0' || *cptr > '7')
            break;
        val[0] = (val[0] << 3) | (*cptr - '0');
        cptr = skip_spaces (cptr+1);            /* next char */
    }
    if (*cptr != 0 && *cptr != ';' && *cptr != '\n' && *cptr != '\r') {
        /*printf ("Extra symbols at eoln: %s\n", cptr);*/
        return SCPE_2MARG;
    }
    return SCPE_OK;
}

/*
 * Чтение строки входного файла.
 * Форматы строк:
 * п 76543                     - адрес пуска
 * в 12345                     - адрес ввода
 * ч -123.45e+6                - вещественное число
 * с 0123 4567 0123 4567       - восьмеричное слово
 * к 00 22 00000 00 010 0000   - команды
 */
t_stat besm6_read_line (FILE *input, int *type, t_value *val)
{
    char buf [512];
    CONST char *p;
    int i, c;
  again:
    if (! fgets (buf, sizeof (buf), input)) {
        *type = 0;
        return SCPE_OK;
    }
    p = skip_spaces (buf);
    if (*p == '\n' || *p == ';')
        goto again;
    c = utf8_to_unicode (&p);
    if (c == CYRILLIC_SMALL_LETTER_VE ||
        c == CYRILLIC_CAPITAL_LETTER_VE ||
        c == 'b' || c == 'B') {
        /* Адрес размещения данных. */
        *type = ':';
        *val = strtol (p, 0, 8);
        return SCPE_OK;
    }
    if (c == CYRILLIC_SMALL_LETTER_PE ||
        c == CYRILLIC_CAPITAL_LETTER_PE ||
        c == 'p' || c == 'P') {
        /* Стартовый адрес. */
        *type = '@';
        *val = strtol (p, 0, 8);
        return SCPE_OK;
    }
    if (c == CYRILLIC_SMALL_LETTER_CHE ||
        c == CYRILLIC_CAPITAL_LETTER_CHE ||
        c == 'f' || c == 'F') {
        /* Вещественное число. */
        *type = '=';
        *val = ieee_to_besm6 (strtod (p, 0));
        return SCPE_OK;
    }
    if (c == CYRILLIC_SMALL_LETTER_ES ||
        c == CYRILLIC_CAPITAL_LETTER_ES ||
        c == 'c' || c == 'C') {
        /* Восьмеричное слово. */
        *type = '=';
        *val = 0;
        for (i=0; i<16; ++i) {
            p = skip_spaces (p);
            if (*p < '0' || *p > '7') {
                if (i == 0) {
                    /* слишком короткое слово */
                    goto bad;
                }
                break;
            }
            *val = *val << 3 | (*p++ - '0');
        }
        return SCPE_OK;
    }
    if (c == CYRILLIC_SMALL_LETTER_KA ||
        c == CYRILLIC_CAPITAL_LETTER_KA ||
        c == 'k' || c == 'K') {
        /* Команда. */
        *type = '*';
        if (parse_instruction_word (p, val) != SCPE_OK)
            goto bad;
        return SCPE_OK;
    }
    /* Неверная строка входного файла */
  bad:    besm6_log ("Invalid input line: %s", buf);
    return SCPE_FMT;
}

/*
 * Load memory from file.
 */
t_stat besm6_load (FILE *input)
{
    int addr, type;
    t_value word;
    t_stat err;

    addr = 1;
    PC = 1;
    for (;;) {
        err = besm6_read_line (input, &type, &word);
        if (err)
            return err;
        switch (type) {
        case 0:                 /* EOF */
            return SCPE_OK;
        case ':':               /* address */
            addr = (int)word;
            break;
        case '=':               /* word */
            if (addr < 010)
                pult [0][addr] = SET_PARITY (word, PARITY_NUMBER);
            else
                memory [addr] = SET_PARITY (word, PARITY_NUMBER);
            ++addr;
            break;
        case '*':               /* instruction */
            if (addr < 010)
                pult [0][addr] = SET_PARITY (word, PARITY_INSN);
            else
                memory [addr] = SET_PARITY (word, PARITY_INSN);
            ++addr;
            break;
        case '@':               /* start address */
            PC = (uint32)word;
            break;
        }
        if (addr > MEMSIZE)
            return SCPE_FMT;
    }
    return SCPE_OK;
}

/*
 * Dump memory to file.
 */
t_stat besm6_dump (FILE *of, const char *fnam)
{
    int addr, last_addr = -1;
    t_value word;

    fprintf (of, "; %s\n", fnam);
    for (addr=1; addr<MEMSIZE; ++addr) {
        if (addr < 010)
            word = pult [0][addr];
        else
            word = memory [addr];
        if (word == 0)
            continue;
        if (addr != last_addr+1) {
            fprintf (of, "\nв %05o\n", addr);
        }
        last_addr = addr;
        if (IS_INSN (word)) {
            fprintf (of, "к ");
            besm6_fprint_cmd (of, (uint32)(word >> 24));
            fprintf (of, ", ");
            besm6_fprint_cmd (of, word & BITS(24));
            fprintf (of, "\t\t; %05o - ", addr);
            fprintf (of, "%04o %04o %04o %04o\n",
                     (int) (word >> 36) & 07777,
                     (int) (word >> 24) & 07777,
                     (int) (word >> 12) & 07777,
                     (int) word & 07777);
        } else {
            fprintf (of, "с %04o %04o %04o %04o",
                     (int) (word >> 36) & 07777,
                     (int) (word >> 24) & 07777,
                     (int) (word >> 12) & 07777,
                     (int) word & 07777);
            fprintf (of, "\t\t; %05o\n", addr);
        }
    }
    return SCPE_OK;
}

/*
 * Loader/dumper
 */
t_stat sim_load (FILE *fi, CONST char *cptr, CONST char *fnam, int dump_flag)
{
    if (dump_flag)
        return besm6_dump (fi, fnam);

    return besm6_load (fi);
}
