/*
 *  iaf_cond_alpha_bias.h
 *
 *  This file is part of NEST
 *
 *  Copyright (C) 2005-2009 by
 *  The NEST Initiative
 *
 *  See the file AUTHORS for details.
 *
 *  Permission is granted to compile and modify
 *  this file for non-commercial use.
 *  See the file LICENSE for details.
 *
 *  written by Philip Tully
 *  first version February 2012
 *
 */

#ifndef IAF_COND_ALPHA_BIAS_H
#define IAF_COND_ALPHA_BIAS_H

#include "config.h"

#ifdef HAVE_GSL

#include "nest.h"
#include "event.h"
#include "archiving_node.h"
#include "ring_buffer.h"
#include "connection.h"
#include "universal_data_logger.h"
#include "recordables_map.h"

#include <gsl/gsl_errno.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_odeiv.h>

/* BeginDocumentation
Name: iaf_cond_alpha_bias - Simple conductance based leaky integrate-and-fire neuron model.
                            Incorporates Bayesian Bias dynamics depending on incoming spike events.

Description:
iaf_cond_alpha is an implementation of a spiking neuron using IAF dynamics with
conductance-based synapses. Incoming spike events induce a post-synaptic change 
of conductance modelled by an alpha function. The alpha function 
is normalised such that an event of weight 1.0 results in a peak current of 1 nS
at t = tau_syn.

Parameters: 
The following parameters can be set in the status dictionary.

V_m        double - Membrane potential in mV 
E_L        double - Leak reversal potential in mV.
C_m        double - Capacity of the membrane in pF
t_ref      double - Duration of refractory period in ms. 
V_th       double - Spike threshold in mV.
V_reset    double - Reset potential of the membrane in mV.
E_ex       double - Excitatory reversal potential in mV.
E_in       double - Inhibitory reversal potential in mV.
g_L        double - Leak conductance in nS;
tau_syn_ex double - Rise time of the excitatory synaptic alpha function in ms.
tau_syn_in double - Rise time of the inhibitory synaptic alpha function in ms.
I_e        double - Constant input current in pA.
tau_j--\
tau_e   >- double - Postsynaptic trace time constants
tau_p--/
kappa      double - 'print now' signal

Sends: SpikeEvent

Receives: SpikeEvent, CurrentEvent, DataLoggingRequest

References: 

Author: Tully, Philip

SeeAlso: iaf_cond_alpha, iaf_cond_exp, iaf_cond_alpha_mc
*/

namespace mynest
{
  /**
   * Function computing right-hand side of ODE for GSL solver.
   * @note Must be declared here so we can befriend it in class.
   * @note Must have C-linkage for passing to GSL. Internally, it is
   *       a first-class C++ function, but cannot be a member function
   *       because of the C-linkage.
   * @note No point in declaring it inline, since it is called
   *       through a function pointer.
   * @param void* Pointer to model neuron instance.
   */
  extern "C"
  int iaf_cond_alpha_bias_dynamics (double, const double*, double*, void*);

  /**
   * Integrate-and-fire neuron model with two conductance-based synapses.
   *
   * @note Per 2009-04-17, this class has been revised to our newest 
   *       insights into class design. Please use THIS CLASS as a reference
   *       when designing your own models with nonlinear dynamics.
   *       One weakness of this class is that it distinguishes between
   *       inputs to the two synapses by the sign of the synaptic weight.
   *       It would be better to use receptor_types, cf iaf_cond_alpha_mc.
   */
  class iaf_cond_alpha_bias : public nest::Archiving_Node
  {
    
    // Boilerplate function declarations --------------------------------

  public:
    
    iaf_cond_alpha_bias();
    iaf_cond_alpha_bias(const iaf_cond_alpha_bias&);
    ~iaf_cond_alpha_bias();

    /*
     * Import all overloaded virtual functions that we
     * override in this class.  For background information, 
     * see http://www.gotw.ca/gotw/005.htm.
     */

    using nest::Node::connect_sender;
    using nest::Node::handle;

    nest::port check_connection(nest::Connection&, nest::port);

    nest::port connect_sender(nest::SpikeEvent &, nest::port);
    nest::port connect_sender(nest::CurrentEvent &, nest::port);
    nest::port connect_sender(nest::DataLoggingRequest &, nest::port);
    
    void handle(nest::SpikeEvent &);
    void handle(nest::CurrentEvent &);
    void handle(nest::DataLoggingRequest &); 
        
    void get_status(DictionaryDatum &) const;
    void set_status(const DictionaryDatum &);

  private:
    void init_node_(const Node& proto);
    void init_state_(const Node& proto);
    void init_buffers_();
    void calibrate();
    void update(nest::Time const &, const nest::long_t, const nest::long_t);

    // END Boilerplate function declarations ----------------------------

    // Friends --------------------------------------------------------

    // make dynamics function quasi-member mynest? or nest? also above
    friend int mynest::iaf_cond_alpha_bias_dynamics(double, const double*, double*, void*);

    // The next two classes need to be friends to access the State_ class/member
    friend class nest::RecordablesMap<iaf_cond_alpha_bias>;
    friend class nest::UniversalDataLogger<iaf_cond_alpha_bias>;

  private:

    // Parameters class ------------------------------------------------- 

    //! Model parameters
    struct Parameters_ {
      nest::double_t V_th;        //!< Threshold Potential in mV
      nest::double_t V_reset;     //!< Reset Potential in mV
      nest::double_t t_ref;       //!< Refractory period in ms
      nest::double_t g_L;         //!< Leak Conductance in nS
      nest::double_t C_m;         //!< Membrane Capacitance in pF
      nest::double_t E_ex;        //!< Excitatory reversal Potential in mV
      nest::double_t E_in;        //!< Inhibitory reversal Potential in mV
      nest::double_t E_L;         //!< Leak reversal Potential (aka resting potential) in mV
      nest::double_t tau_synE;    //!< Synaptic Time Constant Excitatory Synapse in ms
      nest::double_t tau_synI;    //!< Synaptic Time Constant for Inhibitory Synapse in ms
      nest::double_t I_e;         //!< Constant Current in pA
      nest::double_t tau_j;
      nest::double_t tau_e;
      nest::double_t tau_p;
      nest::double_t bias;
      nest::double_t fmax;
      nest::double_t gain;
      nest::double_t epsilon;
      nest::double_t kappa;
  
      Parameters_();        //!< Set default parameter values

      void get(DictionaryDatum&) const;  //!< Store current values in dictionary
      void set(const DictionaryDatum&);  //!< Set values from dicitonary
    };
    
    // State variables class -------------------------------------------- 

    /**
     * State variables of the model.
     * 
     * State variables consist of the state vector for the subthreshold
     * dynamics and the refractory count. The state vector must be a
     * C-style array to be compatible with GSL ODE solvers.
     *
     * @note Copy constructor and assignment operator are required because
     *       of the C-style array.
     */
  public:
    struct State_ {
      
      //! Symbolic indices to the elements of the state vector y
      enum StateVecElems { V_M = 0,           
			   DG_EXC, //1
                           G_EXC,  //2    
			   DG_INH, //3
                           G_INH,  //4
                           Z_J,    //5
                           E_J,    //6
                           P_J,    //7
                           //BIAS,   //8
			   STATE_VEC_SIZE };

      //! state vector, must be C-array for GSL solver
      nest::double_t y[STATE_VEC_SIZE];
  
      //!< number of refractory steps remaining
      nest::int_t    r; 

      //!< Bias calculated from the P_J trace 
      nest::double_t bias;
      nest::double_t epsilon;
      nest::double_t kappa;

      State_(const Parameters_&);  //!< Default initialization
      State_(const State_&);
      State_& operator=(const State_&);

      void get(DictionaryDatum&) const;  //!< Store current values in dictionary

      /**
       * Set state from values in dictionary.
       * Requires Parameters_ as argument to, eg, check bounds.'
       */
      void set(const DictionaryDatum&, const Parameters_&);
    };    
  private:

    // Buffers class -------------------------------------------------------- 

    /**
     * Buffers of the model.
     * Buffers are on par with state variables in terms of persistence,
     * i.e., initalized only upon first Simulate call after ResetKernel
     * or ResetNetwork, but are implementation details hidden from the user.
     */
    struct Buffers_ {
      Buffers_(iaf_cond_alpha_bias&); //!<Sets buffer pointers to 0
      Buffers_(const Buffers_&, iaf_cond_alpha_bias&); //!<Sets buffer pointers to 0

      //! Logger for all analog data
      nest::UniversalDataLogger<iaf_cond_alpha_bias> logger_;

      /** buffers and sums up incoming spikes/currents */
      nest::RingBuffer spike_exc_;
      nest::RingBuffer spike_inh_;
      nest::RingBuffer currents_;

      /* GSL ODE stuff */
      gsl_odeiv_step*    s_;    //!< stepping function
      gsl_odeiv_control* c_;    //!< adaptive stepsize control function
      gsl_odeiv_evolve*  e_;    //!< evolution function
      gsl_odeiv_system   sys_;  //!< struct describing system
      
      // IntergrationStep_ should be reset with the neuron on ResetNetwork,
      // but remain unchanged during calibration. Since it is initialized with
      // step_, and the resolution cannot change after nodes have been created,
      // it is safe to place both here.
      nest::double_t step_;           //!< step size in ms
      double   IntegrationStep_;//!< current integration time step, updated by GSL

      /** 
       * Input current injected by CurrentEvent.
       * This variable is used to transport the current applied into the
       * _dynamics function computing the derivative of the state vector.
       * It must be a part of Buffers_, since it is initialized once before
       * the first simulation, but not modified before later Simulate calls.
       */
      double_t I_stim_;
    };
    
    // Variables class ------------------------------------------------------- 
    
    /**
     * Internal variables of the model.
     * Variables are re-initialized upon each call to Simulate.
     */
    struct Variables_ { 
      /**
       * Impulse to add to DG_EXC on spike arrival to evoke unit-amplitude
       * conductance excursion.
       */
      nest::double_t PSConInit_E; 
      
      /**
       * Impulse to add to DG_INH on spike arrival to evoke unit-amplitude
       * conductance excursion.
       */
      nest::double_t PSConInit_I;    
      
      //! refractory time in steps
      nest::int_t    RefractoryCounts;
    };
    
    // Access functions for UniversalDataLogger -------------------------------
    
    //! Read out state vector elements, used by UniversalDataLogger
    template <State_::StateVecElems elem>
    nest::double_t get_y_elem_() const { return S_.y[elem]; }
    
    //! Read out remaining refractory time, used by UniversalDataLogger
    nest::double_t get_r_() const { return nest::Time::get_resolution().get_ms() * S_.r; }

    //! Read out Bias, used by UniversalDataLogger
    nest::double_t get_bias_() const { return S_.bias; }
    nest::double_t get_epsilon_() const { return S_.epsilon; }
    nest::double_t get_kappa_() const { return S_.kappa; }
    
    // Data members ----------------------------------------------------------- 

    // keep the order of these lines, seems to give best performance
    Parameters_ P_;
    State_      S_;
    Variables_  V_;
    Buffers_    B_;

    //! Mapping of recordables names to access functions
    static nest::RecordablesMap<iaf_cond_alpha_bias> recordablesMap_;
  };
  

  // Boilerplate inline function definitions ----------------------------------

  inline
  nest::port mynest::iaf_cond_alpha_bias::check_connection(nest::Connection& c, nest::port receptor_type)
  {
    nest::SpikeEvent e;
    e.set_sender(*this);
    c.check_event(e);
    return c.get_target()->connect_sender(e, receptor_type);
  }

  inline
  nest::port mynest::iaf_cond_alpha_bias::connect_sender(nest::SpikeEvent&, nest::port receptor_type)
  {
    if (receptor_type != 0)
      throw nest::UnknownReceptorType(receptor_type, get_name());
    return 0;
  }
 
  inline
  nest::port mynest::iaf_cond_alpha_bias::connect_sender(nest::CurrentEvent&, nest::port receptor_type)
  {
    if (receptor_type != 0)
      throw nest::UnknownReceptorType(receptor_type, get_name());
    return 0;
  }
 
  inline
  nest::port mynest::iaf_cond_alpha_bias::connect_sender(nest::DataLoggingRequest& dlr, 
				      nest::port receptor_type)
  {
    if (receptor_type != 0)
      throw nest::UnknownReceptorType(receptor_type, get_name());
    return B_.logger_.connect_logging_device(dlr, recordablesMap_);
  }

  inline
  void iaf_cond_alpha_bias::get_status(DictionaryDatum &d) const
  {
    P_.get(d);
    S_.get(d);
    nest::Archiving_Node::get_status(d);

    (*d)[nest::names::recordables] = recordablesMap_.get_list();
  }

  inline
  void iaf_cond_alpha_bias::set_status(const DictionaryDatum &d)
  {
    Parameters_ ptmp = P_;  // temporary copy in case of errors
    ptmp.set(d);                       // throws if BadProperty
    State_      stmp = S_;  // temporary copy in case of errors
    stmp.set(d, ptmp);                 // throws if BadProperty

    // We now know that (ptmp, stmp) are consistent. We do not 
    // write them back to (P_, S_) before we are also sure that 
    // the properties to be set in the parent class are internally 
    // consistent.
    nest::Archiving_Node::set_status(d);

    // if we get here, temporaries contain consistent set of properties
    P_ = ptmp;
    S_ = stmp;
  }

} // namespace

#endif //IAF_COND_ALPHA_BIAS_H

#endif //HAVE_GSL