/*
 *  izhikevich.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/>.
 *
 */

#ifndef IZHIKEVICH_H
#define IZHIKEVICH_H

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

namespace nest
{

  class Network;

  /* BeginDocumentation
     Name: izhikevich - Izhikevich neuron model

     Description:
     Implementation of the simple spiking neuron model introduced by Izhikevich [1].
     The dynamics are given by:
         dv/dt = 0.04*v^2 + 5*v + 140 - u + I
	    du/dt = a*(b*v - u)

	 if v >= V_th:
 	   v is set to c
	   u is incremented by d

	 v jumps on each spike arrival by the weight of the spike. 
   
     As published in [1], the numerics differs from the standard forward Euler technique 
     in two ways: 
     1) the new value of u is calulated based on the new value of v, rather than the 
        previous value
     2) the variable v is updated using a time step half the size of that used to update 
        variable u. 
   
     This model offers both forms of integration, they can be selected using the
     boolean parameter consistent_integration. To reproduce some results published on 
	 the basis of this model, it is necessary to use the published form of the dynamics.
     In this case, consistent_integration must be set to false. For all other purposes,
     it is recommended to use the standard technique for forward Euler integration. In 
     this case, consistent_integration must be set to true (default).


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

     V_m        double - Membrane potential in mV
     U_m		double - Membrane potential recovery variable
     V_th       double - Spike threshold in mV.
     I_e        double - Constant input current in pA. (R=1)
     V_min      double - Absolute lower value for the membrane potential.
     a			double - describes time scale of recovery variable
     b			double - sensitivity of recovery variable
     c			double - after-spike reset value of V_m
     d			double - after-spike reset value of U_m
	 consistent_integration		bool - use standard integration technique


     References:
     [1] Izhikevich, Simple Model of Spiking Neurons,
     IEEE Transactions on Neural Networks (2003) 14:1569-1572

     Sends: SpikeEvent

     Receives: SpikeEvent, CurrentEvent, DataLoggingRequest
     FirstVersion: 2009
     Author: Hanuschkin, Morrison, Kunkel
     SeeAlso: iaf_psc_delta, mat2_psc_exp
  */
  class izhikevich : public Archiving_Node
  {

   public:

    izhikevich();
    izhikevich(const izhikevich &);

    /**
     * Import sets of overloaded virtual functions.
     * We need to explicitly include sets of overloaded
     * virtual functions into the current scope.
     * According to the SUN C++ FAQ, this is the correct
     * way of doing things, although all other compilers
     * happily live without.
     */

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

    void handle(DataLoggingRequest &);
    void handle(SpikeEvent &);
    void handle(CurrentEvent &);

    port connect_sender(DataLoggingRequest &, port);
    port connect_sender(SpikeEvent &, port);
    port connect_sender(CurrentEvent &, port);

    port check_connection(Connection&, port);

    void get_status(DictionaryDatum &) const;
    void set_status(const DictionaryDatum &);

   private:
    friend class RecordablesMap<izhikevich>;
    friend class UniversalDataLogger<izhikevich>;

    void init_state_(const Node & proto);
    void init_buffers_();
    void calibrate();

    void update(Time const &, const long_t, const long_t);

    // ----------------------------------------------------------------

    /**
     * Independent parameters of the model.
     */
    struct Parameters_ {
      double_t a_;
      double_t b_;
      double_t c_;
      double_t d_;

      /** External DC current */
      double_t I_e_;

      /** Threshold */
      double_t V_th_;

      /** Lower bound */
      double_t V_min_;

	  /** Use standard integration numerics **/	
      bool consistent_integration_;

      Parameters_();  //!< Sets default parameter values

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

    // ----------------------------------------------------------------

    /**
     * State variables of the model.
     */
    struct State_ {
      double_t     v_; // membrane potential
      double_t     u_; // membrane recovery variable
      double_t     I_; // input current


      /** Accumulate spikes arriving during refractory period, discounted for
	  decay until end of refractory period.
      */

      State_();  //!< Default initialization

      void get(DictionaryDatum&, const Parameters_&) const;
      void set(const DictionaryDatum&, const Parameters_&);
    };

    // ----------------------------------------------------------------

    /**
     * Buffers of the model.
     */
    struct Buffers_ {
      /**
       * Buffer for recording
       */
      Buffers_(izhikevich &);
      Buffers_(const Buffers_ &, izhikevich &);
      UniversalDataLogger<izhikevich> logger_;

      /** buffers and sums up incoming spikes/currents */
      RingBuffer spikes_;
      RingBuffer currents_;
    };

    // ----------------------------------------------------------------

    /**
     * Internal variables of the model.
     */
    struct Variables_ {};

    // Access functions for UniversalDataLogger -----------------------

    //! Read out the membrane potential
    double_t get_V_m_() const { return S_.v_; }

    // ----------------------------------------------------------------

    /**
     * @defgroup iaf_psc_alpha_data
     * Instances of private data structures for the different types
     * of data pertaining to the model.
     * @note The order of definitions is important for speed.
     * @{
     */
    Parameters_ P_;
    State_      S_;
    Variables_  V_;
    Buffers_    B_;

    //! Mapping of recordables names to access functions
    static RecordablesMap<izhikevich> recordablesMap_;
    /** @} */

  };

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

  inline
  port izhikevich::connect_sender(SpikeEvent&, port receptor_type)
  {
    if (receptor_type != 0)
      throw UnknownReceptorType(receptor_type, get_name());
    return 0;
  }

  inline
  port izhikevich::connect_sender(CurrentEvent&, port receptor_type)
  {
    if (receptor_type != 0)
      throw UnknownReceptorType(receptor_type, get_name());
    return 0;
  }

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

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

  inline
  void izhikevich::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.
    Archiving_Node::set_status(d);

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

} // namespace nest

#endif /* #ifndef IZHIKEVICH_H */