/*
 *  synth_integrator.h
 *
 *  This file is part of NEST
 *
 *  Copyright (C) 2004-2008 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.
 *
 *  (C) 2010 Jan Morén 
 *
 * This version is for nest2 prereleases. The logging API has changed so we
 * can no longer use the same module code for different nest versions.
 *
 */

#ifndef SYNTH_INTEGRATOR
#define SYNTH_INTEGRATOR

#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 "analog_data_logger.h"

namespace nest
{

  class Network;

  /* BeginDocumentation
Name: synth_integrator - Input integrator.

Description:

  synth integrator takes weighted spiking input, sums it over time, and emits 
  spikes at a rate proportional to the summed input. 

  This is intended as a synthetic replacement or placeholder to a real spike
  integrator circuit. Useful for testing networks where an integrating input 
  is assumed, or to tune a real integrating circuit.

  Hacked-up from iaf_psc_nmda_alpha

Parameters: 

  The following parameters can be set in the status dictionary.

    Smax    double	The accumulated spike*weight at which point the 
			model emits 1 spike/ms.
    Gamma   double	nonlinear scaling factor
    Reset   double	Negative rate input for resetting the integrator


Receives: SpikeEvent, DataLoggingRequest

Author:  February 2010, Moren
*/

  /**
   * Leaky integrate-and-fire neuron with alpha-shaped PSCs.
   */
  class synth_integrator : public Archiving_Node
    {

	public:        

	    typedef Node base;

	    synth_integrator();
	    synth_integrator(const synth_integrator&);

	    /**
	     * 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.
	     */
/*#ifndef IS_BLUEGENE
	    using Node::check_connection;
#endif*/
	    using Node::connect_sender;
	    using Node::handle;

	    port check_connection(Connection&, port);

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

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

	    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(Time const &, const long_t, const long_t);
	    friend class RecordablesMap<synth_integrator>;
	    friend class UniversalDataLogger<synth_integrator>;

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

	    /** 
	     * Independent parameters of the model. 
	     */
	    struct Parameters_ {

		/** event limit - at this point we do one spike per ms **/
		double_t Smax_;

		/** gamma factor for probability scaling **/
		double_t Gamma_;
		double_t Reset_;



		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 acc_; // Currently accumulated events 

		State_();  //!< Default initialization

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

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

	    /**
	     * Buffers of the model.
	     */
	    struct Buffers_ {
		Buffers_(synth_integrator&);
		Buffers_(const Buffers_&, synth_integrator&);
		/** buffers and summs up incoming spikes/currents */
		RingBuffer ex_spikes_;
		RingBuffer in_spikes_;
		//RingBuffer currents_;

		/** Buffer for membrane potential. */
		//AnalogDataLogger<PotentialRequest> potentials_;
		UniversalDataLogger<synth_integrator> logger_;
	    };

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

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

		double_t t_res;	    // Time resolution
		double_t weighted_spikes_ex_; // Not sure we need or want these
		double_t weighted_spikes_in_;

	    };

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

	    //! Read out the real membrane potential
//	    double_t get_V_m_() const { return S_.y3_ + P_.U0_; }

	    double_t get_weighted_spikes_ex_() const { return V_.weighted_spikes_ex_; }
	    double_t get_weighted_spikes_in_() const { return V_.weighted_spikes_in_; }

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

	    /**
	     * @defgroup synth_integrator_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_;
	    /** @} */
	    
	    static RecordablesMap<synth_integrator> recordablesMap_;
    };

  inline
      port synth_integrator::check_connection(Connection& c, port receptor_type)
      {
	  // printf ("\tCheckSynapse: %d\n", receptor_type);

	  SpikeEvent e;
	  e.set_sender(*this);
	  c.check_event(e);
	  return c.get_target()->connect_sender(e, receptor_type);
      }

  inline
      port synth_integrator::connect_sender(SpikeEvent&, port receptor_type)
      {
	  /* 0: normal receptor; 1: NMDA receptor */
	  //  printf ("\n\tSynapse: %d \n\n", receptor_type);

	  //if (receptor_type >= 1)
	  if (receptor_type != 0)
	      throw UnknownReceptorType(receptor_type, get_name());
	  return receptor_type;
      }

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

  inline
      port synth_integrator::connect_sender(DataLoggingRequest& dlr, port receptor_type)
      {
	  if (receptor_type != 0)
	      throw UnknownReceptorType(receptor_type, get_name());
	  //B_.potentials_.connect_logging_device(pr);
	  //return 0;
	  return B_.logger_.connect_logging_device(dlr, recordablesMap_);
      }
  inline
      void synth_integrator::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 synth_integrator::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

#endif /* #ifndef SYNTH_INTEGRATOR*/