/**
 * @file nrndae.h
 * @brief Supports modifying voltage equations and adding new equations.
 * @author Michael Hines, Robert McDougal
 *
 * @remark Subclasses of NrnDAE can work with equations of the form
 *         $C \frac{dy}{dt} = f(y)$. LinearModelAddition, defined in linmod.h
 *         and linmod.cpp is an example that supports linear dynamics of the
 *         form $C \frac{dy}{dt} = A y + b$.
 */

#ifndef nrndae_h
#define nrndae_h
// this defines things needed by ocmatrix
#include <OS/list.h>

#include "ivocvect.h"
#include "matrixmap.h"
#include <list>

/**
 * NEURON Differential Algebraic Equations.
 *
 * @remark This is an abstract class; subclass this (or use a subclass) to
 *         add dynamics. LinearModelAddition is an example. See linmod.h.
 */
class NrnDAE {
public:
    /**
     * Find the number of state variables introduced by this object.
     *
     * @return The number of states added (not modified) by this instance.
     */
	int extra_eqn_count();
	
	/**
	 * Allocate space for these dynamics in the overall system.
	 *
	 * @param start_index   starting index for new states
	 */
	void alloc(int start_index);
	
	/**
	 * Compute the left side portion of $(C - J) \frac{dy}{dt} = f(y)$.
	 */
	void lhs();
	
    /**
	 * Compute the right side portion of $(C - J) \frac{dy}{dt} = f(y)$.
	 */
	void rhs();
	
	/**
	 * Compute the residual: $f(y) - C \frac{dy}{dt}$
	 *
	 * @param y             array of state variables
	 * @param yprime        array of derivatives of state variables
	 * @param delta         array to store the difference $f(y)-Cy'$
	 */
	void dkres(double* y, double* yprime, double* delta);
	
	/**
	 * Initialize the dynamics.
	 *
	 * @remark Does this by calling f_init_. If f_init_ is NULL, initializes to
	 *         values in y0_. If y0_ is NULL, initializes all states to 0.
	 */
	void init();	
	
	/**
	 * Update states to reflect the changes over a time-step.
	 *
	 * @remark When this function is called, the changes have already been
	 *         computed by the solver. This just updates the local variables.
	 */
	void update();
	
	/**
	 * Setup the map between voltages and states in y_.
	 *
	 * @param pv            pointers to voltage nodes (set by this function)
	 * @param pvdot         pointers to voltage derivatives (set by this
	 *                      function)
	 */
	void dkmap(double** pv, double** pvdot);
	
	/**
	 * Destructor.
	 */
	virtual ~NrnDAE();
	
protected:
    /**
     * Constructor.
     *
     * @param cmat          the matrix $C$ in $Cy'=f(y)$.
     * @param yvec          vector to store the state variables in
     * @param y0            initial conditions
     * @param nnode         number of voltage equations to modify
     * @param nodes         pointers to voltage nodes
     * @param elayer        which potential layer to use for each voltage node
     * @param f_init        function to call during an finitialize
     * @param data          data to pass to f_init
     *
     * @remark If cmat is NULL, then assumes $C$ is the identity matrix.
     * @remark If f_init is non-NULL, that takes priority. Otherwise, if
     *         y0 is non-NULL, then initializes to those values. Otherwise
     *         initializes by setting all states to 0.
     */
	NrnDAE(Matrix* cmat, Vect* const yvec, Vect* const y0, int nnode,
	       Node** const nodes, Vect* const elayer,
	       void (*f_init)(void* data) = NULL, void* const data = NULL);
	
private:
    /**
     * The right-hand-side function.
     *
     * @param y             the state variables
     * @param yprime        a vector to store the derivatives
     * @param size          the number of state variables
     */
	virtual void f_(Vect& y, Vect& yprime, int size) = 0;
	
	/**
	 * Compute the Jacobian.
	 *
	 * @param y             the state variables
	 * @return Pointer to a MatrixMap containing the jacobian.
	 *
	 * @remark The calling function will not delete this pointer.
	 * @remark It is occasionally easier to return the Jacobian divided by
	 *         a constant factor. If so, have jacobian_multiplier_ return a
	 *         number that should be multiplied by the matrix returned by
	 *         this function to get the true Jacobian.
	 */
	virtual MatrixMap* jacobian_(Vect& y) = 0;
    
    /// Function used for initializing the state variables.
    void (*f_init_)(void* data);
    
    /// Data to pass to f_init_.
    void* data_;

	// value of jacobian_ must be multiplied by this value before use
	virtual double jacobian_multiplier_() {return 1;}

    /**
     * Additional allocation for subclasses.
     *
     * @remark Called during alloc(). Unless overriden, this function is empty.
     */
	virtual void alloc_(int size, int start, int nnode, Node** nodes,
	                    int* elayer);
	
	/// the matrix $C$ in $C y' = f(y)$
	MatrixMap* c_;

    /// identity matrix if constructed with $C$ NULL; else NULL.	
    Matrix* assumed_identity_;

    /// vector of initial conditions
	Vect* y0_;
	
	/// vector to store the state variables in
	Vect& y_;
	
	/// total number of states declared or modified in this object
	int size_;
	
	/// mapping between the states in y and the states in the whole system
	int* bmap_;

	/// Number of voltage nodes used by the dynamics.
	int nnode_;
	
	/// Pointers to voltage nodes used by the dynamics.
	Node** nodes_;

    /// the position of the first added equation (if any) in the global system
	int start_;
	
	/// temporary vector used for residual calculation.
	Vect cyp_;

	/// temporary vector used for residual calculation.
	Vect yptmp_;
	
	/**
     * Which voltage layers to read from.
     *
     * @remark Normally elements are 0 and refer to internal potential.
     *         Otherwise range from 1 to nlayer and refer to vext[elayer-1].
     *         vm+vext and vext must be placed in y for calculation of rhs
	 */
	int* elayer_;

    /// Transfer any voltage states to y_.
	void v2y();
};

/**
 * Add a NrnDAE object to the system.
 *
 * @param n The NrnDAE object (ie the dynamics) to add.
 */
void nrndae_register(NrnDAE* n);

/**
 * Remove a NrnDAE object from the system.
 *
 * @param n The NrnDAE object (ie the dynamics) to remove.
 */
void nrndae_deregister(NrnDAE* n);

typedef std::list<NrnDAE*> NrnDAEPtrList;
typedef NrnDAEPtrList::const_iterator NrnDAEPtrListIterator;

#endif

