/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
**********/

/*
 * Code to deal with breakpoints and tracing.
 */

#include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h"
#include "ngspice/ftedefs.h"
#include "ngspice/dvec.h"
#include "ngspice/ftedebug.h"
#include "ngspice/cktdefs.h"
#include "breakp2.h"

#include <regex.h>


/* save runs BEFORE the circuit does, so a regular-expression argument matches
 * the names of the circuit NODES (not plot vectors, which don't exist yet).
 * Expand any such argument into the matching node names; leave alone the save
 * spec forms v(..)/i(..), @dev[param], the all/nosub keywords, and plain node
 * names (none of which are treated as patterns).  Returns a fresh wordlist. */
static bool
save_arg_is_regex(const char *w)
{
    if (!ft_curckt || !w || !*w)
        return FALSE;
    if (cieq(w, "all") || cieq(w, "nosub") || w[0] == '@')
        return FALSE;
    if ((tolower_c(w[0]) == 'v' || tolower_c(w[0]) == 'i') && w[1] == '(')
        return FALSE;
    return strpbrk(w, ".*+?[]()^$|\\") != NULL;
}

static wordlist *
save_node_regex_wl(const wordlist *wl)
{
    wordlist *first = NULL, *last = NULL;

    for (; wl; wl = wl->wl_next) {
        const char *w = wl->wl_word;
        if (save_arg_is_regex(w)) {
            regex_t re;
            CKTcircuit *ckt = ft_curckt ? ft_curckt->ci_ckt : NULL;
            if (ckt && ckt->CKTnodes &&
                    regcomp(&re, w, REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0) {
                CKTnode *node;
                int nmatch = 0;
                for (node = ckt->CKTnodes->next; node; node = node->next)
                    if (regexec(&re, node->name, 0, NULL, 0) == 0) {
                        wl_append_word(&first, &last, copy(node->name));
                        nmatch++;
                    }
                regfree(&re);
                if (nmatch == 0)
                    fprintf(cp_err,
                            "Warning: save: no node matches /%s/.\n", w);
                continue;
            }
            if (!ckt || !ckt->CKTnodes)
                fprintf(cp_err, "Warning: save: node list not available yet "
                                "(run once first); '%s' kept literal.\n", w);
        }
        wl_append_word(&first, &last, copy(w));
    }
    return first;
}


/* global linked list to store .save data and breakpoint data */
struct dbcomm *dbs = NULL;      /* export for iplot */

/* used in breakp.c and breakp2.c */
int debugnumber = 1;

static char *copynode(char* s);


/* Analyse the data given by the .save card or 'save' command.
   Store the data in the global dbs struct.
*/

/* Save a vector. */

void
com_save(wordlist *wl)
{
    wordlist *rwl = save_node_regex_wl(wl);
    settrace(rwl, VF_ACCUM, NULL);
    wl_free(rwl);
}


/* Save a vector with the analysis type given (name). */
void
com_save2(wordlist *wl, char *name)
{
    wordlist *rwl = save_node_regex_wl(wl);
    settrace(rwl, VF_ACCUM, name);
    wl_free(rwl);
}


void
settrace(wordlist *wl, int what, char *name)
{
    struct dbcomm *d, *last, *dbcheck;

    if (!ft_curckt) {
        fprintf(cp_err, "Error: no circuit loaded\n");
        return;
    }

    if (dbs)
        for (last = dbs; last->db_next; last = last->db_next)
            ;
    else
        last = NULL;

    for (;wl ;wl = wl->wl_next) {
        char *s = cp_unquote(wl->wl_word);
        char *db_nodename1 = NULL;
        char db_type = 0;
        if (eq(s, "all") || eq(s, "nosub")) {
            switch (what) {
            case VF_PRINT:
                db_type = DB_TRACEALL;
                break;
            case VF_ACCUM:
                db_nodename1 = copy(s);
                db_type = DB_SAVE;
                break;
            }
            tfree(s);
        } else {
            switch (what) {
            case VF_PRINT:
                db_type = DB_TRACENODE;
                break;
            case VF_ACCUM:
                db_type = DB_SAVE;
                break;
            }
            /* v(2) --> 2, i(vds) --> vds#branch */
            db_nodename1 = copynode(s);
            tfree(s);
            if (!db_nodename1)  /* skip on error */
                continue;
        }

        /* Don't save a nodename more than once, except for token 'all' */
        if (db_type == DB_SAVE) {
            for (dbcheck = dbs; dbcheck; dbcheck = dbcheck->db_next) {
                if (dbcheck->db_type == DB_SAVE && eq(dbcheck->db_nodename1, db_nodename1) &&
                    !eq("all", db_nodename1)) {
                    tfree(db_nodename1);
                    goto loopend;
                }
            }
        }

        d = TMALLOC(struct dbcomm, 1);
        d->db_analysis = name;
        d->db_type = db_type;
        d->db_nodename1 = db_nodename1;
        d->db_number = debugnumber++;

        if (last)
            last->db_next = d;
        else
            ft_curckt->ci_dbs = dbs = d;

        last = d;

    loopend:;
    }
}


/* retrieve the save nodes from dbs into an array */
int
ft_getSaves(struct save_info **savesp)
/* global variable: dbs */
{
    struct dbcomm *d;
    int count = 0, i = 0;
    struct save_info *array;

    for (d = dbs; d; d = d->db_next)
        if (d->db_type == DB_SAVE)
            count++;

    if (!count)
        return (0);

    *savesp = array = TMALLOC(struct save_info, count);

    for (d = dbs; d; d = d->db_next)
        if (d->db_type == DB_SAVE) {
            array[i].used = 0;
            if (d->db_analysis)
                array[i].analysis = copy(d->db_analysis);
            else
                array[i].analysis = NULL;
            array[i++].name = copy(d->db_nodename1);
        }

    return (count);
}


/* v(2) --> 2, i(vds) --> vds#branch, 3 --> 3, @mn1[vth0] --> @mn1[vth0]
 *   derived from wordlist *gettoks(char *s)
 */

static char*
copynode(char *s)
{
    char *l, *r;
    char *ret = NULL;

    if (strchr(s, '('))
        s = stripWhiteSpacesInsideParens(s);
    else
        s = copy(s);

    l = strrchr(s, '(');
    if (!l)
        return s;

    r = strchr(s, ')');
    if (!r) {
        fprintf(cp_err, "Warning: Missing ')' in %s\n  Not saved!\n", s);
        tfree(s);
        return NULL;
    }

    *r = '\0';
    if (*(l - 1) == 'i' || *(l - 1) == 'I')
        ret = tprintf("%s#branch", l + 1);
    else
        ret = copy(l + 1);

    tfree(s);
    return ret;
}
