/**********
Copyright 2023 The ngspice team.  All rights reserved.
License: Three-clause BCD
Author: 2023 Holger Vogt
**********/

/*
  For dealing with compatibility transformations

  PSPICE, LTSPICE and others
*/

#include "ngspice/ngspice.h"

#include "ngspice/compatmode.h"
#include "ngspice/cpdefs.h"
#include "ngspice/dstring.h"
#include "ngspice/dvec.h"
#include "ngspice/ftedefs.h"
#include "ngspice/fteext.h"
#include "ngspice/fteinp.h"
#include "numparam/general.h"

#include <limits.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <sys/types.h>

#if !defined(__MINGW32__) && !defined(_MSC_VER)
#include <unistd.h>
#endif

#include "../misc/util.h" /* ngdirname() */
#include "inpcom.h"
#include "ngspice/stringskip.h"
#include "ngspice/stringutil.h"
#include "ngspice/wordlist.h"
#include "subckt.h"
#include "variable.h"

#define INTEGRATE_UDEVICES
#ifdef INTEGRATE_UDEVICES
#include "ngspice/udevices.h"
#endif

void print_compat_mode(void);
void set_compat_mode(void);
struct card* pspice_compat(struct card* newcard);
void pspice_compat_a(struct card* oldcard);
struct card* ltspice_compat(struct card* oldcard);
void ltspice_compat_a(struct card* oldcard);



/* Set a compatibility flag.
Currently available are flags for:
- LTSPICE, HSPICE, Spice3, PSPICE, KiCad, Spectre, XSPICE
*/
struct compat newcompat;
void set_compat_mode(void)
{
    char behaviour[80];
    newcompat.hs = FALSE;
    newcompat.ps = FALSE;
    newcompat.xs = FALSE;
    newcompat.lt = FALSE;
    newcompat.ki = FALSE;
    newcompat.a = FALSE;
    newcompat.spe = FALSE;
    newcompat.isset = FALSE;
    newcompat.s3 = FALSE;
    newcompat.mc = FALSE;
    if (cp_getvar("ngbehavior", CP_STRING, behaviour, sizeof(behaviour))) {
        if (strstr(behaviour, "hs"))
            newcompat.isset = newcompat.hs = TRUE; /*HSPICE*/
        if (strstr(behaviour, "ps"))
            newcompat.isset = newcompat.ps = TRUE; /*PSPICE*/
        if (strstr(behaviour, "xs"))
            newcompat.isset = newcompat.xs = TRUE; /*XSPICE*/
        if (strstr(behaviour, "lt"))
            newcompat.isset = newcompat.lt = TRUE; /*LTSPICE*/
        if (strstr(behaviour, "ki"))
            newcompat.isset = newcompat.ki = TRUE; /*KiCad*/
        if (strstr(behaviour, "a"))
            newcompat.isset = newcompat.a = TRUE; /*complete netlist, used in conjuntion with other mode*/
        if (strstr(behaviour, "ll"))
            newcompat.isset = newcompat.ll = TRUE; /*all (currently not used)*/
        if (strstr(behaviour, "s3"))
            newcompat.isset = newcompat.s3 = TRUE; /*spice3 only*/
        if (strstr(behaviour, "eg"))
            newcompat.isset = newcompat.eg = TRUE; /*EAGLE*/
        if (strstr(behaviour, "spe")) {
            newcompat.isset = newcompat.spe = TRUE; /*Spectre*/
            newcompat.ps = newcompat.lt = newcompat.ki = newcompat.eg = FALSE;
        }
        if (strstr(behaviour, "mc")) {
            newcompat.isset = FALSE;
            newcompat.mc = TRUE; /*make check*/
        }
    }
    if (newcompat.hs && newcompat.ps) {
        fprintf(stderr, "Warning: hs and ps compatibility are mutually exclusive, switch to ps!\n");
        newcompat.hs = FALSE;
    }
    /* reset everything for 'make check' */
    if (newcompat.mc)
        newcompat.eg = newcompat.hs = newcompat.spe = newcompat.ps = newcompat.xs =
        newcompat.ll = newcompat.lt = newcompat.ki = newcompat.a = FALSE;
}

/* Print the compatibility flags */
void print_compat_mode(void) {
    if (newcompat.mc) /* make check */
        return;
    if (newcompat.isset) {
        fprintf(stdout, "\n");
        fprintf(stdout, "Note: Compatibility modes selected:");
        if (newcompat.hs)
            fprintf(stdout, " hs");
        if (newcompat.ps)
            fprintf(stdout, " ps");
        if (newcompat.xs)
            fprintf(stdout, " xs");
        if (newcompat.lt)
            fprintf(stdout, " lt");
        if (newcompat.ki)
            fprintf(stdout, " ki");
        if (newcompat.ll)
            fprintf(stdout, " ll");
        if (newcompat.s3)
            fprintf(stdout, " s3");
        if (newcompat.eg)
            fprintf(stdout, " eg");
        if (newcompat.spe)
            fprintf(stdout, " spe");
        if (newcompat.a)
            fprintf(stdout, " a");
        fprintf(stdout, "\n\n");
    }
    else {
        fprintf(stdout, "\n");
        fprintf(stdout, "Note: No compatibility mode selected!\n\n");
    }
}


/* replace the E and G source TABLE function by a B source pwl
 * (used by ST OpAmps and comparators of Infineon models).
 * E_RO_3 VB_3 VB_4  VALUE={ TABLE( V(VCCP,VCCN), 2 , 35 , 3.3 , 15 , 5 , 10
 *         )*I(VreadIo)}
 * will become
 * BE_RO_3_1 TABLE_NEW_1 0 v = pwl( V(VCCP,VCCN), 2 , 35 , 3.3 , 15 , 5 , 10) 
 * E_RO_3 VB_3 VB_4  VALUE={ V(TABLE_NEW_1)*I(VreadIo)}
 */
static void replace_table(struct card *startcard)
{
    struct card *card;
    static int numb = 0;
    for (card = startcard; card; card = card->nextcard) {
        char *cut_line = card->line;
        if (*cut_line == 'e' || *cut_line == 'g') {
            char *valp = search_plain_identifier(cut_line, "value");
            char *valp2 = search_plain_identifier(cut_line, "cur");
            if (valp || (valp2 && *cut_line == 'g')) {
                char *ftablebeg = strstr(cut_line, "table(");
                while (ftablebeg) {
                    /* get the beginning of the line */
                    char *begline = copy_substring(cut_line, ftablebeg);
                    /* get the table function */
                    char *tabfun = gettok_char(&ftablebeg, ')', TRUE, TRUE);
                    /* the new e, g line */
                    char *neweline = tprintf("%s v(table_new_%d)%s",
                            begline, numb, ftablebeg);
                    char *newbline =
                            tprintf("btable_new_%d table_new_%d 0 v=pwl%s",
                                    numb, numb, tabfun + 5);
                    numb++;
                    tfree(tabfun);
                    tfree(begline);
                    tfree(card->line);
                    card->line = cut_line = neweline;
                    insert_new_line(card, newbline, 0, card->linenum_orig, card->linesource);
                    /* read next TABLE function in cut_line */
                    ftablebeg = strstr(cut_line, "table(");
                }
                continue;
            }
        }
    }
}

/* find the model requested by ako:model and do the replacement */
static struct card *find_model(struct card *startcard,
        struct card *changecard, char *searchname, char *newmname,
        char *newmtype, char *endstr)
{
    struct card *nomod, *returncard = changecard;
    char *origmname, *origmtype;
    char *beginline = startcard->line;
    if (ciprefix(".subckt", beginline))
        startcard = startcard->nextcard;

    int nesting2 = 0;
    for (nomod = startcard; nomod; nomod = nomod->nextcard) {
        char *origmodline = nomod->line;
        if (ciprefix(".subckt", origmodline))
            nesting2++;
        if (ciprefix(".ends", origmodline))
            nesting2--;
        /* skip any subcircuit */
        if (nesting2 > 0)
            continue;
        if (nesting2 == -1) {
            returncard = changecard;
            break;
        }
        if (ciprefix(".model", origmodline)) {
            origmodline = nexttok(origmodline);
            origmname = gettok(&origmodline);
            origmtype = gettok_noparens(&origmodline);
            if (cieq(origmname, searchname)) {
                if (!eq(origmtype, newmtype)) {
                    fprintf(stderr,
                            "Error: Original (%s) and new (%s) type for AKO "
                            "model disagree\n",
                            origmtype, newmtype);
                    controlled_exit(1);
                }
                /* we have got it */
                char *newmodcard = tprintf(".model %s %s %s%s",
                        newmname, newmtype, origmodline, endstr);
                char *tmpstr = strstr(newmodcard, ")(");
                if (tmpstr) {
                    tmpstr[0] = ' ';
                    tmpstr[1] = ' ';
                }
                tfree(changecard->line);
                changecard->line = newmodcard;
                tfree(origmname);
                tfree(origmtype);
                returncard = NULL;
                break;
            }
            tfree(origmname);
            tfree(origmtype);
        }
        else
            returncard = changecard;
    }
    return returncard;
}

/* Process any .distribution cards for PSPICE's Monte-Carlo feature.
 * A .distribution card defines a probability distribution by a PWL
 * density function.  This could be rewritten as a function that
 * returns a random value following that distribution.
 * For now, just comment it away.
 */
static void do_distribution(struct card *oldcard) {
    while (oldcard) {
        char *line = oldcard->line;

        if (line && ciprefix(".distribution", line))
            *line = '*';
        oldcard = oldcard->nextcard;
    }
}

/* Do the .model replacement required by ako (a kind of)
 * PSPICE does not support nested .subckt definitions, so
 * a simple structure is needed: search for ako:modelname,
 * then for modelname in the subcircuit or in the top level.
 * .model qorig npn (BF=48 IS=2e-7)
 * .model qbip1 ako:qorig NPN (BF=60 IKF=45m)
 * after the replacement we have
 * .model qbip1 NPN (BF=48 IS=2e-7 BF=60 IKF=45m)
 * and we benefit from the fact that if parameters have
 * doubled, the last entry of a parameter (e.g. BF=60)
 * overwrites the previous one (BF=48).
 */
static struct card *ako_model(struct card *startcard)
{
    char *newmname, *newmtype;
    struct card *card, *returncard = NULL, *subcktcard = NULL;
    for (card = startcard; card; card = card->nextcard) {
        char *akostr, *searchname;
        char *cut_line = card->line;

        if (ciprefix(".subckt", cut_line))
            subcktcard = card;
        else if (ciprefix(".ends", cut_line))
            subcktcard = NULL;
        if (ciprefix(".model", cut_line)) {
            if ((akostr = strstr(cut_line, "ako:")) != NULL &&
                isspace_c(akostr[-1])) {
                akostr += 4;
                searchname = gettok(&akostr);
                cut_line = nexttok(cut_line);
                newmname = gettok(&cut_line);
                newmtype = gettok_noparens(&akostr);

                /* Find the model and do the replacement. */

                if (subcktcard)
                    returncard = find_model(subcktcard, card, searchname,
                                            newmname, newmtype, akostr);
                if (returncard || !subcktcard)
                    returncard = find_model(startcard, card, searchname,
                                            newmname, newmtype, akostr);
                tfree(searchname);
                tfree(newmname);
                tfree(newmtype);

                /* Replacement not possible, bail out. */

                if (returncard)
                    break;
            }
        }
    }
    return returncard;
}

struct vsmodels {
    char *modelname;
    char *subcktline;
    struct vsmodels *nextmodel;
};

/* insert a new model, just behind the given model */
static struct vsmodels *insert_new_model(
        struct vsmodels *vsmodel, char *name, char *subcktline)
{
    struct vsmodels *x = TMALLOC(struct vsmodels, 1);

    x->nextmodel = vsmodel ? vsmodel->nextmodel : NULL;
    x->modelname = copy(name);
    x->subcktline = copy(subcktline);
    if (vsmodel)
        vsmodel->nextmodel = x;
    else
        vsmodel = x;

    return vsmodel;
}

/* find the model */
static bool find_a_model(
        struct vsmodels *vsmodel, char *name, char *subcktline)
{
    struct vsmodels *x;
    for (x = vsmodel; vsmodel; vsmodel = vsmodel->nextmodel)
        if (eq(vsmodel->modelname, name) &&
                eq(vsmodel->subcktline, subcktline))
            return TRUE;
    return FALSE;
}

/* delete the vsmodels list */
static bool del_models(struct vsmodels *vsmodel)
{
    struct vsmodels *x;

    if (!vsmodel)
        return FALSE;

    while (vsmodel) {
        x = vsmodel->nextmodel;
        tfree(vsmodel->modelname);
        tfree(vsmodel->subcktline);
        tfree(vsmodel);
        vsmodel = x;
    }

    return TRUE;
}

/* Check for double '{', replace the inner '{', '}' by '(', ')'
   in .subckt, .model, or .param (which all three may stem from external sources) */
static void rem_double_braces(struct card* newcard)
{
    struct card* card;
    int slevel = 0;

    for (card = newcard; card; card = card->nextcard) {
        char* cut_line = card->line;
        if (ciprefix(".subckt", cut_line))
            slevel++;
        else if (ciprefix(".ends", cut_line))
            slevel--;
        if (ciprefix(".model", cut_line) || slevel > 0 || ciprefix(".param", cut_line)) {
            cut_line = strchr(cut_line, '{');
            if (cut_line) {
                int level = 1;
                cut_line++;
                while (*cut_line != '\0') {
                    if (*cut_line == '{') {
                        level++;
                        if (level > 1)
                            *cut_line = '(';
                    }
                    else if (*cut_line == '}') {
                        if (level > 1)
                            *cut_line = ')';
                        level--;
                    }
                    cut_line++;
                }
            }
        }
    }
}

#ifdef INTEGRATE_UDEVICES
static void list_the_cards(struct card *startcard, char *prefix)
{
    struct card *card;
    if (!startcard) { return; }
    for (card = startcard; card; card = card->nextcard) {
        char* cut_line = card->line;
        printf("%s %s\n", prefix, cut_line);
    }
}

static struct card *the_last_card(struct card *startcard)
{
    struct card *card, *lastcard = NULL;
    if (!startcard) { return NULL; }
    for (card = startcard; card; card = card->nextcard) {
        lastcard = card;
    }
    return lastcard;
}
 static void remove_old_cards(struct card *first, struct card *stop)
{
    struct card *x, *y, *next = NULL, *nexta = NULL;
    if (!first || !stop || (first == stop)) { return; }
    for (x = first; (x && (x != stop)); x = next) {
        if (x->line) { tfree(x->line); }
        if (x->error) { tfree(x->error); }
        for (y = x->actualLine; y; y = nexta) {
            if (y->line) { tfree(y->line); }
            if (y->error) { tfree(y->error); }
            nexta = y->nextcard;
            tfree(y);
        }
        next = x->nextcard;
        tfree(x);
    }

}

static struct card *u_instances(struct card *startcard)
{
    struct card *card, *returncard = NULL, *subcktcard = NULL;
    struct card *newcard = NULL, *last_newcard = NULL;
    int models_ok = 0, models_not_ok = 0;
    int udev_ok = 0, udev_not_ok = 0;
    bool create_called = FALSE, repeat_pass = FALSE;
    bool skip_next = FALSE;
    struct card *c = startcard;
    bool insub = FALSE;
    int ps_global_tmodels = 0;

    /* NOTE: PSpice ref. manual Product Version 16.5 page 105.
       Subcircuits can be nested. That is, an X device can appear between
       .SUBCKT and .ENDS commands. However, subcircuit definitions cannot
       be nested. That is, a .SUBCKT statement cannot appear in the
       statements between a .SUBCKT and a .ENDS.
    */
    if (!cp_getvar("ps_global_tmodels", CP_NUM, &ps_global_tmodels, 0)) {
        ps_global_tmodels = 0;
    }
    if (ps_global_tmodels) {
        initialize_udevice(NULL);
        /* First scan for global timing models */
        while (c) {
            char *line = c->line;
            if (ciprefix(".subckt", line)) {
                u_subckt_line(line);
                insub = TRUE;
            } else if (ciprefix(".ends", line)) {
                insub = FALSE;
            }
            if (!insub && ciprefix(".model", line)) {
                (void) u_process_model_line(line, TRUE);
            }
            c = c->nextcard;
        }
    }

    /* Now scan for subckts containing U* instances and local timing models */
    card = startcard;
    while (card) {
        char *cut_line = card->line;

        skip_next = FALSE;
        if (ciprefix(".subckt", cut_line)) {
            models_ok = models_not_ok = 0;
            udev_ok = udev_not_ok = 0;
            subcktcard = card;
            if (!repeat_pass) {
                if (create_called) {
                    cleanup_udevice(FALSE);
                }
                initialize_udevice(subcktcard->line);
                create_called = TRUE;
            }
        } else if (ciprefix(".ends", cut_line)) {
            if (repeat_pass) {
                newcard = replacement_udevice_cards();
                if (newcard) {
                    char *tmp = NULL, *pos, *posp, *new_str = NULL, *cl;
                    struct card* tmpc;
                    /* replace linenum_orig and linesource */
                    for (tmpc = newcard; tmpc; tmpc = tmpc->nextcard) {
                        tmpc->linenum_orig = subcktcard->linenum_orig;
                        tmpc->linesource = subcktcard->linesource;
                    }
                    DS_CREATE(ds_tmp, 128);
                    /* Pspice definition of .subckt card:
                       .SUBCKT <name> [node]*
                       + [OPTIONAL: < <interface node> = <default value> >*]
                       + [PARAMS: < <name> = <value> >* ]
                       + [TEXT: < <name> = <text value> >* ]
                       ...
                       .ENDS
                    */
                    cl = subcktcard->line;
                    tmp = TMALLOC(char, strlen(cl) + 1);
                    (void) memcpy(tmp, cl, strlen(cl) + 1);
                    pos = strstr(tmp, "optional:");
                    posp = strstr(tmp, "params:");
                    ds_clear(&ds_tmp);
                    /* If there is an optional: and a param: then posp > pos */
                    if (pos) {
                        /* Remove the optional: section if present */
                        *pos = '\0';
                        if (posp) {
                            ds_cat_str(&ds_tmp, tmp);
                            ds_cat_str(&ds_tmp, posp);
                            new_str = copy(ds_get_buf(&ds_tmp));
                        } else {
                            new_str = copy(tmp);
                        }
                    } else {
                        new_str = copy(tmp);
                    }
                    ds_free(&ds_tmp);
                    tfree(tmp);
                    remove_old_cards(subcktcard->nextcard, card);
                    subcktcard->nextcard = newcard;
                    tfree(subcktcard->line);
                    subcktcard->line = new_str;
                    if (ft_ngdebug) {
                        printf("%s\n", new_str);
                        list_the_cards(newcard, "Replacement:");
                    }
                    last_newcard = the_last_card(newcard);
                    if (last_newcard) {
                        last_newcard->nextcard = card; // the .ends card
                    }
                } else {
                    models_ok = models_not_ok = 0;
                    udev_ok = udev_not_ok = 0;
                }
            }
            if (models_not_ok > 0 || udev_not_ok > 0) {
                repeat_pass = FALSE;
                cleanup_udevice(FALSE);
                create_called = FALSE;
            } else if (udev_ok > 0) {
                repeat_pass = TRUE;
                card = subcktcard;
                skip_next = TRUE;
            } else {
                repeat_pass = FALSE;
                cleanup_udevice(FALSE);
                create_called = FALSE;
            }
            subcktcard = NULL;
        } else if (ciprefix(".model", cut_line)) {
            if (subcktcard && !repeat_pass) {
                // Add .model local to subckt
                if (!u_process_model_line(cut_line, FALSE)) {
                    models_not_ok++;
                } else {
                    models_ok++;
                }
            }
        } else if (ciprefix("u", cut_line) || ciprefix("x", cut_line)) {
            /* U* device instance or X* instance of a subckt */
            if (subcktcard) {
                if (repeat_pass) {
                    if (!u_process_instance(cut_line)) {
                        repeat_pass = FALSE;
                        cleanup_udevice(FALSE);
                        create_called = FALSE;
                        subcktcard = NULL;
                        models_ok = models_not_ok = 0;
                        udev_ok = udev_not_ok = 0;
                        skip_next = FALSE;
                    }
                } else {
                    if (u_check_instance(cut_line)) {
                        udev_ok++;
                    } else {
                        udev_not_ok++;
                    }
                }
            }
        } else {
            if (!ciprefix("*", cut_line)) {
                udev_not_ok++;
            }
        }

        if (!skip_next) {
            card = card->nextcard;
        }
    }
    if (create_called) {
        cleanup_udevice(FALSE);
    }
    cleanup_udevice(TRUE);
    return returncard;
}
#endif

/**** PSPICE to ngspice **************
* .model replacement in ako (a kind of) model descriptions
* replace the E source TABLE function by a B source pwl
* add predefined params TEMP, VT, GMIN to beginning of deck
* add predefined params TEMP, VT, GMIN to beginning of each .subckt call
* add .functions limit, pwr, pwrs, stp, if, int
* replace vswitch part S
  S1 D S DG GND SWN
 .MODEL SWN VSWITCH(VON = { 0.55 } VOFF = { 0.49 }
     RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) }  ROFF = { 1G })
* by
  as1 %vd(DG GND) % gd(D S) aswn
  .model aswn aswitch(cntl_off={0.49} cntl_on={0.55} r_off={1G}
  + r_on={ 1 / (2 * M*(W / LE)*(KPN / 2) * 10) } log = TRUE)
* replace vswitch part S_ST
  S1 D S DG GND S_ST
 .MODEL S_ST VSWITCH(VT = { 1.5 } VH = { 0.3 }
     RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) }  ROFF = { 1G })
* by the classical voltage controlled ngspice switch
  S1 D S DG GND SWN
 .MODEL S_ST SW(VT = { 1.5 } VH = { 0.3 }
     RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) }  ROFF = { 1G })
  switch parameter td is not yet supported
* replace & by &&
* replace | by ||
* in R instance, replace TC = xx1, xx2 by TC1=xx1 TC2=xx2
* replace T_ABS by temp and T_REL_GLOBAL by dtemp in .model cards
* get the area factor for diodes and bipolar devices
* in subcircuit .subckt and X lines with 'params:' statement
  replace comma separator by space. Do nothing if comma is inside of {}.
* in .model, if double curly braces {{}}, replace the inner by {()}  */
struct card *pspice_compat(struct card *oldcard)
{
    struct card *card, *newcard, *nextcard;
    struct vsmodels *modelsfound = NULL;
    int skip_control = 0;

    /* .model replacement in ako (a kind of) model descriptions
     * in first .subckt and top level only */
    struct card *errcard;
    if ((errcard = ako_model(oldcard)) != NULL) {
        fprintf(stderr, "Error: no model found for %s\n", errcard->line);
        controlled_exit(1);
    }

    /* Process .distribution cards. */
    do_distribution(oldcard);

    /* replace TABLE function in E source */
    replace_table(oldcard);

    /* remove double braces */
    rem_double_braces(oldcard);

    /* add predefined params TEMP, VT, GMIN to beginning of deck */
    char *new_str = copy(".param temp = 'temper'");
    newcard = insert_new_line(NULL, new_str, 1, 0, "internal");
    new_str = copy(".param vt = '(temper + 273.15) * 8.6173303e-5'");
    nextcard = insert_new_line(newcard, new_str, 2, 0, "internal");
    new_str = copy(".param gmin = 1e-12");
    nextcard = insert_new_line(nextcard, new_str, 3, 0, "internal");
    /* add funcs limit, pwr, pwrs, stp, if, int */
    /* LIMIT( Output Expression, Limit1, Limit2)
       Output will stay between the two limits given. */
    new_str = copy(".func limit(x, a, b) { ternary_fcn(a > b, max(min(x, a), b), max(min(x, b), a)) }");
    nextcard = insert_new_line(nextcard, new_str, 4, 0, "internal");
    new_str = copy(".func pwr(x, a) { pow(x, a) }");
    nextcard = insert_new_line(nextcard, new_str, 5, 0, "internal");
    new_str = copy(".func pwrs(x, a) { sgn(x) * pow(x, a) }");
    nextcard = insert_new_line(nextcard, new_str, 6, 0, "internal");
    new_str = copy(".func stp(x) { u(x) }");
    nextcard = insert_new_line(nextcard, new_str, 7, 0, "internal");
    new_str = copy(".func if(a, b, c) {ternary_fcn( a , b , c )}");
    nextcard = insert_new_line(nextcard, new_str, 8, 0, "internal");
    new_str = copy(".func int(x) { sgn(x)*floor(abs(x)) }");
    nextcard = insert_new_line(nextcard, new_str, 9, 0, "internal");
    nextcard->nextcard = oldcard;

#ifdef INTEGRATE_UDEVICES
    {
        struct card *ucard;
#ifdef TRACE
        list_the_cards(newcard, "Before udevices");
#endif
        ucard = u_instances(newcard);
#ifdef TRACE
        list_the_cards(newcard, "After udevices");
#endif
    }
#endif


    /* add predefined parameters TEMP, VT after each subckt call */
    /* FIXME: This should not be necessary if we had a better sense of
    hierarchy during the evaluation of TEMPER */
    for (card = newcard; card; card = card->nextcard) {
        char *cut_line = card->line;
        if (ciprefix(".subckt", cut_line)) {
            new_str = copy(".param temp = 'temper'");
            nextcard = insert_new_line(card, new_str, 0, card->linenum_orig, card->linesource);
            new_str = copy(".param vt = '(temper + 273.15) * 8.6173303e-5'");
            nextcard = insert_new_line(nextcard, new_str, 1, card->linenum_orig, card->linesource);
            /* params: replace comma separator by space.
               Do nothing if you are inside of { }. */
            char* parastr = strstr(cut_line, "params:");
            int brace = 0;
            if (parastr) {
                parastr += 8;
                while (*parastr) {
                    if (*parastr == '{')
                        brace++;
                    else if (*parastr == '}')
                        brace--;
                    if (brace == 0 && *parastr == ',')
                        *parastr = ' ';
                    parastr++;
                }
            }
        }
    }

    /* .model xxx NMOS/PMOS level=6 --> level = 8,  version=3.2.4
       .model xxx NMOS/PMOS level=7 --> level = 8,  version=3.2.4
       .model xxx NMOS/PMOS level=5 --> only available per Veriloga, OpenVAF and OSDI
       .model xxx NMOS/PMOS level=8 --> level = 14, version=4.5.0
       .model xxx NPN/PNP   level=2 --> only available per Veriloga, OpenVAF and OSDI
       .model xxx LPNP      level=n --> level = 1 subs=-1
       Remove any Monte - Carlo variation parameters from .model cards.*/
    for (card = newcard; card; card = card->nextcard) {
        char* cut_line = card->line;
        if (ciprefix(".model", cut_line)) {
            char* modname, *modtype, *curr_line;
            int i;
            char *cut_del = curr_line = cut_line = inp_remove_ws(copy(cut_line));
            cut_line = nexttok(cut_line); /* skip .model */
            modname = gettok(&cut_line); /* save model name */
            if (!modname) {
                fprintf(stderr, "Error: No model name given for %s\n", curr_line);
                controlled_exit(EXIT_BAD);
            }
            modtype = gettok_noparens(&cut_line); /* save model type */
            if (!modtype) {
                fprintf(stderr, "Error: No model type given for %s\n", curr_line);
                controlled_exit(EXIT_BAD);
            }
            if (cieq(modtype, "NMOS") || cieq(modtype, "PMOS")) {
                char* lv = strstr(cut_line, "level=");
                if (lv) {
                    int ll;
                    lv = lv + 6;
                    char* ntok = gettok(&lv);
                    ll = atoi(ntok);
                    switch (ll) {
                    case 5:
                        {
                        /* EKV 2.6 only per OSDI and OpenVAF */
                        fprintf(stderr, "Error: MOS model level 5, EKV 2.6, is not available as an intrinsic model.\n");
                        fprintf(stderr, "    Please consider using a Verilog-A model, OpenVAF compilation,\n");
                        fprintf(stderr, "    and the ngspice OSDI interface (see ngspice manual chapter 9).\n");
                        controlled_exit(EXIT_BAD);
                        }
                        break;
                    case 6:
                    case 7:
                        {
                        /* BSIM3 version 3.2.4 */
                        char* newline = tprintf(".model %s %s level=8 version=3.2.4 %s", modname, modtype, lv);
                        tfree(card->line);
                        card->line = curr_line = newline;
                        }
                        break;
                    case 8:
                        {
                        /* BSIM4 version 4.5.0 */
                        char* newline = tprintf(".model %s %s level=14 version=4.5.0 %s", modname, modtype, lv);
                        tfree(card->line);
                        card->line = curr_line = newline;
                        }
                        break;
                    default:
                        break;
                    }
                    tfree(ntok);
                }
            }
            else if (cieq(modtype, "NPN") || cieq(modtype, "PNP")) {
                char* lv = strstr(cut_line, "level=");
                if (lv) {
                    int ll;
                    lv = lv + 6;
                    char* ntok = gettok(&lv);
                    ll = atoi(ntok);
                    switch (ll) {
                    case 2:
                        {
                        /* MEXTRAM 504.12.1 only per OSDI and OpenVAF */
                        fprintf(stderr, "Error: Bipolar model level 2, MEXTRAM 504.12.1, is not available as an intrinsic model.\n");
                        fprintf(stderr, "    Please consider using a Verilog-A model, OpenVAF compilation,\n");
                        fprintf(stderr, "    and the ngspice OSDI interface (see ngspice manual chapter 9).\n");
                        controlled_exit(EXIT_BAD);
                        }
                        break;
                    default:
                        break;
                    }
                    tfree(ntok);
                }
            }
            else if (cieq(modtype, "LPNP")) {
                /* lateral PNP enabled */
                char* newline = tprintf(".model %s PNP level=1 subs=-1 %s", modname, cut_line);
                tfree(card->line);
                card->line = curr_line = newline;
            }
            tfree(modname);
            tfree(modtype);

            /* Remove any Monte-Carlo variation parameters. They qualify
             * a previous parameter, so there must be at least 3 tokens.
             * There are two keywords "dev" (different values for each device),
             * and "lot" (all devices of this model share a value).
             * The keyword may be optionally followed by '/' and
             * a probability distribution name, then there must be '=' and
             * a value, then an optional '%' indicating relative rather than
             * absolute variation. Allow muliple lot and dev on a single .model line.
             */
            bool remdevlot = FALSE;
            cut_line = curr_line;
            for (i = 0; i < 3; i++)
                cut_line = nexttok(cut_line);
            while (cut_line) {
                if (!strncmp(cut_line, "dev=", 4) ||
                    !strncmp(cut_line, "lot=", 4)) {
                    while (*cut_line && !isspace_c(*cut_line)) {
                        *cut_line++ = ' ';
                    }
                    remdevlot = TRUE;
                    cut_line = skip_ws(cut_line);
                    continue;
                }
                cut_line = nexttok(cut_line);
            }
            if (remdevlot) {
                tfree(card->line);
                card->line = curr_line;
            }
            else
                tfree(cut_del);
        } // if .model
    } // for loop through all cards

    /* x ... params: p1=val1, p2=val2 replace comma separator by space.
       Do nothing if you are inside of { }. */
    for (card = newcard; card; card = card->nextcard) {
        char* cut_line = card->line;
        if (ciprefix("x", cut_line)) {
            char* parastr = strstr(cut_line, "params:");
            int brace = 0;
            if (parastr) {
                parastr += 8;
                while (*parastr) {
                    if (*parastr == '{')
                        brace++;
                    else if (*parastr == '}')
                        brace--;
                    if (brace == 0 && *parastr == ',')
                        *parastr = ' ';
                    parastr++;
                }
            }
        }
    }

    /* in R instance, replace TC = xx1, xx2 by TC1=xx1 TC2=xx2 */
    for (card = newcard; card; card = card->nextcard) {
        char *cut_line = card->line;

        /* exclude any command inside .control ... .endc */
        if (ciprefix(".control", cut_line)) {
            skip_control++;
            continue;
        }
        else if (ciprefix(".endc", cut_line)) {
            skip_control--;
            continue;
        }
        else if (skip_control > 0) {
            continue;
        }

        if (*cut_line == 'r' || *cut_line == 'l' || *cut_line == 'c') {
            /* Skip name and two nodes */
            char *ntok = nexttok(cut_line);
            ntok = nexttok(ntok);
            ntok = nexttok(ntok);
            if (!ntok || *ntok == '\0') {
                fprintf(stderr, "Error: Missing token in line %d:\n%s\n",
                        card->linenum, cut_line);
                fprintf(stderr, "    Please correct the input file\n");
                controlled_exit(1);
            }
            char *tctok = search_plain_identifier(ntok, "tc");
            if (tctok) {
                char *tc1, *tc2;
                char *tctok1 = strchr(tctok, '=');
                if (tctok1)
                    /* skip '=' */
                    tctok1 += 1;
                else
                    /* no '=' found, skip 'tc' */
                    tctok1 = tctok + 2;
                /* tc1 may be an expression, enclosed in {} */
                if (*tctok1 == '{') {
                    tc1 = gettok_char(&tctok1, '}', TRUE, TRUE);
                }
                else {
                    tc1 = gettok_node(&tctok1);
                }
                /* skip spaces and commas */
                while (isspace_c(*tctok1) || (*tctok1 == ','))
                   tctok1++;
                /* tc2 may be an expression, enclosed in {} */
                if (*tctok1 == '{') {
                    tc2 = gettok_char(&tctok1, '}', TRUE, TRUE);
                }
                else {
                    tc2 = gettok_node(&tctok1);
                }
                tctok[-1] = '\0';
                char *newstring;
                if (tc1 && tc2)
                    newstring = tprintf("%s tc1=%s tc2=%s",
                            cut_line, tc1, tc2);
                else if (tc1)
                    newstring = tprintf("%s tc1=%s", cut_line, tc1);
                else {
                    fprintf(stderr,
                            "Warning: tc without parameters removed in line "
                            "\n   %s\n",
                            cut_line);
                    continue;
                }
                tfree(card->line);
                card->line = newstring;
                tfree(tc1);
                tfree(tc2);
            }
        }
    }

    /* replace & with && , | with || , *# with * # , and ~ with ! */
    for (card = newcard; card; card = card->nextcard) {
        char *t;
        char *cut_line = card->line;

        /* we don't have command lines in a PSPICE model */
        if (ciprefix("*#", cut_line)) {
            char *tmpstr = tprintf("* #%s", cut_line + 2);
            tfree(card->line);
            card->line = tmpstr;
            continue;
        }

        if (*cut_line == '*')
            continue;

        if (*cut_line == '\0')
            continue;

        /* exclude any command inside .control ... .endc */
        if (ciprefix(".control", cut_line)) {
            skip_control++;
            continue;
        }
        else if (ciprefix(".endc", cut_line)) {
            skip_control--;
            continue;
        }
        else if (skip_control > 0) {
            continue;
        }
        if ((t = strstr(card->line, "&")) != NULL) {
            while (t && (t[1] != '&')) {
                char *tt = NULL;
                char *tn = copy(t + 1); /*skip |*/
                char *strbeg = copy_substring(card->line, t);
                tfree(card->line);
                card->line = tprintf("%s&&%s", strbeg, tn);
                tfree(strbeg);
                tfree(tn);
                t = card->line;
                while ((t = strstr(t, "&&")) != NULL)
                    tt = t = t + 2;
                if (!tt)
                    break;
                else
                    t = strstr(tt, "&");
            }
        }
        if ((t = strstr(card->line, "|")) != NULL) {
            while (t && (t[1] != '|')) {
                char *tt = NULL;
                char *tn = copy(t + 1); /*skip |*/
                char *strbeg = copy_substring(card->line, t);
                tfree(card->line);
                card->line = tprintf("%s||%s", strbeg, tn);
                tfree(strbeg);
                tfree(tn);
                t = card->line;
                while ((t = strstr(t, "||")) != NULL)
                    tt = t = t + 2;
                if (!tt)
                    break;
                else
                    t = strstr(tt, "|");
            }
        }
        /* We may have '~' in path names or A devices */
        if (ciprefix(".inc", card->line) || ciprefix(".lib", card->line) ||
                ciprefix("A", card->line))
            continue;

        if ((t = strstr(card->line, "~")) != NULL) {
            while (t) {
                *t = '!';
                t = strstr(t, "~");
            }
        }
    }

    /* replace T_ABS by temp, T_REL_GLOBAL by dtemp, and T_MEASURED by TNOM
    in .model cards. What about T_REL_LOCAL ? T_REL_LOCAL is used in
    conjunction with AKO and is not yet implemented.  */
    for (card = newcard; card; card = card->nextcard) {
        char *cut_line = card->line;
        if (ciprefix(".model", cut_line)) {
            char *t_str;
            if ((t_str = strstr(cut_line, "t_abs")) != NULL)
                memcpy(t_str, " temp", 5);
            else if ((t_str = strstr(cut_line, "t_rel_global")) != NULL)
                memcpy(t_str, "       dtemp", 12);
            else if ((t_str = strstr(cut_line, "t_measured")) != NULL)
                memcpy(t_str, "      tnom", 10);
        }
    }

    /* get the area factor for diodes and bipolar devices
    d1 n1 n2 dmod 7 --> d1 n1 n2 dmod area=7
    q2 n1 n2 n3 [n4] bjtmod 1.35 --> q2 n1 n2 n3 n4 bjtmod area=1.35
    q3 1 2 3 4 bjtmod 1.45 --> q2 1 2 3 4 bjtmod area=1.45
    */
    for (card = newcard; card; card = card->nextcard) {
        char *cut_line = card->line;
        if (*cut_line == '*')
            continue;
        // exclude any command inside .control ... .endc
        if (ciprefix(".control", cut_line)) {
            skip_control++;
            continue;
        }
        else if (ciprefix(".endc", cut_line)) {
            skip_control--;
            continue;
        }
        else if (skip_control > 0) {
            continue;
        }
        if (*cut_line == 'q') {
            /* According to PSPICE Reference Guide the fourth (substrate) node
            has to be put into [] if it is not just a number */
            cut_line = nexttok(cut_line); //.model
            cut_line = nexttok(cut_line); // node1
            cut_line = nexttok(cut_line); // node2
            cut_line = nexttok(cut_line); // node3
            if (!cut_line || *cut_line == '\0') {
                fprintf(stderr, "Line no. %d, %s, missing tokens\n",
                        card->linenum_orig, card->line);
                if (ft_stricterror)
                    controlled_exit(1);
                else
                    continue;
            }
            if (*cut_line == '[') { // node4 not a number
                *cut_line = ' ';
                cut_line = strchr(cut_line, ']');
                *cut_line = ' ';
                cut_line = skip_ws(cut_line);
                cut_line = nexttok(cut_line); // model name
            }
            else { // if an integer number, it is node4
                bool is_node4 = TRUE;
                while (*cut_line && !isspace_c(*cut_line))
                    if (!isdigit_c(*cut_line++))
                        is_node4 = FALSE; // already model name
                if (is_node4) {
                    cut_line = nexttok(cut_line); // model name
                }
            }
            if (cut_line && *cut_line &&
                    atof(cut_line) > 0.0) { // size of area is a real number
                char *tmpstr1 = copy_substring(card->line, cut_line);
                char *tmpstr2 = tprintf("%s area=%s", tmpstr1, cut_line);
                tfree(tmpstr1);
                tfree(card->line);
                card->line = tmpstr2;
            }
            else if (cut_line && *cut_line &&
                    *(skip_ws(cut_line)) ==
                            '{') { // size of area is parametrized inside {}
                char *tmpstr1 = copy_substring(card->line, cut_line);
                char *tmpstr2 = gettok_char(&cut_line, '}', TRUE, TRUE);
                char *tmpstr3 =
                        tprintf("%s area=%s %s", tmpstr1, tmpstr2, cut_line);
                tfree(tmpstr1);
                tfree(tmpstr2);
                tfree(card->line);
                card->line = tmpstr3;
            }
        }
        else if (*cut_line == 'd') {
            cut_line = nexttok(cut_line); //.model
            cut_line = nexttok(cut_line); // node1
            cut_line = nexttok(cut_line); // node2
            if (!cut_line || *cut_line == '\0') {
                fprintf(stderr, "Line no. %d, %s, missing tokens\n",
                        card->linenum_orig, card->line);
                if (ft_stricterror)
                    controlled_exit(1);
                else
                    continue;
            }
            cut_line = nexttok(cut_line); // model name
            if (cut_line && *cut_line &&
                    atof(cut_line) > 0.0) { // size of area
                char *tmpstr1 = copy_substring(card->line, cut_line);
                char *tmpstr2 = tprintf("%s area=%s", tmpstr1, cut_line);
                tfree(tmpstr1);
                tfree(card->line);
                card->line = tmpstr2;
            }
        }
    }

    /* if vswitch part s, replace
     * S1 D S DG GND SWN
     * .MODEL SWN VSWITCH ( VON = {0.55} VOFF = {0.49}
     *         RON={1/(2*M*(W/LE)*(KPN/2)*10)}  ROFF={1G} )
     * by
     * a1 %v(DG) %gd(D S) swa
     * .MODEL SWA aswitch(cntl_off=0.49 cntl_on=0.55 r_off=1G
     *         r_on={1/(2*M*(W/LE)*(KPN/2)*10)} log=TRUE)
     *
     * if vswitch part s_st, don't replace instance, only model
     * replace
     * S1 D S DG GND S_ST
     * .MODEL S_ST VSWITCH(VT = { 1.5 } VH = { 0.s }
           RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) }  ROFF = { 1G })
     * by the classical voltage controlled ngspice switch
     * S1 D S DG GND S_ST
     * .MODEL S_ST SW(VT = { 1.5 } VH = { 0.s }
             RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) }  ROFF = { 1G })
     * vswitch delay parameter td is not yet supported

     * simple hierachy, as nested subcircuits are not allowed in PSPICE */

    /* first scan: find the vswitch models, transform them and put the S models
       into a list */
    for (card = newcard; card; card = card->nextcard) {
        char *str;
        static struct card *subcktline = NULL;
        static int nesting = 0;
        char *cut_line = card->line;
        if (ciprefix(".subckt", cut_line)) {
            subcktline = card;
            nesting++;
        }
        if (ciprefix(".ends", cut_line))
            nesting--;

        if (ciprefix(".model", card->line) && strstr(card->line, "vswitch")) {
            char *modname;

            str = card->line = inp_remove_ws(card->line);
            str = nexttok(str); /* throw away '.model' */
            INPgetNetTok(&str, &modname, 0); /* model name */
            if (!ciprefix("vswitch", str)) {
                tfree(modname);
                continue;
            }
            str = nexttok_noparens(str); /* throw away 'vswitch' */
            /* S_ST switch (parameters ron, roff, vt, vh)
             * we have to find 0 to 4 parameters, identified by 'vh=' etc.
             * Parameters not found have to be replaced by their default values. */
            if (strstr(str, "vt=") || strstr(str, "vh=")) {
                char* newstr;
                char* lstr = copy(str);
                char* partstr = strstr(lstr, "ron=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "ron=1.0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                partstr = strstr(lstr, "roff=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "roff=1.0e12", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                partstr = strstr(lstr, "vt=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "vt=0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                partstr = strstr(lstr, "vh=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "vh=0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                tfree(card->line);
                if (lstr[strlen(lstr) - 1] == ')')
                    card->line = tprintf(".model %s sw ( %s", modname, lstr);
                else
                    card->line = tprintf(".model %s sw %s", modname, lstr);
                tfree(lstr);
                tfree(modname);
            }
            /* S vswitch  (parameters ron, roff, von, voff) */
            /* We have to find 0 to 4 parameters, identified by 'von=' etc. and
             * replace them by the pswitch code model parameters
             * replace VON by cntl_on, VOFF by cntl_off, RON by r_on, and ROFF by r_off.
             * Parameters not found have to be replaced by their default values. */
            else if (strstr(str, "von=") || strstr(str, "voff=")) {
                char* newstr, *begstr;
                char* lstr = copy(str);
                /* ron */
                char* partstr = strstr(lstr, "ron=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "r_on=1.0", lstr);  //default value
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s r_on%s", begstr, partstr + 3);
                    tfree(begstr);
                }
                tfree(lstr);
                lstr = newstr;
                /* roff */
                partstr = strstr(lstr, "roff=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "r_off=1.0e6", lstr);  //default value
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s r_off%s", begstr, partstr + 4);
                    tfree(begstr);
                }
                tfree(lstr);
                lstr = newstr;
                /* von */
                partstr = strstr(lstr, "von=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "cntl_on=1", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s cntl_on%s", begstr, partstr + 3);
                    tfree(begstr);
                }
                tfree(lstr);
                lstr = newstr;
                /* voff */
                partstr = strstr(lstr, "voff=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "cntl_off=0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s cntl_off%s", begstr, partstr + 4);
                    tfree(begstr);
                }
                tfree(lstr);
                lstr = newstr;
                tfree(card->line);
                if (lstr[strlen(lstr) - 1] == ')')
                    card->line = tprintf(".model a%s pswitch( log=TRUE %s", modname, lstr);
                else
                    card->line = tprintf(".model a%s pswitch(%s log=TRUE)", modname, lstr);
                tfree(lstr);
                /* add to list, to change vswitch instance to code model line */
                if (nesting > 0)
                    modelsfound = insert_new_model(
                        modelsfound, modname, subcktline->line);
                else
                    modelsfound = insert_new_model(modelsfound, modname, "top");
                tfree(modname);
            }
            else {
                fprintf(stderr, "Error: Bad switch model in line %s\n", card->line);
            }
        }
    }

    /* no need to continue if no vswitch is found */
    if (!modelsfound)
        goto iswi;

    /* second scan: find the switch instances s calling a vswitch model and
     * transform them */
    for (card = newcard; card; card = card->nextcard) {
        static struct card *subcktline = NULL;
        static int nesting = 0;
        char *cut_line = card->line;
        if (*cut_line == '*')
            continue;
        // exclude any command inside .control ... .endc
        if (ciprefix(".control", cut_line)) {
            skip_control++;
            continue;
        }
        else if (ciprefix(".endc", cut_line)) {
            skip_control--;
            continue;
        }
        else if (skip_control > 0) {
            continue;
        }
        if (ciprefix(".subckt", cut_line)) {
            subcktline = card;
            nesting++;
        }
        if (ciprefix(".ends", cut_line))
            nesting--;

        if (ciprefix("s", cut_line)) {
            /* check for the model name */
            int i;
            bool good = TRUE;
            char *stoks[6];
            for (i = 0; i < 6; i++) {
                stoks[i] = gettok_node(&cut_line);
                if (!stoks[i]) {
                    int ii;
                    fprintf(stderr,
                        "Error: bad syntax in line %d\n  %s\n"
                        "from file\n"
                        "  %s\n",
                        card->linenum_orig, card->line, card->linesource);
                    good = FALSE;
                    /* null the rest of stoks */
                    for (ii = i + 1; ii < 6; ii++) {
                        stoks[ii] = NULL;
                    }
                    break;
                }
            }
            if (!good) {
                for (i = 0; i < 6; i++)
                    tfree(stoks[i]);
                continue;
            }
            /* rewrite s line and replace it if a model is found */
            if ((nesting > 0) &&
                    find_a_model(modelsfound, stoks[5], subcktline->line)) {
                tfree(card->line);
                card->line = tprintf("a%s %%gd(%s %s) %%gd(%s %s) a%s",
                        stoks[0], stoks[3], stoks[4], stoks[1], stoks[2],
                        stoks[5]);
            }
            /* if model is not within same subcircuit, search at top level */
            else if (find_a_model(modelsfound, stoks[5], "top")) {
                tfree(card->line);
                card->line = tprintf("a%s %%gd(%s %s) %%gd(%s %s) a%s",
                        stoks[0], stoks[3], stoks[4], stoks[1], stoks[2],
                        stoks[5]);
            }
            for (i = 0; i < 6; i++)
                tfree(stoks[i]);
        }
    }
    del_models(modelsfound);
    modelsfound = NULL;

iswi:;

    /* if iswitch part s, replace
     * W1 D S VC SWN
     * .MODEL SWN ISWITCH ( ION = {0.55} IOFF = {0.49}
     *         RON={1/(2*M*(W/LE)*(KPN/2)*10)}  ROFF={1G} )
     * by
     * a1 %v(DG) %gd(D S) swa
     * .MODEL SWA aswitch(cntl_off=0.49 cntl_on=0.55 r_off=1G
     *         r_on={1/(2*M*(W/LE)*(KPN/2)*10)} log=TRUE)
     *
     * if iswitch part s_st (short transition), don't replace instance, but only model
     * replace
     * W1 D S VC S_ST
     * .MODEL S_ST ISWITCH(IT = { 1.5 } IH = { 0.2 }
           RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) }  ROFF = { 1G })
     * by the classical current controlled ngspice switch
     * W1 D S DG GND S_ST
     * .MODEL S_ST CSW(IT = { 1.5 } IH = { 0.2 }
             RON = { 1 / (2 * M*(W / LE)*(KPN / 2) * 10) }  ROFF = { 1G })
     * iswitch delay parameter td is not yet supported

     * simple hierachy, as nested subcircuits are not allowed in PSPICE */

     /* first scan: find the iswitch models, transform them and put them into a
      * list */
    for (card = newcard; card; card = card->nextcard) {
        char* str;
        static struct card* subcktline = NULL;
        static int nesting = 0;
        char* cut_line = card->line;
        if (ciprefix(".subckt", cut_line)) {
            subcktline = card;
            nesting++;
        }
        if (ciprefix(".ends", cut_line))
            nesting--;

        if (ciprefix(".model", card->line) && strstr(card->line, "iswitch")) {
            char* modname;

            card->line = str = inp_remove_ws(card->line);
            str = nexttok(str); /* throw away '.model' */
            INPgetNetTok(&str, &modname, 0); /* model name */
            if (!ciprefix("iswitch", str)) {
                tfree(modname);
                continue;
            }
            str = nexttok_noparens(str); /* throw away 'iswitch' */
            /* S_ST switch (parameters ron, roff, it, ih)
             * we have to find 0 to 4 parameters, identified by 'ih=' etc.
             * Parameters not found have to be replaced by their default values. */
            if (strstr(str, "it=") || strstr(str, "ih=")) {
                char* newstr;
                char* lstr = copy(str);
                char* partstr = strstr(lstr, "ron=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "ron=1.0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                partstr = strstr(lstr, "roff=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "roff=1.0e12", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                partstr = strstr(lstr, "it=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "it=0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                partstr = strstr(lstr, "ih=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "ih=0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                tfree(card->line);
                if (lstr[strlen(lstr) - 1] == ')')
                    card->line = tprintf(".model %s csw ( %s", modname, lstr);
                else
                    card->line = tprintf(".model %s csw %s", modname, lstr);
                tfree(lstr);
                tfree(modname);
            }
            /* S vswitch  (parameters ron, roff, ion, ioff) */
            /* We have to find 0 to 4 parameters, identified by 'ion=' etc. and
             * replace them by the pswitch code model parameters
             * replace VON by cntl_on, VOFF by cntl_off, RON by r_on, and ROFF by r_off.
             * Parameters not found have to be replaced by their default values. */
            else if (strstr(str, "ion=") || strstr(str, "ioff=")) {
                char* newstr, * begstr;
                char* lstr = copy(str);
                /* ron */
                char* partstr = strstr(lstr, "ron=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "r_on=1.0", lstr);  //default value
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s r_on%s", begstr, partstr + 3);
                }
                tfree(lstr);
                lstr = newstr;
                /* roff */
                partstr = strstr(lstr, "roff=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "r_off=1.0e6", lstr);  //default value
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s r_off%s", begstr, partstr + 4);
                }
                tfree(lstr);
                lstr = newstr;
                /* von */
                partstr = strstr(lstr, "ion=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "cntl_on=1", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s cntl_on%s", begstr, partstr + 3);
                }
                tfree(lstr);
                lstr = newstr;
                /* voff */
                partstr = strstr(lstr, "ioff=");
                if (!partstr) {
                    newstr = tprintf("%s %s", "cntl_off=0", lstr);  //default value
                    tfree(lstr);
                    lstr = newstr;
                }
                else {
                    begstr = copy_substring(lstr, partstr);
                    newstr = tprintf("%s cntl_off%s", begstr, partstr + 4);
                }
                tfree(lstr);
                lstr = newstr;
                tfree(card->line);
                if (lstr[strlen(lstr) - 1] == ')')
                    card->line = tprintf(".model a%s aswitch( log=TRUE limit=TRUE %s", modname, lstr);
                else
                    card->line = tprintf(".model a%s aswitch(%s log=TRUE limit=TRUE)", modname, lstr);
                tfree(lstr);
                /* add to list, to change vswitch instance to code model line */
                if (nesting > 0)
                    modelsfound = insert_new_model(
                        modelsfound, modname, subcktline->line);
                else
                    modelsfound = insert_new_model(modelsfound, modname, "top");
                tfree(modname);
            }
            else {
                fprintf(stderr, "Error: Bad switch model in line %s\n", card->line);
            }
        }
    }

#if(0)
            /* we have to find 4 parameters, identified by '=', separated by
             * spaces */
            char* equalptr[4];
            equalptr[0] = strstr(str, "=");
            if (!equalptr[0]) {
                fprintf(stderr,
                    "Error: not enough parameters in iswitch model\n   "
                    "%s\n",
                    card->line);
                controlled_exit(1);
            }
            for (i = 1; i < 4; i++) {
                equalptr[i] = strstr(equalptr[i - 1] + 1, "=");
                if (!equalptr[i]) {
                    fprintf(stderr,
                        "Error: not enough parameters in iswitch model\n "
                        "  %s\n",
                        card->line);
                    controlled_exit(1);
                }
            }
            for (i = 0; i < 4; i++) {
                equalptr[i] = skip_back_ws(equalptr[i], str);
                while (*(equalptr[i]) != '(' && !isspace_c(*(equalptr[i])) &&
                    *(equalptr[i]) != ',')
                    (equalptr[i])--;
                (equalptr[i])++;
            }
            for (i = 0; i < 3; i++)
                modpar[i] = copy_substring(equalptr[i], equalptr[i + 1] - 1);
            if (strrchr(equalptr[3], ')'))
                modpar[3] = copy_substring(
                    equalptr[3], strrchr(equalptr[3], ')'));
            else
                /* iswitch defined without parens */
                modpar[3] = copy(equalptr[3]);

            /* check if we have parameters IT and IH */
            for (i = 0; i < 4; i++) {
                if (ciprefix("ih", modpar[i]))
                    have_ih = TRUE;
                if (ciprefix("it", modpar[i]))
                    have_it = TRUE;
            }
            if (have_ih && have_it) {
                /* replace iswitch by csw */
                char* vs = strstr(card->line, "iswitch");
                memmove(vs, "    csw", 7);
            }
            else {
                /* replace ION by cntl_on, IOFF by cntl_off, RON by r_on, and
                 * ROFF by r_off */
                tfree(card->line);
                rep_spar(modpar);
                card->line = tprintf(
                    /* FIXME: a new switch derived from pswitch with vnam input is due */
                    ".model a%s aswitch(%s %s %s %s  log=TRUE  limit=TRUE)", modname,
                   modpar[0], modpar[1], modpar[2], modpar[3]);
            }
            for (i = 0; i < 4; i++)
                tfree(modpar[i]);
            if (nesting > 0)
                modelsfound = insert_new_model(
                    modelsfound, modname, subcktline->line);
            else
                modelsfound = insert_new_model(modelsfound, modname, "top");
            tfree(modname);
        }
    }
#endif
    /* no need to continue if no iswitch is found */
    if (!modelsfound)
        return newcard;

    /* second scan: find the switch instances s calling an iswitch model and
     * transform them */
    for (card = newcard; card; card = card->nextcard) {
        static struct card* subcktline = NULL;
        static int nesting = 0;
        char* cut_line = card->line;
        if (*cut_line == '*')
            continue;
        // exclude any command inside .control ... .endc
        if (ciprefix(".control", cut_line)) {
            skip_control++;
            continue;
        }
        else if (ciprefix(".endc", cut_line)) {
            skip_control--;
            continue;
        }
        else if (skip_control > 0) {
            continue;
        }
        if (ciprefix(".subckt", cut_line)) {
            subcktline = card;
            nesting++;
        }
        if (ciprefix(".ends", cut_line))
            nesting--;

        if (ciprefix("w", cut_line)) {
            /* check for the model name */
            int i;
            char* stoks[5];
            for (i = 0; i < 5; i++)
                stoks[i] = gettok_node(&cut_line);
            /* rewrite w line and replace it if a model is found */
            if ((nesting > 0) &&
                find_a_model(modelsfound, stoks[4], subcktline->line)) {
                tfree(card->line);
                card->line = tprintf("a%s %%vnam(%s) %%gd(%s %s) a%s",
                    stoks[0], stoks[3], stoks[1], stoks[2],
                    stoks[4]);
            }
            /* if model is not within same subcircuit, search at top level */
            else if (find_a_model(modelsfound, stoks[4], "top")) {
                tfree(card->line);
                card->line = tprintf("a%s %%vnam(%s) %%gd(%s %s) a%s",
                    stoks[0], stoks[3], stoks[1], stoks[2],
                    stoks[4]);
            }
            for (i = 0; i < 5; i++)
                tfree(stoks[i]);
        }
    }
    del_models(modelsfound);

    return newcard;
}



/* do not modify oldcard address, insert everything after first line only */
void pspice_compat_a(struct card *oldcard)
{
    oldcard->nextcard = pspice_compat(oldcard->nextcard);
}


/**** LTSPICE to ngspice **************
 * add functions uplim, dnlim
 * Replace
 * D1 A K SDMOD
 * .MODEL SDMOD D (Roff=1000 Ron=0.7  Rrev=0.2  Vfwd=1  Vrev=10 Revepsilon=0.2
 *         Epsilon=0.2 Ilimit=7 Revilimit=7)
 * by
 * ad1 a k asdmod
 * .model asdmod sidiode(Roff=1000 Ron=0.7  Rrev=0.2  Vfwd=1  Vrev=10
 *         Revepsilon=0.2 Epsilon=0.2 Ilimit=7 Revilimit=7)
 * Remove '.backanno'
 */
struct card *ltspice_compat(struct card *oldcard)
{
    struct card *card, *newcard, *nextcard;
    struct vsmodels *modelsfound = NULL;
    int skip_control = 0;


    /* remove double braces only if not yet done in pspice_compat() */
    if (!newcompat.ps)
        rem_double_braces(oldcard);

    /* add funcs uplim, dnlim to beginning of deck */
    char *new_str =
            copy(".func uplim(x, pos, z) { min(x, pos - z) + (1 - "
                 "(min(max(0, x - pos + z), 2 * z) / 2 / z - 1)**2)*z }");
    newcard = insert_new_line(NULL, new_str, 1, 0, "internal");
    new_str = copy(".func dnlim(x, neg, z) { max(x, neg + z) - (1 - "
                   "(min(max(0, -x + neg + z), 2 * z) / 2 / z - 1)**2)*z }");
    nextcard = insert_new_line(newcard, new_str, 2, 0, "internal");
    new_str = copy(".func uplim_tanh(x, pos, z) { min(x, pos - z) + "
                   "tanh(max(0, x - pos + z) / z)*z }");
    nextcard = insert_new_line(nextcard, new_str, 3, 0, "internal");
    new_str = copy(".func dnlim_tanh(x, neg, z) { max(x, neg + z) - "
                   "tanh(max(0, neg + z - x) / z)*z }");
    nextcard = insert_new_line(nextcard, new_str, 4, 0, "internal");
    nextcard->nextcard = oldcard;

    /* remove .backanno, replace 'noiseless' by 'moisy=0' */
    for (card = nextcard; card; card = card->nextcard) {
        char* cut_line = card->line;
        if (ciprefix(".backanno", cut_line)) {
            *cut_line = '*';
        }
        else if (*cut_line == 'r') {
            char* noi = strstr(cut_line, "noiseless");
            /* only if 'noiseless' is an unconnected token */
            if (noi && isspace_c(noi[-1]) && (isspace_c(noi[9]) || !isprint_c(noi[9]))) {
                memcpy(noi, "noisy=0  ", 9);
            }
        }
    }

    /* replace
   * D1 A K SDMOD
   * .MODEL SDMOD D (Roff=1000 Ron=0.7  Rrev=0.2  Vfwd=1  Vrev=10
   *          Revepsilon=0.2 Epsilon=0.2 Ilimit=7 Revilimit=7)
   * by
   * a1 a k SDMOD
   * .model SDMOD sidiode(Roff=1000 Ron=0.7  Rrev=0.2  Vfwd=1  Vrev=10
   *            Revepsilon=0.2 Epsilon=0.2 Ilimit=7 Revilimit=7)
   * Do this if one of the parameters, which are uncommon to standard diode
   * model, has been found.

   * simple hierachy, as nested subcircuits are not allowed in PSPICE */

    /* first scan: find the d models, transform them and put them into a list
     */
    for (card = nextcard; card; card = card->nextcard) {
        char *str;
        static struct card *subcktline = NULL;
        static int nesting = 0;
        char *cut_line = card->line;
        if (*cut_line == '*' || *cut_line == '\0')
            continue;
        else if (ciprefix(".subckt", cut_line)) {
            subcktline = card;
            nesting++;
        }
        else if (ciprefix(".ends", cut_line))
            nesting--;

        else if (ciprefix(".model", card->line) &&
                search_plain_identifier(card->line, "d")) {
            if (search_plain_identifier(card->line, "roff") ||
                    search_plain_identifier(card->line, "ron") ||
                    search_plain_identifier(card->line, "rrev") ||
                    search_plain_identifier(card->line, "vfwd") ||
                    search_plain_identifier(card->line, "vrev") ||
                    search_plain_identifier(card->line, "revepsilon") ||
                    search_plain_identifier(card->line, "epsilon") ||
                    search_plain_identifier(card->line, "revilimit") ||
                    search_plain_identifier(card->line, "ilimit")) {
                char *modname;

                /* remove parameter 'noiseless' (the model is noiseless anyway) */
                char *nonoise = search_plain_identifier(card->line, "noiseless");
                if (nonoise) {
                    size_t iii;
                    for (iii = 0; iii < 9; iii++)
                        nonoise[iii] = ' ';
                }
                card->line = str = inp_remove_ws(card->line);
                str = nexttok(str); /* throw away '.model' */
                INPgetNetTok(&str, &modname, 0); /* model name */
                if (!ciprefix("d", str)) {
                    tfree(modname);
                    continue;
                }
                /* skip d */
                str++;
                /* we take all the existing parameters */
                char *newstr = copy(str);
                tfree(card->line);
                card->line = tprintf(".model a%s sidiode%s", modname, newstr);
                if (nesting > 0)
                    modelsfound = insert_new_model(
                            modelsfound, modname, subcktline->line);
                else
                    modelsfound =
                            insert_new_model(modelsfound, modname, "top");
                tfree(modname);
                tfree(newstr);
            }
        }
        else
            continue;
    }

    /* no need to continue if no d is found */
    if (!modelsfound)
        return newcard;

    /* second scan: find the diode instances d calling a simple diode model
     * and transform them */
    for (card = nextcard; card; card = card->nextcard) {
        static struct card *subcktline = NULL;
        static int nesting = 0;
        char *cut_line = card->line;
        if (*cut_line == '*')
            continue;
        if (*cut_line == '\0')
            continue;
        // exclude any command inside .control ... .endc
        if (ciprefix(".control", cut_line)) {
            skip_control++;
            continue;
        }
        else if (ciprefix(".endc", cut_line)) {
            skip_control--;
            continue;
        }
        else if (skip_control > 0) {
            continue;
        }
        if (ciprefix(".subckt", cut_line)) {
            subcktline = card;
            nesting++;
        }
        if (ciprefix(".ends", cut_line))
            nesting--;

        if (ciprefix("d", cut_line)) {
            /* check for the model name */
            int i;
            char *stoks[4];
            for (i = 0; i < 4; i++) {
                stoks[i] = gettok_node(&cut_line);
                if (stoks[i] == NULL) {
                    fprintf(stderr, "Error in line %d: buggy diode instance line\n    %s\n", card->linenum_orig, card->linesource);
                    fprintf(stderr, "At least 'Dxx n1 n2 d' is required.\n");
                    controlled_exit(EXIT_BAD);
                }
            }
            /* rewrite d line and replace it if a model is found */
            if ((nesting > 0) &&
                    find_a_model(modelsfound, stoks[3], subcktline->line)) {
                tfree(card->line);
                card->line = tprintf("a%s %s %s a%s",
                    stoks[0], stoks[1], stoks[2], stoks[3]);
            }
            /* if model is not within same subcircuit, search at top level */
            else if (find_a_model(modelsfound, stoks[3], "top")) {
                tfree(card->line);
                card->line = tprintf("a%s %s %s a%s",
                        stoks[0], stoks[1], stoks[2], stoks[3]);
            }
            for (i = 0; i < 4; i++)
                tfree(stoks[i]);
        }
    }
    del_models(modelsfound);

    return newcard;
}

/* do not modify oldcard address, insert everything after first line only */
void ltspice_compat_a(struct card *oldcard)
{
    oldcard->nextcard = ltspice_compat(oldcard->nextcard);
}
