/* Eldo/HSPICE PATTERN (PAT) stimulus sources, rewritten as PWL.
 *
 *   Vname n+ n- PATTERN vhi vlo tdelay trise tfall tsample bits [RB=n] [R[=n]]
 *   Vname n+ n- PAT ( ...same... )
 *
 * This is a text transform on the card, run before parameters are evaluated,
 * so 'vdd' / 'tr' / 'th' survive as expressions and numparam resolves them
 * exactly as it would have. No new device and no arithmetic done here.
 *
 * The waveform was MEASURED against Eldo 2023.4 rather than inferred from the
 * manual, which leaves both the edge placement and the RB base open:
 *
 *   - the 50% crossing of the edge INTO bit k is at tdelay + k*tsample;
 *   - the edge is CENTRED on that crossing, spanning +/- e/2 around it, where
 *     e is trise when the destination bit is 1 and tfall when it is 0;
 *   - trise/tfall are full 0->100% durations, not 10-90%;
 *   - the value held through tdelay is bits[0], so the first edge is at k=1;
 *   - tsample is therefore the 50%-to-50% bit period.
 *
 * Repeat: R (or R=-1) repeats for ever, R=0 or no R does not. RB=n gives the
 * bit the repeat returns to and is ONE-BASED, so RB=1 equals a plain R.
 *
 * 'P' is the same repeat with one difference, and it exists because Eldo's own
 * two constructs differ here. Under PATTERN ... R the wrap edge is centred on
 * the period boundary like every other edge. Under .SIGBUS ... P it is
 * COMPRESSED to end exactly on the boundary: measured with thold=20n, tfall=1n
 * and a delay of 10n, Eldo ramps 49.50n..50.00n at the wrap and 69.50n..70.50n
 * at the next ordinary edge. Both were measured; P is what inpbus.c emits.
 *
 * The repeat becomes ngspice's own PWL 'r=', which replays the card from the
 * point whose time equals r. Because the edges are centred, a bit boundary
 * falls in the middle of an edge, so the anchor is placed at the END of the
 * edge into bit rb-1, where the waveform is flat and identical on every pass.
 * vsrcpar.c compares r against the emitted times for exact equality, so the
 * anchor point is always emitted and its time expression is generated once
 * and used verbatim in both places.
 *
 * Refused rather than approximated, because silently guessing would produce a
 * run that looks fine and is wrong:
 *   - Z (high impedance) in the bit string: PWL cannot disconnect a source;
 *   - R=<positive n>, a finite repeat count: PWL's r= only repeats for ever;
 *   - PATTERN_FILE=, the [bits^N] compact syntax, PRJRMS/RJRMS/RJSEED jitter.
 */

#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 "inpcom.h"
#include "inppat.h"

struct pat {
    char *vhi, *vlo, *td, *tr, *tf, *ts; /* bare expression text */
    char *bits;
    int rb;     /* 1-based bit the repeat returns to */
    bool rep;   /* repeat for ever */
    bool pwrap; /* P: the wrap edge ENDS on the period boundary (see below) */
    bool pdelay;/* P=2: tdelay is re-applied before EVERY repetition */
};


/* Strip one layer of '...' "..." or {...} so the text can be dropped inside a
   larger expression. */
static char *pat_bare(const char *tok)
{
    size_t n = strlen(tok);

    if (n >= 2 &&
            ((tok[0] == '\'' && tok[n - 1] == '\'') ||
                    (tok[0] == '"' && tok[n - 1] == '"') ||
                    (tok[0] == '{' && tok[n - 1] == '}')))
        return copy_substring(tok + 1, tok + n - 1);

    return copy(tok);
}


/* A level is emitted straight into the pwl value list, where the times around
   it are always wrapped in {...} and so get evaluated as expressions.  A level
   has to be treated the same way: when the user quoted it -- vhi='vdd', the
   ordinary Eldo spelling -- it is an expression, usually a .param name, and
   handing the bare token to the pwl parser leaves it to the netlist-wide
   parameter substitution, which does not always reach it ("no such parameter
   on this device").  Braces make it evaluate here instead.  An unquoted token
   is a plain number and is passed through exactly as written, so decks that
   never quote a level are byte-for-byte unchanged. */
static char *pat_level_text(const char *tok)
{
    size_t n = strlen(tok);

    if (n >= 2 &&
            ((tok[0] == '\'' && tok[n - 1] == '\'') ||
                    (tok[0] == '"' && tok[n - 1] == '"') ||
                    (tok[0] == '{' && tok[n - 1] == '}')))
        return tprintf("{%.*s}", (int) (n - 2), tok + 1);

    return copy(tok);
}


/* Time of the k-th bit boundary, optionally offset by half an edge.
   edge == NULL gives the boundary itself. */
static char *pat_time(const struct pat *p, size_t k, const char *edge, int sign)
{
    if (!edge)
        return tprintf("{(%s)+%zu*(%s)}", p->td, k, p->ts);

    return tprintf("{(%s)+%zu*(%s)%c(%s)/2}", p->td, k, p->ts,
            sign < 0 ? '-' : '+', edge);
}


static const char *pat_level(const struct pat *p, char bit)
{
    return bit == '1' ? p->vhi : p->vlo;
}


static const char *pat_edge(const struct pat *p, char to)
{
    return to == '1' ? p->tr : p->tf;
}


static void pat_pt(DSTRING *ds, char *t, const char *v)
{
    ds_cat_str(ds, " ");
    ds_cat_str(ds, t);
    ds_cat_str(ds, " ");
    ds_cat_str(ds, v);
    tfree(t);
}


/* Assemble the pwl(...) text. Returns a fresh string, or NULL with *err set. */
static char *pat_build(const struct pat *p, const char **err)
{
    DS_CREATE(ds, 512);
    const size_t n = strlen(p->bits);
    char *anchor = NULL;
    size_t k;

    if (n == 0) {
        *err = "empty bit string";
        ds_free(&ds);
        return NULL;
    }

    if (p->rep) {
        /* The anchor is where a repetition begins. With a centred wrap that is
           just after the edge into bit rb-1 has finished; with P the cycle
           starts on the boundary itself, because the wrap edge ended there.

           P=2 re-applies tdelay before every repetition, which needs no
           arithmetic of its own: replaying from time 0 replays the initial
           hold as well, so the anchor is simply 0 and the period grows from
           n*tsample to tdelay + n*tsample. MEASURED against Eldo 2023.4 with
           tdelay=10n, thold=20n and two words: P=1 repeats every 40 ns and
           P=2 every 50 ns, both holding bits[0] through the delay and both
           compressing the wrap edge onto the boundary. */
        anchor = p->pdelay
                ? copy("0")
                : p->pwrap
                ? pat_time(p, (size_t) (p->rb - 1), NULL, 0)
                : pat_time(p, (size_t) (p->rb - 1),
                          pat_edge(p, p->bits[p->rb - 1]), +1);
    }

    /* Under P with rb == 1 the cycle starts on the boundary itself, which is
       tdelay + 0*tsample -- the very time the waveform already starts at. A
       leading 0 point plus the anchor then puts TWO points on the same time
       whenever tdelay is 0, and ngspice reports "non-increasing PWL time
       points" for every bit of every such bus. The waveform is unharmed (both
       points carry the same level), but a warning that fires on every run is
       one nobody reads, and it would hide a real non-monotonicity.

       Whether the two coincide depends on the VALUE of tdelay, which is an
       expression here and cannot be decided from its text -- the old guard
       tried, comparing the anchor against the string "0", which pat_time()
       never produces, so it never fired. Make the anchor the FIRST point
       instead: r= still finds its exact time, and before it pwl holds the
       first value, which is all the leading 0 point ever said. */
    if (p->rep && p->rb == 1 && p->pwrap) {
        ds_cat_str(&ds, "pwl(");
        ds_cat_str(&ds, anchor);
        ds_cat_str(&ds, " ");
        ds_cat_str(&ds, pat_level(p, p->bits[0]));
    }
    else {
        ds_cat_str(&ds, "pwl(0 ");
        ds_cat_str(&ds, pat_level(p, p->bits[0]));

        /* an anchor at k = 0 has no preceding edge, so emit it explicitly */
        if (p->rep && p->rb == 1)
            pat_pt(&ds, copy(anchor), pat_level(p, p->bits[0]));
    }

    for (k = 1; k < n; k++) {
        const char from = p->bits[k - 1], to = p->bits[k];

        if (from != to) {
            const char *e = pat_edge(p, to);
            pat_pt(&ds, pat_time(p, k, e, -1), pat_level(p, from));
            pat_pt(&ds, pat_time(p, k, e, +1), pat_level(p, to));
        }

        /* if bit rb-1 was reached without an edge, the anchor is still needed */
        if (p->rep && (size_t) (p->rb - 1) == k && from == to)
            pat_pt(&ds, copy(anchor), pat_level(p, to));
    }

    if (p->rep) {
        /* close the loop exactly one period after the anchor */
        const char to = p->bits[p->rb - 1];
        const char *e = pat_edge(p, to);

        if (p->bits[n - 1] != to)
            pat_pt(&ds, pat_time(p, n, e, -1), pat_level(p, p->bits[n - 1]));
        pat_pt(&ds, p->pwrap ? pat_time(p, n, NULL, 0)
                             : pat_time(p, n, e, +1), pat_level(p, to));
    }

    ds_cat_str(&ds, ")");

    if (p->rep) {
        ds_cat_str(&ds, " r=");
        ds_cat_str(&ds, anchor);
    }
    tfree(anchor);

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


/* Split the text after the PATTERN keyword into tokens, honouring '...' and
   {...} so an expression stays one token. Returns a NULL-terminated array. */
static char **pat_tokens(const char *s, int *count)
{
    int cap = 16, n = 0;
    char **out = TMALLOC(char *, cap);

    for (;;) {
        const char *start;
        char close = '\0';

        s = skip_ws(s);
        if (!*s || *s == '\n')
            break;

        if (*s == '\'')
            close = '\'';
        else if (*s == '"')
            close = '"';
        else if (*s == '{')
            close = '}';

        start = s;
        if (close) {
            s++;
            while (*s && *s != close)
                s++;
            if (*s)
                s++;
        }
        else {
            while (*s && !isspace_c(*s))
                s++;
        }

        if (n + 2 > cap) {
            cap *= 2;
            out = TREALLOC(char *, out, cap);
        }
        out[n++] = copy_substring(start, s);
    }

    out[n] = NULL;
    *count = n;
    return out;
}


static void pat_free_tokens(char **t)
{
    int i;
    for (i = 0; t[i]; i++)
        tfree(t[i]);
    tfree(t);
}


static bool pat_is_bits(const char *s)
{
    const char *p = s;
    if (!*p)
        return FALSE;
    while (*p == '0' || *p == '1')
        p++;
    return *p == '\0';
}


static bool pat_has_z(const char *s)
{
    return strchr(s, 'z') || strchr(s, 'Z');
}


/* Rewrite one card if it carries a PATTERN source. Returns the new line, or
   NULL if the card is not one. On a bad pattern it reports and exits. */
char *inppat_rewrite(const char *line, const char *file_name, int line_number)
{
    const char *s, *kw;
    char *head = NULL, *bitbuf = NULL, *pwl = NULL, *out = NULL;
    char **tok = NULL;
    int ntok = 0, i;
    struct pat p;
    const char *err = NULL;

    if (!line || (*line != 'v' && *line != 'V' && *line != 'i' && *line != 'I'))
        return NULL;

    /* find the keyword; it follows name and two nodes */
    for (s = line, i = 0; i < 3; i++) {
        s = skip_non_ws(s);
        s = skip_ws(s);
    }
    if (!*s)
        return NULL;

    if (ciprefix("pattern", s))
        kw = s + 7;
    else if (ciprefix("pat", s) && (isspace_c(s[3]) || s[3] == '('))
        kw = s + 3;
    else
        return NULL;

    head = copy_substring(line, s); /* "vname n+ n- " */

    /* PAT ( ... ) : drop the parens */
    {
        char *body = copy(kw);
        char *b = skip_ws(body);
        if (*b == '(') {
            char *e = strrchr(b, ')');
            if (e)
                *e = ' ';
            *b = ' ';
        }
        tok = pat_tokens(body, &ntok);
        tfree(body);
    }

    memset(&p, 0, sizeof p);
    p.rb = 1;
    p.rep = FALSE;

    if (ntok < 7) {
        err = "expected vhi vlo tdelay trise tfall tsample and a bit string";
        goto bad;
    }

    p.vhi = pat_level_text(tok[0]);
    p.vlo = pat_level_text(tok[1]);
    p.td = pat_bare(tok[2]);
    p.tr = pat_bare(tok[3]);
    p.tf = pat_bare(tok[4]);
    p.ts = pat_bare(tok[5]);

    /* the bit string may be split over several whitespace-separated groups */
    {
        DS_CREATE(bs, 128);
        for (i = 6; i < ntok; i++) {
            if (pat_has_z(tok[i]) && !strchr(tok[i], '=')) {
                err = "the Z (high impedance) state is not supported: a PWL "
                      "source cannot be disconnected from its node";
                ds_free(&bs);
                goto bad;
            }
            if (pat_is_bits(tok[i])) {
                ds_cat_str(&bs, tok[i]);
                continue;
            }
            if (ciprefix("rb=", tok[i])) {
                p.rb = atoi(tok[i] + 3);
                continue;
            }
            if (cieq(tok[i], "r")) {
                p.rep = TRUE;
                continue;
            }
            if (cieq(tok[i], "p")) { /* generated by inpbus.c, see above */
                p.rep = TRUE;
                p.pwrap = TRUE;
                continue;
            }
            if (cieq(tok[i], "p=2")) { /* likewise; tdelay every repetition */
                p.rep = TRUE;
                p.pwrap = TRUE;
                p.pdelay = TRUE;
                continue;
            }
            if (ciprefix("r=", tok[i])) {
                long v = atol(tok[i] + 2);
                if (v == 0)
                    p.rep = FALSE;
                else if (v < 0)
                    p.rep = TRUE;
                else {
                    err = "a finite repeat count R=<n> is not supported; "
                          "ngspice's pwl r= repeats for ever";
                    ds_free(&bs);
                    goto bad;
                }
                continue;
            }
            if (ciprefix("pattern_file=", tok[i]) ||
                    ciprefix("prjrms=", tok[i]) || ciprefix("rjrms=", tok[i]) ||
                    ciprefix("rjseed=", tok[i])) {
                err = "PATTERN_FILE and the jitter arguments are not supported";
                ds_free(&bs);
                goto bad;
            }
            if (strchr(tok[i], '^') || strchr(tok[i], '[')) {
                err = "the compact [bits^N] pattern syntax is not supported";
                ds_free(&bs);
                goto bad;
            }
            err = "unrecognised token in a PATTERN source";
            ds_free(&bs);
            goto bad;
        }
        bitbuf = copy(ds_get_buf(&bs));
        ds_free(&bs);
    }

    p.bits = bitbuf;

    if (p.rb < 1 || (size_t) p.rb > strlen(p.bits)) {
        err = "RB= is outside the bit string (it is 1-based)";
        goto bad;
    }

    /* P=2 replays from time 0 so that the delay is re-applied. RB= asks the
       repeat to return somewhere else, and the two cannot both hold. Eldo
       does not combine them either -- RB= belongs to PATTERN and P= to
       .SIGBUS -- so refuse rather than silently honour one of them. */
    if (p.pdelay && p.rb != 1) {
        err = "P=2 and RB= cannot be combined: P=2 restarts from the delay, "
              "RB= restarts from a chosen bit";
        goto bad;
    }

    pwl = pat_build(&p, &err);
    if (!pwl)
        goto bad;

    out = tprintf("%s%s", head, pwl);

    tfree(pwl);
    tfree(head);
    tfree(p.vhi); tfree(p.vlo); tfree(p.td);
    tfree(p.tr); tfree(p.tf); tfree(p.ts);
    tfree(bitbuf);
    pat_free_tokens(tok);
    return out;

bad:
    fprintf(cp_err, "Error: %s\n    in %s\n    file %s, line %d\n",
            err ? err : "bad PATTERN source", line,
            file_name ? file_name : "<input>", line_number);
    controlled_exit(EXIT_FAILURE);
    return NULL;
}
