/*
 *  mip_generator.cpp
 *
 *  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 "mip_generator.h"
#include "network.h"
#include "dict.h"
#include "random_datums.h"
#include "dictutils.h"
#include "exceptions.h"
#include "gslrandomgen.h"

/* ---------------------------------------------------------------- 
 * Default constructors defining default parameter
 * ---------------------------------------------------------------- */
    
nest::mip_generator::Parameters_::Parameters_()
  : rate_  (0.0),  // Hz
    p_copy_(1.0),
    mother_seed_(0)
{
  rng_ = librandom::RandomGen::create_knuthlfg_rng(mother_seed_);
}

nest::mip_generator::Parameters_::Parameters_(const Parameters_& p)
  : rate_  (p.rate_),
    p_copy_(p.p_copy_),
    mother_seed_(p.mother_seed_)
{
  rng_ = p.rng_->clone(p.mother_seed_);  // deep copy of random number generator
}

/* ---------------------------------------------------------------- 
 * Parameter extraction and manipulation functions
 * ---------------------------------------------------------------- */

void nest::mip_generator::Parameters_::get(DictionaryDatum &d) const
{
  (*d)[names::rate]   = rate_;
  (*d)[names::p_copy] = p_copy_;
  (*d)[names::mother_seed] = mother_seed_;
}  

void nest::mip_generator::Parameters_::set(const DictionaryDatum& d)
{
  updateValue<double_t>(d, names::rate,   rate_);
  updateValue<double_t>(d, names::p_copy, p_copy_);
   
  if ( rate_ < 0 )
    throw BadProperty("Rate must be non-negative.");

  if ( p_copy_ < 0 || p_copy_ > 1 )
    throw BadProperty("Copy probability must be in [0, 1].");

  bool reset_rng = updateValue<librandom::RngPtr>(d, names::mother_rng, rng_);

  // order important to avoid short-circuitung
  reset_rng = updateValue<long_t>(d, names::mother_seed, mother_seed_) || reset_rng;
  
  if ( reset_rng )
    rng_->seed(mother_seed_);
}

/* ---------------------------------------------------------------- 
 * Default and copy constructor for node
 * ---------------------------------------------------------------- */

nest::mip_generator::mip_generator()
  : Node(),
    device_(), 
    P_()
{}

nest::mip_generator::mip_generator(const mip_generator& n)
  : Node(n), 
    device_(n.device_),
    P_(n.P_) // also causes deep copy of random nnumber generator
{}


/* ---------------------------------------------------------------- 
 * Node initialization functions
 * ---------------------------------------------------------------- */

void nest::mip_generator::init_state_(const Node& proto)
{ 
  const mip_generator& pr = downcast<mip_generator>(proto);

  device_.init_state(pr.device_);
}

void nest::mip_generator::init_buffers_()
{ 
  device_.init_buffers();
}

void nest::mip_generator::calibrate()
{
  device_.calibrate();

  // rate_ is in Hz, dt in ms, so we have to convert from s to ms
  V_.poisson_dev_.set_lambda(Time::get_resolution().get_ms() * P_.rate_ * 1e-3);
}


/* ---------------------------------------------------------------- 
 * Other functions
 * ---------------------------------------------------------------- */

void nest::mip_generator::update(Time const & T, const long_t from, const long_t to)
{
  assert(to >= 0 && (delay) from < Scheduler::get_min_delay());
  assert(from < to);

  for ( long_t lag = from ; lag < to ; ++lag )
  {
    if ( !device_.is_active(T) || P_.rate_ <= 0 )
      return; // no spikes to be generated
    
    // generate spikes of mother process for each time slice
    ulong_t n_mother_spikes = V_.poisson_dev_.uldev(P_.rng_);

    if ( n_mother_spikes )
    {
      DSSpikeEvent se;

      se.set_multiplicity(n_mother_spikes);
      network()->send(*this, se, lag);
    }
  }
}

void nest::mip_generator::event_hook(DSSpikeEvent& e)
{
  // note: event_hook() receives a reference of the spike event that
  // was originally created in the update function. there we set
  // the multiplicty to store the number of mother spikes. the *same*
  // reference will be delivered multiple times to the event hook,
  // once for every receiver. when calling handle() of the receiver
  // above, we need to change the multiplicty to the number of copied
  // child process spikes, so afterwards it needs to be reset to correctly 
  // store the number of mother spikes again during the next call of event_hook().
  // reichert

  librandom::RngPtr rng = net_->get_rng(get_thread());
  ulong_t n_mother_spikes = e.get_multiplicity();
  ulong_t n_spikes = 0;

  for (ulong_t n = 0; n < n_mother_spikes; n++)
  {
    if ( rng->drand() < P_.p_copy_ )
      n_spikes++;
  }

  if (n_spikes > 0)
  {
    e.set_multiplicity(n_spikes);
    e.get_receiver().handle(e);
  }

  e.set_multiplicity(n_mother_spikes);
}