/*************
* com_disable.c
*
* ngspice-jit: mask top-level instances in the netlist ngspice already holds
* in memory, so a block can be taken out and put back without ever touching a
* file.
*
* `disable x1 x2 ...` marks instances; `enable` unmarks; the mask is applied
* the next time the deck is elaborated, i.e. on the user's explicit `reset`.
* Marking and re-elaborating are kept separate on purpose: rebuilding a large
* circuit as a side effect of naming a block would be a bad surprise, and
* scripts want the two steps distinct anyway.
*
* The mask is applied to the WORKING copy of the deck.  ft_curckt->ci_mcdeck /
* mc_deck keep the pristine pre-expansion deck (their lines are deep-copied by
* inp_deckcopy), so disabling is freely reversible and a disabled block is
* never subckt-expanded at all -- it costs no time and no memory.
*
* The mask is global rather than per-circuit because `reset` runs com_remcirc,
* which frees ft_curckt: a mask stored there would be destroyed by the very
* operation it has to survive.  Multi-circuit sessions therefore share one mask.
************/
#include <stdio.h>

#include "ngspice/ngspice.h"
#include "ngspice/wordlist.h"
#include "ngspice/cpdefs.h"
#include "ngspice/ftedefs.h"
#include "ngspice/inpdefs.h"
#include "ngspice/stringutil.h"

#include "com_disable.h"


static char **dis_names = NULL;    /* instance names, matched case-insensitively */
static int dis_count = 0;

static int dis_index_of_line(const char *line);


/* Skip leading blanks. */
static const char *skip_ws(const char *s)
{
    while (*s && isspace_c(*s))
        s++;
    return s;
}


/* Does the first token of `line` equal `name` (case-insensitive)? */
static int tok_is(const char *line, const char *name)
{
    size_t n = strlen(name);
    if (strncasecmp(line, name, n) != 0)
        return 0;
    return line[n] == '\0' || isspace_c(line[n]);
}


static int dis_index(const char *name)
{
    int i;
    for (i = 0; i < dis_count; i++)
        if (strcasecmp(dis_names[i], name) == 0)
            return i;
    return -1;
}


/* Is `name` a top-level (depth 0) X instance of the loaded circuit?
   Guards against a typo silently leaving the block in the circuit. */
static int dis_instance_exists(const char *name)
{
    struct card *c;
    int depth = 0;

    if (!ft_curckt || !ft_curckt->ci_mcdeck)
        return -1;                      /* cannot check */

    for (c = ft_curckt->ci_mcdeck; c; c = c->nextcard) {
        const char *s = skip_ws(c->line);
        if (ciprefix(".subckt", s)) { depth++; continue; }
        if (ciprefix(".ends", s))   { if (depth > 0) depth--; continue; }
        if (depth)
            continue;
        if ((*s == 'x' || *s == 'X') && tok_is(s, name))
            return 1;
    }
    return 0;
}


/* Comment out every disabled top-level instance in `deck`.  Returns the count.
   Called on the working deck each time it is (re-)elaborated. */
int inp_apply_disabled(struct card *deck)
{
    struct card *c;
    int depth = 0, n = 0;

    if (dis_count == 0)
        return 0;

    for (c = deck; c; c = c->nextcard) {
        const char *s = skip_ws(c->line);
        if (ciprefix(".subckt", s)) { depth++; continue; }
        if (ciprefix(".ends", s))   { if (depth > 0) depth--; continue; }
        if (depth)
            continue;
        if (*s != 'x' && *s != 'X')
            continue;
        if (dis_index_of_line(s) >= 0) {
            /* '*' in column 1 makes the whole card a comment; continuation
               lines were already folded into this card by inpcom, so there is
               nothing else to mask. */
            c->line[0] = '*';
            n++;
        }
    }
    return n;
}


/* Which disabled name (if any) does this instance line start with? */
static int dis_index_of_line(const char *line)
{
    int i;
    for (i = 0; i < dis_count; i++)
        if (tok_is(line, dis_names[i]))
            return i;
    return -1;
}


void com_disable(wordlist *wl)
{
    if (!wl) {
        com_disabled(NULL);
        return;
    }

    for (; wl; wl = wl->wl_next) {
        const char *nm = wl->wl_word;
        int known;

        if (dis_index(nm) >= 0) {
            fprintf(cp_out, "disable: %s is already disabled\n", nm);
            continue;
        }

        known = dis_instance_exists(nm);
        if (known == 0) {
            /* Refuse rather than accept silently: a mistyped block would stay
               in the circuit and the run would look fine and be wrong. */
            fprintf(cp_err,
                    "disable: no top-level instance '%s' -- ignored\n", nm);
            continue;
        }
        if (known < 0)
            fprintf(cp_err,
                    "disable: no circuit loaded, '%s' not checked\n", nm);

        dis_names = TREALLOC(char *, dis_names, dis_count + 1);
        dis_names[dis_count++] = copy(nm);
    }

    if (dis_count)
        fprintf(cp_out, "disable: %d instance(s) marked; "
                        "run 'reset' to re-elaborate\n", dis_count);
}


void com_enable(wordlist *wl)
{
    int i;

    if (!wl) {                          /* enable with no args: clear all */
        for (i = 0; i < dis_count; i++)
            tfree(dis_names[i]);
        tfree(dis_names);
        dis_names = NULL;
        dis_count = 0;
        fprintf(cp_out, "enable: all instances re-enabled; "
                        "run 'reset' to re-elaborate\n");
        return;
    }

    for (; wl; wl = wl->wl_next) {
        int k = dis_index(wl->wl_word);
        if (k < 0) {
            fprintf(cp_err, "enable: %s is not disabled\n", wl->wl_word);
            continue;
        }
        tfree(dis_names[k]);
        for (i = k; i < dis_count - 1; i++)
            dis_names[i] = dis_names[i + 1];
        dis_count--;
    }
    fprintf(cp_out, "enable: %d instance(s) still disabled; "
                    "run 'reset' to re-elaborate\n", dis_count);
}


void com_disabled(wordlist *wl)
{
    int i;
    NG_IGNORE(wl);

    if (dis_count == 0) {
        fprintf(cp_out, "No instances are disabled.\n");
        return;
    }
    fprintf(cp_out, "Disabled top-level instances (%d):\n", dis_count);
    for (i = 0; i < dis_count; i++)
        fprintf(cp_out, "  %s\n", dis_names[i]);
    fprintf(cp_out, "Effective after 'reset'.\n");
}
