#ifndef PIF_PSC_ALPHA_H
#define PIF_PSC_ALPHA_H

/*
 *  pif_psc_alpha.h
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  NEST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


#include "nest.h"
#include "event.h"
#include "node.h"
#include "ring_buffer.h"
#include "connection.h"
#include "universal_data_logger.h"
#include "dictdatum.h"

namespace mynest {

  /* BeginDocumentation
Name: pif_psc_alpha - Perfect integrate-and-fire neuron model with alpha PSC synapse.

Description:
  pif_psc_alpha implements a non-leaky integrate-and-fire neuron with
  with alpha-function shaped synaptic currents. The threshold crossing is
  followed by an absolute refractory period during which the membrane potential
  is clamped to the resting potential, while synaptic currents evolve normally.

  The dynamics of the neuron are defined by

     C_m dV/dt  = I_syn(t) + I_e

     I_syn(t)   = Sum_{t_{j,k} < t} w_j x (t-t_{j,k}) x e/tau_syn x e^{-(t-t_{j,k})/tau_syn}

  where t_{j,k} is the time of the k-th spike arriving from neuron j, and w_j is
  the weight of the synapse from neuron j onto the present neuron. The alpha function
  is normalized by amplitude, i.e., the maximum input current for any spike is w_j.

  Parameters:
  C_m      double - Membrane capacitance, in pF
  I_e      double - Intrinsic DC current, in nA
  tau_syn  double - Synaptic time constant, in ms
  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.

  Remarks:
  The linear subthresold dynamics is integrated by the Exact
  Integration scheme [1]. The neuron dynamics is solved on the time
  grid given by the computation step size. Incoming as well as emitted
  spikes are forced to that grid.

References:
  [1] Rotter S & Diesmann M (1999) Exact simulation of time-invariant linear
      systems with applications to neuronal modeling. Biologial Cybernetics
      81:381-402.

Sends: SpikeEvent

Receives: SpikeEvent, CurrentEvent, DataLoggingRequest

Author:
  Hans Ekkehard Plesser, based on iaf_psc_alpha

SeeAlso:
iaf_psc_delta, iaf_psc_exp, iaf_psc_alpha
*/

  /**
   * Non-leaky integrate-and-fire neuron with alpha-shaped PSCs.
   */
  class pif_psc_alpha : public nest::Node
  {
  public:

    /**
     * The constructor is only used to create the model prototype in the model manager.
     */
    pif_psc_alpha();

    /**
     * The copy constructor is used to create model copies and instances of the model.
     * @node The copy constructor needs to initialize the parameters and the state.
     *       Initialization of buffers and interal variables is deferred to
     *       @c init_buffers_() and @c calibrate().
     */
    pif_psc_alpha(const pif_psc_alpha&);

    /**
     * Import sets of overloaded virtual functions.
     * This is necessary to ensure proper overload and overriding resolution.
     * @see http://www.gotw.ca/gotw/005.htm.
     */
    using nest::Node::connect_sender;
    using nest::Node::handle;

    /**
     * Used to validate that we can send SpikeEvent to desired target:port.
     */
    nest::port check_connection(nest::Connection&, nest::port);

    /**
     * @defgroup mynest_handle Functions handling incoming events.
     * We tell nest that we can handle incoming events of various types by
     * defining @c handle() and @c connect_sender() for the given event.
     * @{
     */
    void handle(nest::SpikeEvent &);        //! accept spikes
    void handle(nest::CurrentEvent &);      //! accept input current
    void handle(nest::DataLoggingRequest &);//! allow recording with multimeter

    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 get_status(DictionaryDatum &) const;
    void set_status(const DictionaryDatum &);

  private:

    //! Reset parameters and state of neuron.

    //! Reset state of neuron.
    void init_state_(const Node& proto);

    //! Reset internal buffers of neuron.
    void init_buffers_();

    //! Initialize auxiliary quantities, leave parameters and state untouched.
    void calibrate();

    //! Take neuron through given time interval
    void update(nest::Time const &, const nest::long_t, const nest::long_t);

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

    /**
     * Free parameters of the neuron.
     *
     * These are the parameters that can be set by the user through @c SetStatus.
     * They are initialized from the model prototype when the node is created.
     * Parameters do not change during calls to @c update() and are not reset by
     * @c ResetNetwork.
     *
     * @note Parameters_ need neither copy constructor nor @c operator=(), since
     *       all its members are copied properly by the default copy constructor
     *       and assignment operator. Important:
     *       - If Parameters_ contained @c Time members, you need to define the
     *         assignment operator to recalibrate all members of type @c Time . You
     *         may also want to define the assignment operator.
     *       - If Parameters_ contained members that cannot copy themselves, such
     *         as C-style arrays, you need to define the copy constructor and
     *         assignment operator to copy those members.
     */
    struct Parameters_ {
      double C_m;      //!< Membrane capacitance, in pF.
      double I_e;      //!< Intrinsic DC current, in nA.
      double tau_syn;  //!< Synaptic time constant, in ms.
      double V_th;     //!< Spike threshold, in mV.
      double V_reset;  //!< Reset potential of the membrane, in mV.
      double t_ref;    //!< Duration of refractory period, in ms.

      //! Initialize parameters to their default values.
      Parameters_();

      //! Store parameter values in dictionary.
      void get(DictionaryDatum&) const;

      //! Set parameter values from dictionary.
      void set(const DictionaryDatum&);
    };

    /**
     * Dynamic state of the neuron.
     *
     * These are the state variables that are advanced in time by calls to
     * @c update(). In many models, some or all of them can be set by the user
     * through @c SetStatus. The state variables are initialized from the model
     * prototype when the node is created. State variables are reset by @c ResetNetwork.
     *
     * @note State_ need neither copy constructor nor @c operator=(), since
     *       all its members are copied properly by the default copy constructor
     *       and assignment operator. Important:
     *       - If State_ contained @c Time members, you need to define the
     *         assignment operator to recalibrate all members of type @c Time . You
     *         may also want to define the assignment operator.
     *       - If State_ contained members that cannot copy themselves, such
     *         as C-style arrays, you need to define the copy constructor and
     *         assignment operator to copy those members.
     */
    struct State_ {
      double V_m;        //!< Membrane potential, in mV.
      double dI_syn;     //!< Derivative of synaptic current, in nA/ms.
      double I_syn;      //!< Synaptic current, in nA.
      double I_ext;      //!< External current, in nA.
      long   refr_count; //!< Number of steps neuron is still refractory for

      /**
       * Construct new default State_ instance based on values in Parameters_.
       * This c'tor is called by the no-argument c'tor of the neuron model. It
       * takes a reference to the parameters instance of the model, so that the
       * state can be initialized in accordance with parameters, e.g., initializing
       * the membrane potential with the resting potential.
       */
      State_(const Parameters_&);

      /** Store state values in dictionary. */
      void get(DictionaryDatum&) const;

      /**
       * Set membrane potential from dictionary.
       * @note Receives Parameters_ so it can test that the new membrane potential
       *       is below threshold.
       */
      void set(const DictionaryDatum&, const Parameters_&);
    };

    /**
     * Buffers of the neuron.
     * Ususally buffers for incoming spikes and data logged for analog recorders.
     * Buffers must be initialized by @c init_buffers_(), which is called before
     * @c calibrate() on the first call to @c Simulate after the start of NEST,
     * ResetKernel or ResetNetwork.
     * @node Buffers_ needs neither constructor, copy constructor or assignment operator,
     *       since it is initialized by @c init_nodes_(). If Buffers_ has members that
     *       cannot destroy themselves, Buffers_ will need a destructor.
     */
    struct Buffers_ {
      Buffers_(pif_psc_alpha&);
      Buffers_(const Buffers_ &, pif_psc_alpha&);

      nest::RingBuffer spikes;    //!< Buffer incoming spikes through delay, as sum
      nest::RingBuffer currents;  //!< Buffer incoming currents through delay, as sum

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

    /**
     * Internal variables of the neuron.
     * These variables must be initialized by @c calibrate, which is called before
     * the first call to @c update() upon each call to @c Simulate.
     * @node Variables_ needs neither constructor, copy constructor or assignment operator,
     *       since it is initialized by @c calibrate(). If Variables_ has members that
     *       cannot destroy themselves, Variables_ will need a destructor.
     */
    struct Variables_ {
      double P11;
      double P21;
      double P22;
      double P31;
      double P32;
      double P30;
      double P33;

      double pscInitialValue;
      long   t_ref_steps;  //!< Duration of refractory period, in steps.
    };

    /**
     * @defgroup Access functions for UniversalDataLogger.
     * @{
     */
    //! Read out the real membrane potential
    nest::double_t get_V_m_() const { return S_.V_m; }
    /** @} */

    /**
     * @defgroup pif_members Member variables of neuron model.
     * Each model neuron should have precisely the following four data members,
     * which are one instance each of the parameters, state, buffers and variables
     * structures. Experience indicates that the state and variables member should
     * be next to each other to achieve good efficiency (caching).
     * @note Devices require one additional data member, an instance of the @c Device
     *       child class they belong to.
     * @{
     */
    Parameters_ P_;  //!< Free parameters.
    State_      S_;  //!< Dynamic state.
    Variables_  V_;  //!< Internal Variables
    Buffers_    B_;  //!< Buffers.

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

    /** @} */
  };

inline
nest::port mynest::pif_psc_alpha::check_connection(nest::Connection& c, nest::port receptor_type)
{
  // You should usually not change the code in this function.
  // It confirms that the target of connection @c c accepts @c SpikeEvent on
  // the given @c 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::pif_psc_alpha::connect_sender(nest::SpikeEvent&, nest::port receptor_type)
{
  // You should usually not change the code in this function.
  // It confirms to the connection management system that we are able
  // to handle @c SpikeEvent on port 0. You need to extend the function
  // if you want to differentiate between input ports.
  if (receptor_type != 0)
    throw nest::UnknownReceptorType(receptor_type, get_name());
  return 0;
}

inline
nest::port mynest::pif_psc_alpha::connect_sender(nest::CurrentEvent&, nest::port receptor_type)
{
  // You should usually not change the code in this function.
  // It confirms to the connection management system that we are able
  // to handle @c CurrentEvent on port 0. You need to extend the function
  // if you want to differentiate between input ports.
  if (receptor_type != 0)
    throw nest::UnknownReceptorType(receptor_type, get_name());
  return 0;
}

inline
nest::port mynest::pif_psc_alpha::connect_sender(nest::DataLoggingRequest& dlr,
						 nest::port receptor_type)
{
  // You should usually not change the code in this function.
  // It confirms to the connection management system that we are able
  // to handle @c DataLoggingRequest on port 0.
  // The function also tells the built-in UniversalDataLogger that this node
  // is recorded from and that it thus needs to collect data during simulation.
  if (receptor_type != 0)
    throw nest::UnknownReceptorType(receptor_type, get_name());

  return B_.logger_.connect_logging_device(dlr, recordablesMap_);
}

inline
void pif_psc_alpha::get_status(DictionaryDatum &d) const
{
  P_.get(d);
  S_.get(d);
  (*d)[nest::names::recordables] = recordablesMap_.get_list();
}

inline
void pif_psc_alpha::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

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

} // namespace

#endif /* #ifndef PIF_PSC_ALPHA_H */