/* Eldo .SETBUS / .SIGBUS: drive a whole bus from a list of values.
 *
 *   .SETBUS bname node ... node                 (most significant bit FIRST)
 *   .SIGBUS bname|bname<msb:lsb>|bname[msb:lsb]
 *   +   [VHI=] [VLO=] [TRISE=] [TFALL=] THOLD= [TDELAY=]
 *   +   [BASE=HEX|BIN|DEC] PATTERN word ... word [P[=0|1]]
 *
 * Each card expands to one PATTERN source per bit, which inppat.c then turns
 * into a pwl. So the bus form is a front door onto the single-bit generator
 * rather than a second implementation, and the two cannot drift apart.
 *
 * The timing is the single-bit timing: THOLD is the 50%-to-50% period of each
 * word ("Each bus signal has period PER (THOLD)", Eldo User's Manual), the
 * edge is centred on the boundary, and TRISE/TFALL are full 0->100% times.
 *
 * Bit order follows the declaration: the most significant bit is named first,
 * so <13:0> and <0:13> mean opposite things and both occur in real decks. A
 * value narrower than the bus is zero-extended at the top -- a 45-bit bus is
 * routinely written with 11 hex digits. A value that does not FIT is an error,
 * as it is in Eldo ("Unable to code 0XF on 3 bits"); masking the high bits off
 * would change the stimulus without saying so.
 *
 * Defaults are Eldo's, and they are not all zero: VHI=5, VLO=0,
 * TRISE=TFALL=2ns, BASE=DEC, TDELAY=0. THOLD has no default and is required.
 *
 * Refused rather than approximated, each with an error naming file and line:
 *   - SIGNED=, BASE=OCT, BASE=THERM, FILE=, a Z bit, the time-value-pair form
 *     of .SIGBUS, $(param) patterns and the HX[]/BX[] per-value bases;
 *   - the abbreviated bname<msb>:<lsb> spelling without brackets, which the
 *     Eldo manual itself warns is ambiguous when the name ends in a digit.
 * None of these occurs in the decks this was written for.
 */

#include "ngspice/ngspice.h"

#include "ngspice/cpdefs.h"
#include "ngspice/dstring.h"
#include "ngspice/fteinp.h"
#include "ngspice/stringskip.h"
#include "ngspice/stringutil.h"

#include "inpbus.h"
#include "inpcom.h"

struct bus {
    char *name;
    char **node; /* most significant bit first */
    int n;
    struct bus *next;
};

static struct bus *buses = NULL;


/* Strip one layer of quotes. */
static char *sig_val(const char *tok)
{
    size_t n = strlen(tok);
    if (n >= 2 && ((tok[0] == '\'' && tok[n - 1] == '\'') ||
                          (tok[0] == '"' && tok[n - 1] == '"')))
        return copy_substring(tok + 1, tok + n - 1);
    return copy(tok);
}

/* Literal .param values, needed because a .sigbus PATTERN value may be given
   as a parameter -- 'name' or $(name) -- and the bits have to be worked out
   here, long before numparam runs. Only plain literals are resolved; an
   expression is reported rather than guessed at. */
struct pval {
    char *name, *val;
    struct pval *next;
};

static struct pval *pvals = NULL;


static void pval_add(const char *name, const char *val)
{
    struct pval *p = TMALLOC(struct pval, 1);
    p->name = copy(name);
    p->val = copy(val);
    p->next = pvals;       /* later definitions shadow earlier ones */
    pvals = p;
}


static const char *pval_find(const char *name)
{
    struct pval *p;
    for (p = pvals; p; p = p->next)
        if (cieq(p->name, name))
            return p->val;
    return NULL;
}


/* Collect 'name = value' pairs from every .param card in the deck. */
static void pval_scan(struct card *deck)
{
    struct card *c;

    for (c = deck; c; c = c->nextcard) {
        const char *s = skip_ws(c->line);
        if (!ciprefix(".param", s))
            continue;
        s = skip_ws(skip_non_ws(s));
        while (*s) {
            const char *ns = s, *ne, *vs, *ve;
            char *nm, *vl;
            while (*s && *s != '=' && !isspace_c(*s))
                s++;
            ne = s;
            s = skip_ws(s);
            if (*s != '=' || ne == ns)
                break;
            s = skip_ws(s + 1);
            vs = s;
            if (*s == '\'' || *s == '"') {
                char q = *s++;
                while (*s && *s != q)
                    s++;
                if (*s)
                    s++;
            }
            else {
                while (*s && !isspace_c(*s))
                    s++;
            }
            ve = s;
            nm = copy_substring(ns, ne);
            vl = copy_substring(vs, ve);
            {
                char *bare = sig_val(vl);
                pval_add(nm, bare);
                tfree(bare);
            }
            tfree(nm);
            tfree(vl);
            s = skip_ws(s);
        }
    }
}


static void bus_free(struct bus *b)
{
    int i;
    for (i = 0; i < b->n; i++)
        tfree(b->node[i]);
    tfree(b->node);
    tfree(b->name);
    tfree(b);
}


void inpbus_reset(void)
{
    while (pvals) {
        struct pval *p = pvals;
        pvals = p->next;
        tfree(p->name);
        tfree(p->val);
        tfree(p);
    }
    while (buses) {
        struct bus *b = buses;
        buses = b->next;
        bus_free(b);
    }
}


static struct bus *bus_find(const char *name)
{
    struct bus *b;
    for (b = buses; b; b = b->next)
        if (cieq(b->name, name))
            return b;
    return NULL;
}


static void bus_add(char *name, char **node, int n)
{
    struct bus *b = bus_find(name);

    if (b) { /* a later .setbus replaces an earlier one */
        struct bus **pb;
        for (pb = &buses; *pb; pb = &(*pb)->next)
            if (*pb == b) {
                *pb = b->next;
                break;
            }
        bus_free(b);
    }

    b = TMALLOC(struct bus, 1);
    b->name = name;
    b->node = node;
    b->n = n;
    b->next = buses;
    buses = b;
}


static void bus_err(const char *msg, const char *line, const char *file,
        int linenum)
{
    fprintf(cp_err, "Error: %s\n    %s\n    file %s, line %d\n", msg, line,
            file ? file : "<input>", linenum);
    controlled_exit(EXIT_FAILURE);
}


/* ---- .SETBUS ------------------------------------------------------------ */

static void do_setbus(const char *line, const char *file, int linenum)
{
    const char *s = skip_ws(skip_non_ws(line));
    char *name;
    char **node;
    int cap = 8, n = 0;

    if (!*s)
        bus_err("a .setbus needs a bus name", line, file, linenum);

    {
        const char *e = skip_non_ws(s);
        name = copy_substring(s, e);
        s = skip_ws(e);
    }

    node = TMALLOC(char *, cap);
    while (*s) {
        const char *e = skip_non_ws(s);
        if (n + 1 > cap) {
            cap *= 2;
            node = TREALLOC(char *, node, cap);
        }
        node[n++] = copy_substring(s, e);
        s = skip_ws(e);
    }

    if (n == 0)
        bus_err("a .setbus needs at least one node", line, file, linenum);

    bus_add(name, node, n);
}


/* ---- value words -------------------------------------------------------- */

/* Append the bits of one word, most significant first, to ds. Returns FALSE
   if the word is not a legal value in this base. */
static bool word_bits(const char *w, int base, DSTRING *ds)
{
    static const char *hexd = "0123456789abcdef";
    const char *p = w;
    int b = base;

    if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
        b = 16;
        p += 2;
    }

    if (!*p)
        return FALSE;

    if (b == 10) {
        char *end;
        unsigned long long v = strtoull(p, &end, 10);
        int i;
        if (*end)
            return FALSE;
        for (i = 63; i >= 0; i--)
            ds_cat_char(ds, (v >> i) & 1ULL ? '1' : '0');
        return TRUE;
    }

    for (; *p; p++) {
        const char *q;
        int d, k, w4;

        if (b == 2) {
            if (*p != '0' && *p != '1')
                return FALSE;
            ds_cat_char(ds, *p);
            continue;
        }

        q = strchr(hexd, tolower_c(*p));
        if (!q)
            return FALSE;
        d = (int) (q - hexd);
        if (d >= b)
            return FALSE;
        w4 = 4; /* only base 16 reaches here */
        for (k = w4 - 1; k >= 0; k--)
            ds_cat_char(ds, (d >> k) & 1 ? '1' : '0');
    }

    return TRUE;
}


/* Force a bit string to exactly width bits. A shorter value is zero-extended
   at the top; a longer one may only lose leading ZEROS. Eldo rejects a value
   that does not fit ("Unable to code 0XF on 3 bits"), so we do too -- masking
   it off would change the stimulus silently. */
static char *fit_width(const char *bits, int width, bool *fits)
{
    const int n = (int) strlen(bits);
    DS_CREATE(ds, 64);
    char *out;

    *fits = TRUE;

    if (n >= width) {
        int i;
        for (i = 0; i < n - width; i++)
            if (bits[i] != '0')
                *fits = FALSE;
        ds_cat_str(&ds, bits + (n - width));
    }
    else {
        int i;
        for (i = 0; i < width - n; i++)
            ds_cat_char(&ds, '0');
        ds_cat_str(&ds, bits);
    }

    out = copy(ds_get_buf(&ds));
    ds_free(&ds);
    return out;
}


/* One word -> exactly `width` bits, most significant first. */
static char *word_to_bits(const char *w, int base, int width, bool *ok)
{
    DS_CREATE(ds, 64);
    char *fitted;

    *ok = word_bits(w, base, &ds);
    if (!*ok) {
        ds_free(&ds);
        return NULL;
    }
    fitted = fit_width(ds_get_buf(&ds), width, ok);
    ds_free(&ds);
    if (!*ok) {
        tfree(fitted);
        return NULL;
    }
    return fitted;
}


/* ---- .SIGBUS ------------------------------------------------------------ */

struct sig {
    const char *vhi, *vlo, *trise, *tfall, *thold, *tdelay;
    int base;
    bool periodic;
    bool pdelay;   /* P=2: TDELAY before every repetition */
};


/* Turn one .sigbus card into a list of PATTERN cards inserted after it. */
static struct card *do_sigbus(struct card *c, const char *file, int linenum)
{
    const char *line = c->line;
    const char *s = skip_ws(skip_non_ws(line));
    char *busname = NULL;
    struct bus *b = NULL;
    struct bus tmpbus;
    char **tmpnode = NULL;
    struct sig g;
    char **word = NULL;
    int nword = 0, wcap = 8;
    char **bits = NULL;
    struct card *last = c;
    int i, k;
    bool in_pattern = FALSE;
    char *owned[8];
    int nowned = 0;

    memset(&g, 0, sizeof g);
    g.vhi = "5";
    g.vlo = "0";
    g.trise = "2n";
    g.tfall = "2n";
    g.thold = NULL;
    g.tdelay = "0";
    g.base = 10;
    g.periodic = FALSE;
    g.pdelay = FALSE;

    /* --- the bus name, with an optional range that declares it implicitly */
    {
        const char *e = skip_non_ws(s);
        char *tok = copy_substring(s, e);
        char *lb = strpbrk(tok, "<[");
        s = skip_ws(e);

        if (lb) {
            char open = *lb, close = (open == '<') ? '>' : ']';
            char *colon = strchr(lb, ':');
            char *rb = strchr(lb, close);
            int msb, lsb, n, step;

            if (!colon || !rb || rb < colon)
                bus_err("cannot read the bus range", line, file, linenum);
            *lb = '\0';
            msb = atoi(lb + 1);
            lsb = atoi(colon + 1);
            busname = copy(tok);
            n = (msb > lsb ? msb - lsb : lsb - msb) + 1;
            step = (msb > lsb) ? -1 : 1;
            tmpnode = TMALLOC(char *, n);
            for (i = 0, k = msb; i < n; i++, k += step)
                tmpnode[i] = tprintf("%s%c%d%c", busname, open, k, close);
            tmpbus.name = busname;
            tmpbus.node = tmpnode;
            tmpbus.n = n;
            b = &tmpbus;
        }
        else if (strchr(tok, ':')) {
            bus_err("the abbreviated bus<msb>:<lsb> form is not supported; "
                    "write bus<msb:lsb> or declare it with .setbus",
                    line, file, linenum);
        }
        else {
            busname = copy(tok);
            b = bus_find(busname);
            if (!b)
                bus_err("no .setbus for this bus name", line, file, linenum);
        }
        tfree(tok);
    }

    /* --- keywords, then the pattern words */
    word = TMALLOC(char *, wcap);
    while (*s) {
        const char *e = skip_non_ws(s);
        char *tok = copy_substring(s, e);
        char *eq = strchr(tok, '=');
        s = skip_ws(e);

        if (!in_pattern && cieq(tok, "pattern")) {
            in_pattern = TRUE;
            tfree(tok);
            continue;
        }

        if (!in_pattern && eq) {
            char *key = copy_substring(tok, eq);
            char *val = sig_val(eq + 1);
            const char **slot = NULL;

            if (cieq(key, "vhi")) slot = &g.vhi;
            else if (cieq(key, "vlo")) slot = &g.vlo;
            else if (cieq(key, "trise")) slot = &g.trise;
            else if (cieq(key, "tfall")) slot = &g.tfall;
            else if (cieq(key, "thold")) slot = &g.thold;
            else if (cieq(key, "tdelay")) slot = &g.tdelay;
            else if (cieq(key, "base")) {
                if (cieq(val, "hex")) g.base = 16;
                else if (cieq(val, "bin")) g.base = 2;
                else if (cieq(val, "dec")) g.base = 10;
                else
                    bus_err("only BASE=HEX, BIN and DEC are supported",
                            line, file, linenum);
            }
            else if (cieq(key, "signed")) {
                bus_err("SIGNED= is not supported", line, file, linenum);
            }
            else if (cieq(key, "file")) {
                bus_err("the FILE= form of .sigbus is not supported",
                        line, file, linenum);
            }
            else if (cieq(key, "p")) {
                if (cieq(val, "0")) g.periodic = FALSE;
                else if (cieq(val, "1")) g.periodic = TRUE;
                else if (cieq(val, "2")) {
                    g.periodic = TRUE;
                    g.pdelay = TRUE;
                }
                else
                    bus_err("only P, P=0, P=1 and P=2 are defined",
                            line, file, linenum);
            }
            else {
                bus_err("unrecognised .sigbus keyword", line, file, linenum);
            }

            if (slot) {
                /* A level is copied straight into the pwl value list, so its
                   quoting has to survive to inppat.c, which is what turns it
                   into {...} and gets it evaluated.  Strip it here and a
                   VHI='vdd' arrives at the pwl parser as the bare word vdd,
                   which fails with "no such parameter on this device".  The
                   times need no such care: pat_time() wraps them in braces
                   itself, whether or not they were quoted. */
                if (slot == &g.vhi || slot == &g.vlo) {
                    tfree(val);
                    val = copy(eq + 1);
                }
                *slot = val;
                owned[nowned++] = val;
            }
            else {
                tfree(val);
            }
            tfree(key);
            tfree(tok);
            continue;
        }

        /* P, and P=n, may appear either before or AFTER the PATTERN
           keyword; the values follow it, so this has to be recognised here
           too or the marker is parsed as a bus value. */
        if (cieq(tok, "p") || ciprefix("p=", tok)) {
            const char *v = (tok[1] == '=') ? tok + 2 : "1";
            if (cieq(v, "0"))
                g.periodic = FALSE;
            else if (cieq(v, "1"))
                g.periodic = TRUE;
            else if (cieq(v, "2")) {
                g.periodic = TRUE;
                g.pdelay = TRUE;
            }
            else
                bus_err("only P, P=0, P=1 and P=2 are defined",
                        line, file, linenum);
            tfree(tok);
            continue;
        }
        /* 'name' or $(name): substitute the parameter's literal value, which
           may itself be several words (a quoted string of bus values). */
        if (in_pattern && (tok[0] == '\'' || ciprefix("$(", tok))) {
            char *nm = (tok[0] == '\'') ? sig_val(tok)
                                       : copy_substring(tok + 2,
                                               tok + strlen(tok) -
                                               (tok[strlen(tok) - 1] == ')'));
            const char *v = pval_find(nm);
            if (!v)
                bus_err("a .sigbus PATTERN value names a parameter that has "
                        "no plain .param value in this deck", line, file,
                        linenum);
            {
                const char *q = skip_ws(v);
                while (*q) {
                    const char *e2 = skip_non_ws(q);
                    if (nword + 1 > wcap) {
                        wcap *= 2;
                        word = TREALLOC(char *, word, wcap);
                    }
                    word[nword++] = copy_substring(q, e2);
                    q = skip_ws(e2);
                }
            }
            tfree(nm);
            tfree(tok);
            continue;
        }
        if (cieq(tok, "z") || strchr(tok, 'z') || strchr(tok, 'Z')) {
            bus_err("the Z (high impedance) state is not supported: a pwl "
                    "source cannot be disconnected from its node",
                    line, file, linenum);
        }
        if (strchr(tok, '$') || strchr(tok, '[')) {
            bus_err("$(param) patterns and the HX[]/BX[] per-value bases are "
                    "not supported", line, file, linenum);
        }
        if (!in_pattern)
            bus_err("expected PATTERN before the bus values", line, file,
                    linenum);

        if (nword + 1 > wcap) {
            wcap *= 2;
            word = TREALLOC(char *, word, wcap);
        }
        word[nword++] = tok;
    }

    if (!g.thold)
        bus_err("THOLD= is required on a .sigbus PATTERN", line, file, linenum);
    if (nword == 0)
        bus_err("a .sigbus PATTERN needs at least one value. Note that a "
                "value written $(name) cannot be used here: ngspice strips "
                "' $...' as an end-of-line comment before this point, so use "
                "the equivalent 'name' form instead", line, file, linenum);

    /* --- one bit string per bus bit, most significant bit first */
    bits = TMALLOC(char *, b->n);
    for (i = 0; i < b->n; i++)
        bits[i] = TMALLOC(char, nword + 1);

    for (k = 0; k < nword; k++) {
        bool ok;
        char *fitted = word_to_bits(word[k], g.base, b->n, &ok);
        if (!ok)
            bus_err("a .sigbus value is not legal in the declared base, or "
                    "does not fit in the width of the bus",
                    line, file, linenum);
        for (i = 0; i < b->n; i++)
            bits[i][k] = fitted[i];
        tfree(fitted);
    }
    for (i = 0; i < b->n; i++)
        bits[i][nword] = '\0';

    /* --- emit one PATTERN source per bit */
    for (i = 0; i < b->n; i++) {
        DS_CREATE(nm, 32);
        char *nl;
        const char *q;

        ds_cat_str(&nm, "vsigbus_");
        for (q = b->node[i]; *q; q++)
            ds_cat_char(&nm, (isalnum_c(*q) || *q == '_') ? *q : '_');

        nl = tprintf("%s %s 0 pattern %s %s %s %s %s %s %s%s",
                ds_get_buf(&nm), b->node[i], g.vhi, g.vlo, g.tdelay, g.trise,
                g.tfall, g.thold, bits[i],
                g.pdelay ? " P=2" : g.periodic ? " P" : "");
        ds_free(&nm);

        last = insert_new_line(last, nl, c->linenum, c->linenum_orig,
                c->linesource);
    }

    /* --- the .sigbus itself becomes a comment */
    {
        char *old = c->line;
        c->line = tprintf("* expanded: %s", old);
        tfree(old);
    }

    for (i = 0; i < b->n; i++)
        tfree(bits[i]);
    tfree(bits);
    for (k = 0; k < nword; k++)
        tfree(word[k]);
    tfree(word);
    for (i = 0; i < nowned; i++)
        tfree(owned[i]);
    if (tmpnode) {
        for (i = 0; i < b->n; i++)
            tfree(tmpnode[i]);
        tfree(tmpnode);
    }
    tfree(busname);

    return last;
}


void inpbus_expand(struct card *deck)
{
    struct card *c;

    pval_scan(deck);

    for (c = deck; c; c = c->nextcard) {
        const char *s = skip_ws(c->line);

        if (ciprefix(".setbus", s)) {
            do_setbus(s, c->linesource, c->linenum_orig);
            {
                char *old = c->line;
                c->line = tprintf("* %s", old);
                tfree(old);
            }
        }
        else if (ciprefix(".sigbus", s)) {
            c = do_sigbus(c, c->linesource, c->linenum_orig);
        }
    }
}
