/*
 *  mip_generator.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 MIP_GENERATOR_H
#define MIP_GENERATOR_H
/****************************************/
/* class mip_generator                  */
/*       Vers. 1.0       moritz         */
/*                                      */
/****************************************/

#include "nest.h"
#include "event.h"
#include "node.h"
#include "stimulating_device.h"
#include "connection.h"
#include "poisson_randomdev.h"

namespace nest
{
//! class mip_generator                           
/*! Class mip_generator generates spike trains as described
    in the MIP model. 
*/


/*BeginDocumentation
Name: mip_generator - create spike trains as described by the MIP model.
Description:
  The mip_generator generates correlated spike trains using an Multiple Interaction 
  Process (MIP) as described in [1]. Underlying principle is a Poisson mother process 
  with rate r, the spikes of which are copied into the child processes with a certain 
  probability p. Every node the mip_generator is connected to receives a distinct
  child process as input, whose rate is p*r. The value of the pairwise correlation 
  coefficient of two child processes created by a MIP process equals p.

    
Parameters:
   The following parameters appear in the element's status dictionary:

   rate         double - Mean firing rate of the mother process in Hz
   p_copy       double - Copy probability
   mother_rng   rng    - Random number generator of mother process
   mother_seed  long   - Seed of RNG of mother process

Remarks:
   The MIP generator may emit more than one spike through a child process
   during a single time step, especially at high rates.  If this happens, 
   the generator does not actually send out n spikes.  Instead, it emits 
   a single spike with n-fold synaptic weight for the sake of efficiency.  
   Furthermore, note that as with the Poisson generator, different threads
   have their own copy of a MIP generator. By using the same mother_seed
   it is ensured that the mother process is identical for each of the 
   generators.
   
   IMPORTANT: The mother_seed of mpi_generator must be different from any
              seeds used for the global or thread-specific RNGs set in
	      the kernel.

Sends: SpikeEvent
              
References:
  [1] Alexandre Kuhn, Ad Aertsen, Stefan Rotter
      Higher-Order Statistics of Input Ensembles and the Response of Simple
      Model Neurons
      Neural Computation 15, 67-101 (2003)

Author: May 2006, Helias
SeeAlso: Device
*/

  /**
   * Class for MIP generator.
   * @todo Better handling of private random number generator, see #143.
   *       Most important: If RNG is changed in prototype by SetDefaults,
   *       then this is 
   */
  class mip_generator : public Node
  {

  public:
	 
    /** 
     * The generator is threaded, so the RNG to use is determined
     * at run-time, depending on thread. An additional RNG is used
     * for the mother process.
     */
    mip_generator();

    /**
     * Copy constructor. Called, when a new instance is created.
     * Needs to be overrwritten to initialize the random generator
     * for the mother process.
     */
    mip_generator(const mip_generator & rhs);

    bool has_proxies() const {return false;}


    using Node::event_hook;

    port check_connection(Connection&, port);
    
    void get_status(DictionaryDatum &) const;
    void set_status(const DictionaryDatum &) ;

  private:

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

    void update(Time const &, const long_t, const long_t);
    
    /**
     * @todo Should use binomial distribution
     */
    void event_hook(DSSpikeEvent&);

    // ------------------------------------------------------------
    
    /**
     * Store independent parameters of the model.
     * Mother RNG is a parameter since it can be changed. Not entirely in
     * keeping with persistence rules, since it changes state during 
     * updates. But okay in the sense that it thus is not reset on
     * ResetNetwork. Should go once we have proper global RNG scheme.
     */
    struct Parameters_ {
      double_t rate_;       //!< process rate in Hz
      double_t p_copy_;     //!< copy probability for each spike in the mother process
      ulong_t mother_seed_; //!< seed of the mother process
      librandom::RngPtr rng_; //!< random number generator for mother process
      
      Parameters_();  //!< Sets default parameter values
      Parameters_(const Parameters_&);

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

    struct Variables_ {
      librandom::PoissonRandomDev poisson_dev_;  //!< random deviate generator
    };
    
    // ------------------------------------------------------------

    StimulatingDevice<SpikeEvent> device_;
    Parameters_ P_;
    Variables_  V_;
    
  };

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

  inline
  void mip_generator::get_status(DictionaryDatum &d) const
  {
    P_.get(d);
    device_.get_status(d);
  }

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

    // We now know that ptmp is consistent. We do not write it back
    // to P_ before we are also sure that the properties to be set
    // in the parent class are internally consistent.
    device_.set_status(d);

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

} // namespace nest

#endif