/* Copyright 1990
   Regents of the University of California.
   All rights reserved.

   Author: 1985 Wayne A. Christopher

   The main routine for ngspice
*/

#include "ngspice/ngspice.h"
#include "ngspice/const.h"
#include "ngspice/dstring.h"

#include <setjmp.h>
#include <signal.h>

/* Added GNU Readline Support 11/3/97 -- Andrew Veliath <veliaa@rpi.edu> */
/* from spice3f4 patch to ng-spice. jmr */
#ifdef HAVE_GNUREADLINE
# include <readline/readline.h>
# include <readline/history.h>
#endif

/* editline development has added the following typdef to readline.h in 06/2018.
   It is not vailable with older libedit versions (pre-1.42.2) , thus we have to set it ourselves */
#ifdef HAVE_BSDEDITLINE
#include <editline/readline.h>
#ifndef rl_hook_func_t
    typedef int rl_hook_func_t(void);
#endif
extern rl_hook_func_t *rl_event_hook;    /* missing from editline/readline.h */
extern int rl_catch_signals;        /* missing from editline/readline.h */
#endif

#include "ngspice/iferrmsg.h"
#include "ngspice/ftedefs.h"
#include "ngspice/devdefs.h"
#include "spicelib/devices/dev.h"
#include "spicelib/analysis/analysis.h"
#include "misc/ivars.h"
#include "misc/misc_time.h"
#include "misc/util.h"

#if defined(HAS_WINGUI) || defined(_MSC_VER) || defined(__MINGW32__)
# include "misc/mktemp.h"
#endif

#if defined(HAVE_GETOPT_LONG) && defined(HAVE_GETOPT_H)
# include <getopt.h>
#else
# include "misc/getopt_bsd.h"
#endif

#include "frontend/spiceif.h"
#include "frontend/resource.h"
#include "frontend/variable.h"
#include "frontend/display.h"  /* added by SDB to pick up Input() fcn */
#include "frontend/signal_handler.h"
#include "frontend/misccoms.h"
#include "ngspice/compatmode.h"
#include "ngspice/randnumb.h"

/* saj xspice headers */
#ifdef XSPICE
# include "ngspice/ipctiein.h"
# include "ngspice/mif.h"
# include "ngspice/enh.h"
# include "ngspice/mifproto.h"
# include "ngspice/evtproto.h"
#endif

#ifdef CIDER
# include "ngspice/numenum.h"
# include "maths/misc/accuracy.h"
#endif

#if defined(HAVE_GNUREADLINE) || defined(HAVE_BSDEDITLINE)
char history_file[512] = {'\0'};
static char *application_name;
#endif

/* Undefine this next line for debug tracing */
/* #define TRACE */

/* Main options */
static bool ft_servermode = FALSE;
bool ft_batchmode = FALSE;
bool ft_pipemode = FALSE;
bool rflag = FALSE; /* has rawfile */

/* Frontend options */
bool ft_intrpt = FALSE;     /* Set by the (void) signal handlers. TRUE = we've been interrupted. */
bool ft_setflag = FALSE;    /* TRUE = Don't abort simulation after an interrupt. */
char *ft_rawfile = "rawspice.raw";

#ifdef XSPICE
bool wantevtdata = FALSE;
#endif

#ifdef HAS_WINGUI
extern void winmessage(char *new_msg); /* display a message box (defined in winmain.c)*/
extern void SetSource(char *Name);    /* display the source file name in the source window */
extern int  xmain(int argc, char **argv);
FILE *flogp = NULL;         /* log file ('-o logfile' command line option) */
#else
bool orflag = FALSE; /* global for -o option */
#endif

FILE *slogp = NULL;          /* soa log file ('--soa-log file' command line option) */

/* Frontend and circuit options */
IFsimulator *ft_sim = NULL;

char *errRtn;     /* name of the routine declaring error */
char *errMsg = NULL;     /* descriptive message about what went wrong */
char *cp_program; /* program name 'ngspice' */

char *Infile_Path = NULL; /* Path to netlist input file */


/* Globals definitions for Machine Accuracy Limits
 * (needed by CIDER)
 */
double BMin;                /* lower limit for B(x) */
double BMax;                /* upper limit for B(x) */
double ExpLim;              /* limit for exponential */
double Accuracy;            /* accuracy of the machine */
double MuLim, MutLim;

#ifdef CIDER
/* Global debug flags from CIDER, soon they will become
 * spice variables :)
 */
BOOLEAN ONEacDebug   = FALSE;
BOOLEAN ONEdcDebug   = TRUE;
BOOLEAN ONEtranDebug = TRUE;
BOOLEAN ONEjacDebug  = FALSE;

BOOLEAN TWOacDebug   = FALSE;
BOOLEAN TWOdcDebug   = TRUE;
BOOLEAN TWOtranDebug = TRUE;
BOOLEAN TWOjacDebug  = FALSE;

/* CIDER Global Variable Declarations */

int BandGapNarrowing;
int TempDepMobility, ConcDepMobility, FieldDepMobility, TransDepMobility;
int SurfaceMobility, MatchingMobility, MobDeriv;
int CCScattering;
int Srh, Auger, ConcDepLifetime, AvalancheGen;
int FreezeOut = FALSE;
int OneCarrier;

int MaxIterations = 100;
int AcAnalysisMethod = DIRECT;

double Temp, RelTemp, Vt;
double RefPsi;/* potential at Infinity */
double EpsNorm, VNorm, NNorm, LNorm, TNorm, JNorm, GNorm, ENorm;

/* end cider globals */
#endif /* CIDER */

struct variable *(*if_getparam)(CKTcircuit *ckt, char **name, char *param, int ind, int do_model);

/* static functions */
int SIMinit(IFfrontEnd *frontEnd, IFsimulator **simulator);

static ATTRIBUTE_NORETURN void sp_shutdown(int exitval);
static void app_rl_readlines(void);

#if defined(HAVE_GNUREADLINE) || defined(HAVE_BSDEDITLINE)
static char *prompt(void);
#ifndef X_DISPLAY_MISSING
# include "frontend/plotting/x11.h"
static int app_event_func(void);
#endif
#endif

static void show_help(void);
static bool read_initialisation_file(const char *dir, const char *name);

#ifdef SIMULATOR
static void append_to_stream(FILE *dest, FILE *source);
#endif

extern IFsimulator SIMinfo;

#ifdef SIMULATOR

const bool ft_nutmeg = FALSE;
extern struct comm spcp_coms[];
struct comm *cp_coms = spcp_coms;

extern int OUTpBeginPlot(CKTcircuit *, JOB *, IFuid, IFuid, int, int, IFuid *, int, runDesc **);
extern int OUTpData(runDesc *, IFvalue *, IFvalue *);
extern int OUTwBeginPlot(CKTcircuit *, JOB *, IFuid, IFuid, int, int, IFuid *, int, runDesc **);
extern int OUTwReference(runDesc *, IFvalue *, void **);
extern int OUTwData(runDesc *, int, IFvalue *, void *), OUTwEnd(runDesc *), OUTendPlot(runDesc *);
extern int OUTbeginDomain(runDesc *, IFuid, int, IFvalue *);
extern int OUTendDomain(runDesc *), OUTstopnow(void);
extern void OUTerror(int, char *, IFuid *);

#ifdef __GNUC__
extern void OUTerrorf(int, const char *fmt, ...)  __attribute__ ((format (__printf__, 2, 3)));
#else
extern void OUTerrorf(int, const char *fmt, ...);
#endif

extern int OUTattributes(runDesc *, IFuid, int, IFvalue *);

IFfrontEnd nutmeginfo = {
    IFnewUid,
    IFdelUid,
    OUTstopnow,
    seconds,
    OUTerror,
    OUTerrorf,
    OUTpBeginPlot,
    OUTpData,
    OUTwBeginPlot,
    OUTwReference,
    OUTwData,
    OUTwEnd,
    OUTendPlot,
    OUTbeginDomain,
    OUTendDomain,
    OUTattributes
};

#else /* SIMULATOR */

const bool ft_nutmeg = TRUE;
extern struct comm nutcp_coms[];
struct comm *cp_coms = nutcp_coms;
IFfrontEnd nutmeginfo;

/* -------------------------------------------------------------------------- */
int
if_run(CKTcircuit *t, char *w, wordlist *s, INPtables *b)
{
    NG_IGNORE(b);
    NG_IGNORE(s);
    NG_IGNORE(w);
    NG_IGNORE(t);

    return 0;
}

/* -------------------------------------------------------------------------- */
int
if_sens_run(CKTcircuit *t, wordlist *args, INPtables *tab)
{
    NG_IGNORE(tab);
    NG_IGNORE(args);
    NG_IGNORE(t);

    return 0;
}

/* -------------------------------------------------------------------------- */
void
if_dump(CKTcircuit *ckt, FILE *fp)
{
    NG_IGNORE(fp);
    NG_IGNORE(ckt);
}

/* -------------------------------------------------------------------------- */
CKTcircuit *
if_inpdeck(struct card *deck, INPtables **tab)
{
    NG_IGNORE(tab);
    NG_IGNORE(deck);

    return NULL;
}

/* -------------------------------------------------------------------------- */
int
if_option(CKTcircuit *ckt, char *name, enum cp_types type, void *value)
{
    NG_IGNORE(value);
    NG_IGNORE(type);
    NG_IGNORE(name);
    NG_IGNORE(ckt);

    return 0;
}

/* -------------------------------------------------------------------------- */
void
if_cktfree(CKTcircuit *ckt, INPtables *tab)
{
    NG_IGNORE(tab);
    NG_IGNORE(ckt);
}

/* -------------------------------------------------------------------------- */
void
if_setndnames(char *line)
{
    NG_IGNORE(line);
}

/* -------------------------------------------------------------------------- */
char *
if_errstring(int code)
{
    NG_IGNORE(code);

    return copy("spice error");
}

/* -------------------------------------------------------------------------- */
void
if_setparam_model(CKTcircuit *ckt, char **name, char *val)
{
    NG_IGNORE(val);
    NG_IGNORE(name);
    NG_IGNORE(ckt);
}

void
if_setparam(CKTcircuit *ckt, char **name, char *param, struct dvec *val, int do_model)
{
    NG_IGNORE(do_model);
    NG_IGNORE(val);
    NG_IGNORE(param);
    NG_IGNORE(name);
    NG_IGNORE(ckt);
}

/* -------------------------------------------------------------------------- */
bool
if_tranparams(struct circ *ci, double *start, double *stop, double *step)
{
    NG_IGNORE(step);
    NG_IGNORE(stop);
    NG_IGNORE(start);
    NG_IGNORE(ci);

    return FALSE;
}

/* -------------------------------------------------------------------------- */
struct variable *
if_getstat(CKTcircuit *ckt, char *name)
{
    NG_IGNORE(name);
    NG_IGNORE(ckt);

    return NULL;
}

void
com_snload(wordlist *wl)
{
    NG_IGNORE(wl);
}

void
com_snsave(wordlist *wl)
{
    NG_IGNORE(wl);
}

void
SMPprint(SMPmatrix *n1, char *n2)
{
    NG_IGNORE(n1);
    NG_IGNORE(n2);
}

void
SMPprintRHS(SMPmatrix *n1, char *n2, double *n3, double *n4)
{
    NG_IGNORE(n1);
    NG_IGNORE(n2);
    NG_IGNORE(n3);
    NG_IGNORE(n4);
}

void
spice_destroy_devices(void)
{
}

#endif /* SIMULATOR */

#ifndef SIMULATOR

#ifdef XSPICE
/* saj to get nutmeg to compile, not nice but necessary */
Ipc_Tiein_t  g_ipc;

Ipc_Status_t
ipc_send_errchk(void)
{
    Ipc_Status_t x = IPC_STATUS_OK;
    return x;
}

Ipc_Status_t
ipc_get_line(char *str, int *len, Ipc_Wait_t wait)
{
    Ipc_Status_t x = IPC_STATUS_OK;

    NG_IGNORE(str);
    NG_IGNORE(len);
    NG_IGNORE(wait);

    return x;
}

struct card *
ENHtranslate_poly(struct card *deck)
{
    NG_IGNORE(deck);
    return NULL;
}

int
EVTswitch_plot(CKTcircuit* ckt, const char* plottypename)
{
    NG_IGNORE(ckt);
    NG_IGNORE(plottypename);
    return 1;
};

int
load_opus(const char *name)
{
    NG_IGNORE(name);
    return 1;
}

char *
MIFgettok(char **s)
{
    NG_IGNORE(s);
    return NULL;
}

void
EVTprint(wordlist *wl)
{
    NG_IGNORE(wl);
}

void
EVTprintvcd(wordlist *wl)
{
    NG_IGNORE(wl);
}

void
EVTdisplay(wordlist *wl)
{
    NG_IGNORE(wl);
}

struct dvec *
EVTfindvec(char *node) {
    NG_IGNORE(node);
    return NULL;
}

#endif /* XSPICE */

#endif /* SIMULATOR */

char *hlp_filelist[] = { "ngspice", NULL };


/* Allocate space for global constants declared in const.h
 * and set their values */
double CONSTroot2 = CONSTsqrt2;
double CONSTvt0 = CONSTboltz * REFTEMP / CHARGE;
double CONSTKoverQ = CONSTboltz / CHARGE;
double CONSTe = CONSTnap;

IFfrontEnd *SPfrontEnd = NULL;
int DEVmaxnum = 0;


/* -------------------------------------------------------------------------- */
int
SIMinit(IFfrontEnd *frontEnd, IFsimulator **simulator)
{
#ifdef SIMULATOR
    spice_init_devices();
    SIMinfo.numDevices = DEVmaxnum = num_devices();
    SIMinfo.devices = devices_ptr();
    SIMinfo.numAnalyses = spice_num_analysis();

    /* va: we recast, because we use only the public part */
    SIMinfo.analyses = (IFanalysis **)spice_analysis_ptr();


#ifdef CIDER
/* Evaluates limits of machine accuracy for CIDER */
    evalAccLimits();
#endif /* CIDER */

#endif /* SIMULATOR */

    SPfrontEnd = frontEnd;
    *simulator = &SIMinfo;

    return OK;
} /* end of function SIMinit */


/* -------------------------------------------------------------------------- */
/* Shutdown gracefully. */
static ATTRIBUTE_NORETURN void
sp_shutdown(int exitval)
{
    destroy_ivars();
#ifdef HAS_WINGUI
    if (exitval == EXIT_BAD)
        winmessage("Fatal error in SPICE");
    else if (exitval == EXIT_INFO)
        winmessage("Information during setup, see main window!");
#endif
    if  (exitval == EXIT_INFO)
        exitval = EXIT_NORMAL;
    exit(exitval);
}

/* -------------------------------------------------------------------------- */

#if defined(HAVE_GNUREADLINE) || defined(HAVE_BSDEDITLINE)
/* Adapted ../lib/cp/lexical.c:prompt() for GNU Readline -- Andrew Veliath <veliaa@rpi.edu> */
static char *
prompt(void)
{
    static char pbuf[128];
    char *p = pbuf, *s;

    if (cp_interactive == FALSE)
        return NULL;    /* NULL means no prompt */

    s = get_alt_prompt();

    if (!s)
        s = cp_promptstring;

    if (!s)
        s = "->";

    while (*s) {
        char c = (*s++);

        /* FALLTHROUGH added to suppress GCC warning due to
         * -Wimplicit-fallthrough flag */
        switch (c) {
        case '!':
            p += sprintf(p, "%d", where_history() + 1);
            break;
        case '\\': /* skip an escape char */
            if (*s)
                c = (char) (*s++);
            /* FALLTHROUGH */
        default:
            *p++ = c;
            break;
        }
    }

    *p = '\0';

    return pbuf;
}
#endif /* HAVE_GNUREADLINE || HAVE_BSDEDITLINE */

#ifndef X_DISPLAY_MISSING
#if defined(HAVE_GNUREADLINE) || defined(HAVE_BSDEDITLINE)
/* -------------------------------------------------------------------------- */
/* Process device events in Readline's hook since there is no where
   else to do it now - AV */
static int
app_event_func(void)
/* called by GNU readline periodically to know what to do about keypresses */
{
    static REQUEST reqst = { char_option, 0 };
    reqst.fp = rl_instream;
    X11_Input(&reqst, NULL);
    return 0;
}
#endif

#endif

/* -------------------------------------------------------------------------- */
/* This is the command processing loop for spice and nutmeg.
   The function is called even when GNU readline is unavailable, in which
   case it falls back to repeatable calling cp_evloop()
   SJB 26th April 2005 */
static void
app_rl_readlines(void)
{
#if defined(HAVE_GNUREADLINE) || defined(HAVE_BSDEDITLINE)
    /* GNU Readline Support -- Andrew Veliath <veliaa@rpi.edu> */
    char *line, *expanded_line;

    /* ---  set up readline params --- */
    strcpy(history_file, getenv("HOME"));
    strcat(history_file, "/.");
    strcat(history_file, application_name);
    strcat(history_file, "_history");

    using_history();
    read_history(history_file);

    rl_readline_name = application_name;
    rl_instream = cp_in;
    rl_outstream = cp_out;
#ifndef X_DISPLAY_MISSING
    if (dispdev->Input == X11_Input)
        rl_event_hook = app_event_func;
#endif
    rl_catch_signals = 0;   /* disable signal handling  */

    /* sjb - what to do for editline?
       This variable is not supported by editline. */
#if defined(HAVE_GNUREADLINE)
    rl_catch_sigwinch = 1;  /* allow readline to respond to resized windows  */
#endif

    /* note that we want some mechanism to detect ctrl-D and expand it to exit */
    for (;;) {
        history_set_pos(history_length);

        if (SETJMP(jbuf, 1)) { /* Set location to jump to after handling SIGINT (ctrl-C)  */
            ft_sigintr_cleanup();
        }

        line = readline(prompt());

        if (!line) {
            cp_evloop("quit");
            continue;
        }

        if (*line) {
            int s = history_expand(line, &expanded_line);

            if (s == 2) {
                fprintf(stderr, "-> %s\n", expanded_line);
            }
            else if (s == -1) {
                fprintf(stderr, "readline: %s\n", expanded_line);
            }
            else {
                cp_evloop(expanded_line);
                add_history(expanded_line);
            }
            tfree(expanded_line);
        }

        tfree(line);
    }
    /* History gets written in ../fte/misccoms.c com_quit */

#else
    while (cp_evloop(NULL) == 1) {
        ;
    }
#endif /* defined(HAVE_GNUREADLINE) || defined(HAVE_BSDEDITLINE) */
} /* end of function app_rl_readlines */


/* -------------------------------------------------------------------------- */
static void
show_help(void)
{
    printf("Usage: %s [OPTION]... [FILE]...\n"
           "Simulate the electical circuits in FILE.\n"
           "\n"
           "  -a  --autorun             run the loaded netlist\n"
           "  -b, --batch               process FILE in batch mode\n"
           "  -c, --circuitfile=FILE    set the circuitfile\n"
           "  -D, --define=variable[=value] define variable to true/[value]\n"
           "  -i, --interactive         run in interactive mode\n"
           "  -n, --no-spiceinit        don't load the local or user's config file\n"
           "  -o, --output=FILE         set the outputfile\n"
           "  -p, --pipe                run in I/O pipe mode\n"
           "  -q, --completion          activate command completion\n"
           "  -r, --rawfile=FILE        set the rawfile output\n"
           "      --soa-log=FILE        set the outputfile for SOA warnings\n"
           "  -s, --server              run spice as a server process\n"
           "  -t, --term=TERM           set the terminal type\n"
           "  -h, --help                display this help and exit\n"
           "  -v, --version             output version information and exit\n"
           "\n"
           "Report bugs to %s.\n", cp_program, Bug_Addr);
}

#ifdef SIMULATOR
/* -------------------------------------------------------------------------- */
static void
append_to_stream(FILE *dest, FILE *source)
{
    char buf[BSIZE_SP];
    size_t n;

    while ((n = fread(buf, 1, BSIZE_SP, source)) > 0)
        fwrite(buf, n, 1, dest);
}
#endif /* SIMULATOR */

/* -------------------------------------------------------------------------- */
/* Read an initialisation file.
   dir    is the directory (use NULL or "" for current directory)
   name   is the initialisation file's name
   Return true on success
   SJB 25th April 2005 */
static bool read_initialisation_file(const char *dir, const char *name)
{
    const char *path;
    bool result = FALSE;

    /* check name */
    if (!name || *name == '\0') {
        return FALSE;   /* Fail; name needed */
    }

    /* contruct the full path */
    if (!dir || *dir == '\0') {
        path = name;
    }
    else {
        path = tprintf("%s" DIR_PATHSEP "%s", dir, name);
        if (!path) {
            return FALSE;    /* memory allocation error */
        }
    }

    /* now access the file */
#ifdef HAVE_UNISTD_H
    if (access(path, R_OK) == 0) {
        result = TRUE;
    }
#else
    {
        FILE *fp = fopen(path, "r");
        if (fp) {
            fclose(fp);
            result = TRUE;
        }
    }
#endif

    if (result) {
        inp_source(path);
#ifdef TRACE
        printf("Init file: '%s'\n", path);
#endif
    }

    if (path != name) { /* Allocated by tprintf() */
        txfree(path);
    }

    return result;
} /* end of function read_initialisation_file */

/* -------------------------------------------------------------------------- */
static void print_news(void)
{
    if (News_File && *News_File) {
        const char * const fname = cp_tildexpand(News_File); /*DG  Memory leak */
        FILE * const fp = fopen(fname, "r");
        txfree(fname);
        if (fp) {
            char buf[BSIZE_SP];
            while (fgets(buf, BSIZE_SP, fp)) {
                fputs(buf, stdout);
            }
            fclose(fp);
        }
    }
} /* end of function print_news */

#ifdef HAS_WINGUI
#define main xmain
#endif

int main(int argc, char **argv)
{
    char log_file[BSIZE_SP];
    char soa_log_file[BSIZE_SP];

    /* volatile added to resolve GCC -Wclobbered */
    volatile bool readinit = TRUE; /* read initialization file */
    volatile bool istty = TRUE;

    bool iflag = FALSE; /* flag for interactive mode */
    bool qflag = FALSE; /* flag for command completion */

    FILE * volatile circuit_file;
    bool oflag = FALSE;
    bool srflag = FALSE;

#ifdef TRACE
    /* this is used to detect memory leaks during debugging */
    /* added by SDB during debug . . . . */
    /* mtrace(); */
#endif

#ifdef TRACE
    /* this is also used for memory leak plugging . . . */
    /* added by SDB during debug . . . . */
    /* mwDoFlush(1); */
#endif
#ifdef TRACE1
    int jj;
    for (jj = 0; jj < argc; jj++)
    {
        fprintf(stdout, "%s\n", argv[jj]);
    }
#endif

    /* MFB tends to jump to 0 on errors.  This tends to catch it. */
    {
        static int started = 0;
        if (started++) {
            fprintf(cp_err, "main: Internal Error: jump to zero\n");
            sp_shutdown(EXIT_BAD);
        }
    }

#if defined(HAVE_GNUREADLINE) || defined(HAVE_BSDEDITLINE)
    application_name = strrchr(argv[0], '/');
    if (application_name) {
        ++application_name;
    }
    else {
        application_name = argv[0];
    }
#endif

    ivars(argv[0]); /* Create internal variables */

    /* Set default data sources */
    cp_in  = stdin;
    cp_out = stdout;
    cp_err = stderr;
    circuit_file = stdin;

#if defined(HAVE_ISATTY) && !defined(HAS_WINGUI)
    istty = (bool) isatty(fileno(stdin));
#endif

    init_time();

    {
        int rv = SIMinit(&nutmeginfo, &ft_sim);
        if (rv != OK) {
            ft_sperror(rv, "SIMinit");
            sp_shutdown(EXIT_BAD);
        }
    }

    cp_program = ft_sim->simulator;

    /* initialze random number generator with seed = 1 */
    int ii = 1;
    cp_vset("rndseed", CP_NUM, &ii);
    com_sseed(NULL);

    /* --- Process command line options --- */
    for (;;) {
        enum { soa_log = 1001, };

        static struct option long_options[] = {
            {"define",       required_argument, NULL, 'D'},
            {"help",         no_argument,       NULL, 'h'},
            {"version",      no_argument,       NULL, 'v'},
            {"batch",        no_argument,       NULL, 'b'},
            {"autorun",      no_argument,       NULL, 'a'},
            {"circuitfile",  required_argument, NULL, 'c'},
            {"interactive",  no_argument,       NULL, 'i'},
            {"no-spiceinit", no_argument,       NULL, 'n'},
            {"output",       required_argument, NULL, 'o'},
            {"pipe",         no_argument,       NULL, 'p'},
            {"completion",   no_argument,       NULL, 'q'},
            {"rawfile",      required_argument, NULL, 'r'},
            {"server",       no_argument,       NULL, 's'},
            {"terminal",     required_argument, NULL, 't'},
            {"soa-log",      required_argument, NULL, soa_log},
            {NULL,           0,                 NULL, 0}
        };

        int option_index = 0;

        int c = getopt_long(argc, argv, "D:hvbac:ino:pqr:st:",
                            long_options, &option_index);

        if (c == -1) {
            break;
        }

        switch (c) {
        case 'D':       /* Definition of variable */
            if (optarg) {
                const char *eq = strchr(optarg, '=');
                if (eq == (char *) NULL) { /* no assignment */
                    bool true_val = TRUE;
                    cp_vset(optarg, CP_BOOL, &true_val);
                }
                else {
                    DS_CREATE(ds, 100);
                    if (ds_cat_mem(&ds, optarg, (size_t) (eq - optarg)) == 0) {
                        cp_vset(ds_get_buf(&ds), CP_STRING, eq + 1);
                    }
                    ds_free(&ds);
                }
            }
            break;

        case 'h':       /* Help */
            show_help();
            sp_shutdown(EXIT_INFO);
            break;

        case 'v':       /* Version info */
            com_version(NULL);
            sp_shutdown(EXIT_INFO);
            break;

        case 'b':       /* Batch mode */
        {
            bool x_false = FALSE;
            bool x_true = TRUE;
            cp_vset("addcontrol", CP_BOOL, &x_false);
            cp_vset("batchmode", CP_BOOL, &x_true);
            ft_batchmode = TRUE;
        }
        break;

        case 'a':       /* Add control section for autorun */
            if (!ft_batchmode) {
                bool x_true = TRUE;
                cp_vset("addcontrol", CP_BOOL, &x_true);
            }
            break;

        case 'c':       /* Circuit file */
            if (optarg) {
                if ((circuit_file = fopen(optarg, "r")) == NULL) {
                    perror(optarg);
                    sp_shutdown(EXIT_BAD);
                }
                istty = FALSE;
            }
            break;

        case 'i':       /* Interactive mode */
            iflag = TRUE;
            break;

        case 'n':       /* Don't read initialisation file */
            readinit = FALSE;
            break;

        case 'o':       /* Output file */
            if (optarg) {
                /* switch to line buffering for stdout */
                setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
                sprintf(log_file, "%s", optarg);
                oflag = TRUE;
            }
            break;

        case 'p':       /* Run in pipe mode */
            iflag = TRUE;
            istty = TRUE;
            ft_pipemode = TRUE;
            /* switch to line buffering for stdout */
            setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
            break;

        case 'q':       /* Command completion */
            qflag = TRUE;
            break;

        case 'r':       /* The raw file */
            if (optarg) {
                cp_vset("rawfile", CP_STRING, optarg);
            }
            rflag = TRUE;
            break;

        case 's':       /* Server mode */
            ft_servermode = TRUE;
            break;

        case 't':
            if (optarg) {
                cp_vset("term", CP_STRING, optarg);
            }
            break;

        case soa_log:
            if (optarg) {
                sprintf(soa_log_file, "%s", optarg);
                srflag = TRUE;
            }
            break;

        case '?':
            break;

        default:
            printf ("?? getopt returned character code 0%o ??\n", c);
        }
    }  /* --- End of command line option processing (While(1)-loop) --- */


    if (oflag) {   /* -o option has been set */

        com_version(NULL);

        if (ft_servermode) {
            fprintf(stdout, "\nServer mode\n\n");
        }
        else if (ft_batchmode) {
            fprintf(stdout, "\nBatch mode\n\n");
        }
        else {
            fprintf(stdout, "\nInteractive mode, better used without -o option\n\n");
        }

        if (rflag) {
            fprintf(stdout, "Simulation output goes to rawfile: %s\n", ft_rawfile);
        }

        fprintf(stdout, "Comments and warnings go to log-file: %s\n\n", log_file);

        /* Open the log file */
#ifdef HAS_WINGUI
        /* flogp used by winmain's putc which writes to file 'log_file' */
        flogp = fopen(log_file, "w");
        if (!flogp) {
            perror(log_file);
            sp_shutdown(EXIT_BAD);
        }
#else
        orflag = TRUE;
        /* Connect stdout and stderr to file log_file and log stdout */
        if (!freopen(log_file, "w", stdout)) {
            perror(log_file);
            sp_shutdown(EXIT_BAD);
        }
        dup2(fileno(stdout), fileno(stderr));
        setvbuf(stdout, NULL, _IOLBF, BUFSIZ); /* enable line buffering */
#endif
    } /* orflag */

    if (srflag) {   /* --soa-log option has been set */

        fprintf(stdout, "\nSOA warnings go to log-file: %s\n", soa_log_file);

        /* Open the soa log file */
        slogp = fopen(soa_log_file, "w");
        if (!slogp) {
            perror(soa_log_file);
            sp_shutdown(EXIT_BAD);
        }
    } /* srflag */

#ifdef SIMULATOR
    if_getparam = spif_getparam_special;
#else
    if_getparam = nutif_getparam;
#endif

    if ((!iflag && !istty) || ft_servermode) { /* (batch and file) or
                                                * server operation */
        ft_batchmode = TRUE;
    }

    if ((iflag && !istty) || qflag)  { /* (interactive and file) or
                                        * command completion */
        cp_nocc = TRUE;              /* set command completion */
    }
    else {
        cp_nocc = FALSE;
    }

    if ((iflag || istty) && !ft_batchmode) {
        /* Enable interactive prompting. */

        bool x_true = TRUE;
        cp_vset("interactive", CP_BOOL, &x_true);
    }

    if (ft_servermode) {             /* in server no init file */
        readinit = FALSE;
    }

    if (!istty || ft_batchmode) {    /* file or batch - no more output */
        out_moremode = FALSE;
    }

    /* Get information on memory status upon startup.
       Would like to do this later, but cpinit evals commands.
       fcn is in resource.c */
    init_rlimits();

    /* Have to initialize cp now.
       fcn is in cpitf.c */
    ft_cpinit();


    /* Set up signal handling */
    if (!ft_batchmode) {
        /*  Set up interrupt handler  */
        signal(SIGINT, (SIGNAL_FUNCTION) ft_sigintr);

        /* floating point exception  */
        signal(SIGFPE, (SIGNAL_FUNCTION) sigfloat);

#ifdef SIGTSTP
        signal(SIGTSTP, (SIGNAL_FUNCTION) sigstop);
#endif
    }

    /* Set up signal handling for fatal errors. */
    signal(SIGILL, (SIGNAL_FUNCTION) sigill);

#ifdef SIGBUS
    signal(SIGBUS, (SIGNAL_FUNCTION) sigbus);
#endif
#if defined(SIGSEGV) && !defined(NGDEBUG) && defined(HAS_WINGUI)
/* Allow a comment and graceful shutdown after seg fault */
    signal(SIGSEGV, (SIGNAL_FUNCTION) sigsegv);
#endif
#ifdef SIGSYS
    signal(SIGSYS, (SIGNAL_FUNCTION) sig_sys);
#endif

#ifdef TRACE1
    fprintf(stdout, "We are ready to read initialization files.\n");
#endif

    /* To catch interrupts during .spiceinit... */
    if (SETJMP(jbuf, 1)) {
        ft_sigintr_cleanup();
        fprintf(cp_err, "Warning: error executing .spiceinit.\n");
    }
    else {
        if (readinit) {
            /* load user's initialisation file
               try accessing the initialisation file in the current directory
               if that fails try the alternate name */
            do {
                if (read_initialisation_file("", INITSTR) != FALSE) {
                    break;
                }
                if (read_initialisation_file("", ALT_INITSTR) != FALSE) {
                    break;
                }

                {
                    const char * const home = getenv("HOME");
                    if (home) {
                        if (read_initialisation_file(home, INITSTR) != FALSE) {
                            break;
                        }
                        if (read_initialisation_file(home, ALT_INITSTR) != FALSE) {
                            break;
                        }
                    }
                }

                {
                    const char * const usr = getenv("USERPROFILE");
                    if (usr) {
                        if (read_initialisation_file(usr, INITSTR) != FALSE) {
                            break;
                        }
                        if (read_initialisation_file(usr, ALT_INITSTR) != FALSE) {
                            break;
                        }
                    }
                }
            } while (0);
        } /* end of case that init file is read */

        if (!ft_batchmode) {
            com_version(NULL);
            DevInit();
            print_news();
        }
    } /* end of normal execution for setjmp() */


#ifdef SIMULATOR

    /* Pass 2 -- get the filenames. If we are spice, then this means
     * build a circuit for this file. If this is in server mode, don't
     * process any of these args.  */

    if (SETJMP(jbuf, 1)) {
        ft_sigintr_cleanup();
        fprintf(cp_err, "Warning: error executing during ngspice startup.\n");
    }
    else {
        bool gotone = FALSE;

        cp_interactive = FALSE;

#ifdef FastRand
// initialization and seed for FastNorm Gaussian random generator
        {
            unsigned int rseed = 66;
            initnorm(0, 0);
            if (!cp_getvar("rndseed", CP_NUM, &rseed, 0)) {
                time_t acttime = time(NULL);
                rseed = (unsigned int) acttime;
            }
            initnorm(rseed, 2);
            fprintf(cp_out, "SoS %f, seed value: %ld\n", renormalize(), rseed);
        }
#elif defined(WaGauss)
        initw();
#endif

        if (!ft_servermode) {

            int err = 0;

            /* Concatenate all non-option arguments into a temporary file
               and load that file into the spice core.

               The original routine took a special path if there was only
               one non-option argument.  In that case, it didn't create
               the temporary file but used the original file instead.  The
               current algorithm is uniform at the expense of a little
               startup time.  */

            FILE *tempfile = tmpfile();
            char *dname = NULL;   /* input file */

#if defined(HAS_WINGUI) || defined(_MSC_VER) || defined(__MINGW32__)
            char *tpf = NULL;     /* temporary file */

            /* tmpfile() returns NULL, if in MS Windows as non admin user
               in directory C:\something (no write permission to root C:).
               Then we add a tempfile in the user's home directory.
               File will be removed automatically due to TD option in fopen */
            if (tempfile == NULL) {
                tpf = smktemp("sp");
                tempfile = fopen(tpf, "w+bTD");
                if (tempfile == NULL) {
                    fprintf(stderr, "Could not open a temporary file "
                            "to save and use optional arguments.\n");
                    sp_shutdown(EXIT_BAD);
                }
            }
#endif

            if (!tempfile) {
                perror("tmpfile()");
                sp_shutdown(EXIT_BAD);
            }

            if (optind == argc && !istty) {
                append_to_stream(tempfile, stdin);
            }

            while (optind < argc) {
                char *arg = argv[optind++];
                FILE *tp;
                /* Copy the the path of the first filename only */
                if (!Infile_Path) {
                    Infile_Path = ngdirname(arg);
                }

             /* unquote the input string, needed if it results from double clicking the filename */
#if defined(HAS_WINGUI)
                arg = cp_unquote(arg);
#endif
                /* Copy all the arguments into the temporary file */
                tp = fopen(arg, "r");
                if (!tp) {
                    char *lbuffer = getenv("NGSPICE_INPUT_DIR");
                    if (lbuffer && *lbuffer) {
                        char *p = tprintf("%s" DIR_PATHSEP "%s",
                                lbuffer, arg);
                        tp = fopen(p, "r");
                        tfree(p);
                    }
                    if (!tp) {
                        perror(arg);
                        err = 1;
                        break;
                    }
                }

                /* Copy the input file name which otherwise will be lost due to the
                   temporary file */
                dname = copy(arg);
#if defined(HAS_WINGUI)
                /* write source file name into source window */
                SetSource(dname);
                /* free arg that has been unquoted above */
                tfree(arg);
#endif

                append_to_stream(tempfile, tp);
                fclose(tp);
            }

            fseek(tempfile, 0L, SEEK_SET);

            if (tempfile && (!err || !ft_batchmode)) {
                /* Copy the input file name for becoming another file search path */
                if (inp_spsource(tempfile, FALSE, dname, FALSE) != 0) {
                    fprintf(stderr, "    Simulation interrupted due to error!\n\n");
                }
                tfree(dname);
                gotone = TRUE;
            }

            if (ft_batchmode && err) {
                sp_shutdown(EXIT_BAD);
            }

        }   /* ---  if (!ft_servermode) --- */

        if (!gotone && ft_batchmode) {
            if (inp_spsource(circuit_file, FALSE, NULL, FALSE) != 0) {
                    fprintf(stderr, "    Simulation interrupted due to error!\n\n");
            }
        }

    }

    if (ft_batchmode) {

        /* If we get back here in batch mode then something is wrong,
         * so exit.  */

        if (SETJMP(jbuf, 1)) {
            ft_sigintr_cleanup();
            sp_shutdown(EXIT_BAD);
        }


        if (ft_servermode) {
            if (ft_curckt == NULL) {
                fprintf(cp_err, "Error: no circuit loaded!\n");
                sp_shutdown(EXIT_BAD);
            }
            if (ft_dorun(""))
                sp_shutdown(EXIT_BAD);
            sp_shutdown(EXIT_NORMAL);
        }


        cp_interactive = FALSE;

        if (rflag) {
            /* If -r is specified, then dot cards (.width, .plot, .print, .op, .meas, .tf)
               are ignored, except .save, which has been handled by ft_dotsaves()
               from within inp_spsource (), data are put into linked list dbs.
            */
            int error2 = ft_dorun(ft_rawfile);
            /* Execute the .whatever lines found in the deck, after we are done running. */
            if (ft_cktcoms(TRUE) || error2) {
                sp_shutdown(EXIT_BAD);
            }
        }
        else if (ft_savedotargs()) {
            /* all dot card data to be put into dbs */
            int error2 = ft_dorun(NULL);
            /* Execute the .whatever lines found in the deck, after we are done running. */
            if (ft_cktcoms(FALSE) || error2)
                sp_shutdown(EXIT_BAD);
        }
        else {
            fprintf(stderr,
                    "Note: No \".plot\", \".print\", or \".fourier\" lines; "
                    "no simulations run\n");
            sp_shutdown(EXIT_BAD);
        }

        sp_shutdown(EXIT_NORMAL);
    }  /* ---  if (ft_batchmode) ---  */


#else  /* ~ SIMULATOR */

    if (SETJMP(jbuf, 1)) {

        ft_sigintr_cleanup();
        fprintf(cp_err, "Warning: error executing during ft_loadfile().\n");

    }
    else {
        cp_interactive = FALSE;

        while (optind < argc) {
            ft_loadfile(argv[optind++]);
        }
    }

#endif /* ~ SIMULATOR */

    for (;;) {
        if (!SETJMP(jbuf, 1)) {
            /*  enter the command processing loop  */
            cp_interactive = TRUE;
#ifdef HAS_WINGUI
            int i;
            if (argv) {
                for (i = 0; i < argc; i++) {
                    txfree(argv[i]);
                }
                tfree(argv);
            }
#endif
            app_rl_readlines();
        }
        else {
            ft_sigintr_cleanup();
        }
    }
} /* end of function main */
