/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Thomas L. Quarles
**********/

#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "vsrcdefs.h"
#include "ngspice/trandefs.h"
#include "ngspice/sperror.h"
#include "ngspice/suffix.h"
#include "ngspice/missing_math.h"
#include "ngspice/1-f-code.h"
#include "ngspice/compatmode.h"

#ifndef HAVE_LIBFFTW3
extern void fftFree(void);
#endif

extern bool ft_ngdebug; /* some additional debug info printed */

int
VSRCaccept(CKTcircuit *ckt, GENmodel *inModel)
        /* set up the breakpoint table.  */
{
    VSRCmodel *model = (VSRCmodel *) inModel;
    VSRCinstance *here;
    int error;

    /*  loop through all the voltage source models */
    for( ; model != NULL; model = VSRCnextModel(model)) {

        /* loop through all the instances of the model */
        for (here = VSRCinstances(model); here != NULL ;
                here=VSRCnextInstance(here)) {

            if(!(ckt->CKTmode & (MODETRAN | MODETRANOP))) {
                /* not transient, so shouldn't be here */
                return(OK);
            } else {
                /* use the transient functions */
                switch(here->VSRCfunctionType) {

                    default: { /* no function specified:DC   no breakpoints */
                        break;
                    }

                    case PULSE: {

                        double TD, TR, TF, PW, PER;
                        double time = 0.;
                        double basetime = 0;
                        double tmax = 1e99;

                        double PHASE;
                        double phase;
                        double deltat;

                        /* TR negative or 0 --> TR = ckt->CKTstep
                           TF negative or 0 --> TF = ckt->CKTstep
                           PW < 0 --> PW = 0
                           PER <= 0 --> PER = TR + TF + PW */
                        TD = here->VSRCfunctionOrder > 2
                            ? here->VSRCcoeffs[2] : 0.0;
                        TR = here->VSRCfunctionOrder > 3
                            && here->VSRCcoeffs[3] > 0.0
                            ? here->VSRCcoeffs[3] : ckt->CKTstep;
                        TF = here->VSRCfunctionOrder > 4
                            && here->VSRCcoeffs[4] > 0.0
                            ? here->VSRCcoeffs[4] : ckt->CKTstep;
                        PW = here->VSRCfunctionOrder > 5
                            && here->VSRCcoeffs[5] >= 0.0
                            ? here->VSRCcoeffs[5] : 0.0;
                        PER = here->VSRCfunctionOrder > 6
                            && here->VSRCcoeffs[6] > 0.0
                            ? here->VSRCcoeffs[6] : TR + TF + PW;
                        PHASE = here->VSRCfunctionOrder > 7
                            ? here->VSRCcoeffs[7] : 0.0;

                        /* offset time by delay */
                        time = ckt->CKTtime - TD;

                        if (newcompat.xs) {
                            /* normalize phase to 0 - 360° */
                            /* normalize phase to cycles */
                            phase = PHASE / 360.0;
                            phase = fmod(phase, 1.0);
                            deltat = phase * PER;
                            while (deltat > 0)
                                deltat -= PER;
                            time += deltat;
                        } else if (PHASE > 0.0) {
                            tmax = PHASE * PER;
                            if (time > tmax)
                                break;
                        }

                        if (ckt->CKTtime >= here->VSRCbreak_time) {
                            double wait, atime;

                            if (time >= PER) {
                                /* Repeating signal: where in period are we? */

                                basetime = PER * floor(time / PER);
                                time -= basetime;
                            }

                            /* A request for a breakpoint very close
                             * to the current time will be ignored.
                             * Adjust so the next corner will be
                             * selected.
                             */

                            atime = time + ckt->CKTminBreak;

                            /* Set next breakpoint. */

                            if (atime < 0.0) {
                                /* Await first pulse */

                                wait = -time;
                            } else if (atime < TR) {
                                /* Wait for end of rise. */

                                wait = TR - time;
                            } else if (atime < TR + PW) {
                                /* Wait for fall. */

                                wait = TR + PW - time;
                            } else if (atime < TR + PW + TF) {
                                /* Wait for end of fall. */

                                wait = TR + PW + TF - time;
                            } else {
                                /* Wait for next pulse. */

                                wait = PER - time;
                            }
                            here->VSRCbreak_time = ckt->CKTtime + wait;
                            error = CKTsetBreak(ckt, here->VSRCbreak_time);
                            if (error)
                                return error;

                            /* If a timestep ends just before the break time,
                             * the break request may be ignored.
                             * Set threshold for requesting following break.
                             */

                            here->VSRCbreak_time -= ckt->CKTminBreak;
                        }
                    }
                    break;

                    case SINE: {
                        /* no  breakpoints (yet) */
                    }
                    break;

                    case EXP: {
                        /* no  breakpoints (yet) */
                    }
                    break;

                    case SFFM:{
                        /* no  breakpoints (yet) */
                    }
                    break;

                    case AM:{
                        /* no  breakpoints (yet) */
                    }
                    break;

                    case PWL:
                        if (ckt->CKTtime >= here->VSRCbreak_time) {
                            double time, atime, end, period;
                            int    i;

                            time = ckt->CKTtime - here->VSRCrdelay;
                            end =
                                here->VSRCcoeffs[here->VSRCfunctionOrder - 2];
                            if (time > end) {
                                if (here->VSRCrGiven) {
                                    /* Repeating. */

                                    period = end -
                                        here->VSRCcoeffs[here->VSRCrBreakpt];
                                    time -=
                                        here->VSRCcoeffs[here->VSRCrBreakpt];
                                    time -= period * floor(time / period);
                                    time +=
                                        here->VSRCcoeffs[here->VSRCrBreakpt];
                                } else {
                                     here->VSRCbreak_time = ckt->CKTfinalTime;
                                     break;
                                }
                            }

                            /* A request for a breakpoint very close
                             * to the current time will be ignored.
                             * Adjust so the next corner will be
                             * selected.
                             */

                            atime = time + ckt->CKTminBreak;

                            for (i = 0;
                                 i < here->VSRCfunctionOrder;
                                 i += 2) {
                                if (here->VSRCcoeffs[i] > atime) {
                                    here->VSRCbreak_time =
                                        ckt->CKTtime +
                                            here->VSRCcoeffs[i] - time;
                                    error = CKTsetBreak(ckt,
                                                        here->VSRCbreak_time);
                                    if (error)
                                        return error;
                                    here->VSRCbreak_time -= ckt->CKTminBreak;
                                    break;
                                }
                            }
                        }
                        break;

    /**** transient noise routines:
    VNoi2 2 0  DC 0 TRNOISE(10n 0.5n 0 0n) : generate gaussian distributed noise
                            rms value, time step, 0 0
    VNoi1 1 0  DC 0 TRNOISE(0n 0.5n 1 10n) : generate 1/f noise
                            0,  time step, exponent < 2, rms value
    */
                    case TRNOISE: {


                        struct trnoise_state *state = here -> VSRCtrnoise_state;
                        double TS = state -> TS;
                        double RTSAM = state ->RTSAM;

                        if ((TS == 0.0) &&  (RTSAM == 0.0)) // no further breakpoint if value not given
                            break;

#ifndef HAVE_LIBFFTW3
                        /* FIXME, dont' want this here, over to aof_get or somesuch */
                        if (ckt->CKTtime == 0.0) {
                            if(ft_ngdebug)
                                printf("VSRC: free fft tables\n");
                            fftFree();
                        }
#endif
                        if (TS > 0 && ckt->CKTtime >= here->VSRCbreak_time) {
                            if (here->VSRCbreak_time < 0.0)
                                here->VSRCbreak_time = TS;
                            else
                                here->VSRCbreak_time += TS;
                            error = CKTsetBreak(ckt, here->VSRCbreak_time);
                            if (error)
                                return(error);
                            here->VSRCbreak_time -= ckt->CKTminBreak;
                        }

                        if (RTSAM <= 0)
                            break;      /* No shot noise. */

                        if (ckt->CKTtime == 0) {
                            /* initialzing here again needed for repeated calls to tran command */
                            state->RTScapTime = exprand(state->RTSCAPT);
                            state->RTSemTime =
                                state->RTScapTime + exprand(state->RTSEMT);
                            error = CKTsetBreak(ckt, state->RTScapTime);
                            if(error)
                                return(error);
                            break;
                        }

                        /* Break handling code ends a timestep close to
                         * the requested time.
                         */

                        if (ckt->CKTtime >=
                                state->RTScapTime - ckt->CKTminBreak &&
                            ckt->CKTtime <=
                                state->RTScapTime + ckt->CKTminBreak) {
                            error = CKTsetBreak(ckt, state->RTSemTime);
                            if(error)
                                return(error);
                        }

                        if (ckt->CKTtime >=
                                state->RTSemTime - ckt->CKTminBreak) {
                            /* new values */

                            state->RTScapTime =
                                ckt->CKTtime + exprand(state->RTSCAPT);
                            state->RTSemTime =
                                state->RTScapTime + exprand(state->RTSEMT);
                            error = CKTsetBreak(ckt, state->RTScapTime);
                            if(error)
                                return(error);
                        }
                    }
                    break;

                    case TRRANDOM: {
                        struct trrandom_state *state = here -> VSRCtrrandom_state;
                        double TS = state -> TS;
                        double TD = state -> TD;

                        if (ckt->CKTtime == 0 && TD > 0) {
                            error = CKTsetBreak(ckt, TD);
                            here->VSRCbreak_time = TD;
                            if (error)
                                return(error);
                            break;
                        }

                        if (ckt->CKTtime >= here->VSRCbreak_time) {
                            if (here->VSRCbreak_time < 0.0)
                                here->VSRCbreak_time = TS;
                            else
                                here->VSRCbreak_time += TS;
                            error = CKTsetBreak(ckt, here->VSRCbreak_time);
                            if (error)
                                return(error);
                            here->VSRCbreak_time -= ckt->CKTminBreak;
                            state->value = trrandom_state_get(state);
                        }
                    }
                    break;

#ifdef SHARED_MODULE
                    case EXTERNAL: {
                        /* no  breakpoints (yet) */
                    }
                    break;
#endif

                } // switch
            } // if ... else
        } // for
    } // for

    return(OK);
}
