/*
 *  noise_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 "noise_generator.h"
#include "network.h"
#include "dict.h"
#include "integerdatum.h"
#include "doubledatum.h"
#include "dictutils.h"

/* ---------------------------------------------------------------- 
 * Default constructors defining default parameter
 * ---------------------------------------------------------------- */
    
nest::noise_generator::Parameters_::Parameters_()
  : mean_(0.0),  // pA
    std_(0.0),   // pA / sqrt(s)
    dt_(Time::ms(1.0)),
    num_targets_(0)
{}

nest::noise_generator::Parameters_::Parameters_(const Parameters_& p)
  : mean_(p.mean_),
    std_(p.std_),
    dt_(p.dt_),
    num_targets_(0)  // we do not copy connections
{
  // do not check validity of dt_ here, otherwise we cannot copy
  // to temporary in set(); see node copy c'tor
  dt_.calibrate();
}


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

void nest::noise_generator::Parameters_::get(DictionaryDatum &d) const
{
  (*d)[names::mean] = mean_;
  (*d)[names::std ] = std_;
  (*d)[names::dt]   = dt_.get_ms();
}  

void nest::noise_generator::Parameters_::set(const DictionaryDatum& d,
                                             const noise_generator& n)
{
  updateValue<double_t>(d, names::mean, mean_);
  updateValue<double_t>(d, names::std , std_);
  double_t dt;
  if ( updateValue<double_t>(d, names::dt, dt) )
    dt_ = Time::ms(dt);
  
  if ( std_ < 0 )
    throw BadProperty("The standard deviation cannot be negative.");
    
  if ( !dt_.is_step() )
    throw StepMultipleRequired(n.get_name(), names::dt, dt_);
}


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

nest::noise_generator::noise_generator()
  : Node(),
    device_(), 
    P_()
{
  if ( !P_.dt_.is_step() )
    throw InvalidDefaultResolution(get_name(), names::dt, P_.dt_);
}

nest::noise_generator::noise_generator(const noise_generator& n)
  : Node(n), 
    device_(n.device_),
    P_(n.P_)
{
  if ( !P_.dt_.is_step() )
    throw InvalidTimeInModel(get_name(), names::dt, P_.dt_);
}


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

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

  device_.init_state(pr.device_);
}

void nest::noise_generator::init_buffers_()
{ 
  device_.init_buffers();
  
  B_.next_step_ = 0;
  B_.amps_.clear();
  B_.amps_.resize(P_.num_targets_, 0.0);
}

void nest::noise_generator::calibrate()
{
  device_.calibrate();
  if ( P_.num_targets_ != B_.amps_.size() )
  {
    network()->message(SLIInterpreter::M_INFO, "noise_generator::calibrate()",
      "The number of targets has changed, drawing new amplitudes.");
    init_buffers_();
  }

  V_.dt_steps_ = P_.dt_.get_steps();
}


/* ---------------------------------------------------------------- 
 * Update function and event hook
 * ---------------------------------------------------------------- */


nest::port nest::noise_generator::check_connection(Connection& c, port receptor_type)
{
    DSCurrentEvent e;
    e.set_sender(*this);
    c.check_event(e);
    const port receptor = c.get_target()->connect_sender(e, receptor_type);
    ++P_.num_targets_;
    return receptor;
}

//
// Time Evolution Operator
//
void nest::noise_generator::update(Time const &origin, const long_t from, const long_t to)
{
  const long_t start = origin.get_steps();

  for ( long_t offs = from ; offs < to ; ++offs )
  {
    const long_t now = start + offs;
    
    if ( !device_.is_active(Time::step(now)) )
      continue;
    
    // >= in case we woke from inactivity  
    if( now >= B_.next_step_ )
    {
      // compute new currents
      for ( AmpVec_::iterator it = B_.amps_.begin() ;
            it != B_.amps_.end() ; ++it )
        *it = P_.mean_ + P_.std_ * V_.normal_dev_(net_->get_rng(get_thread()));

      // use now as reference, in case we woke up from inactive period
      B_.next_step_ = now + V_.dt_steps_;
    }
    
    DSCurrentEvent ce;
    network()->send(*this, ce, offs);
  }
}

void nest::noise_generator::event_hook(DSCurrentEvent& e)
{
  // get port number
  const port prt = e.get_port();

  // we handle only one port here, get reference to vector elem
  assert(0 <= prt && static_cast<size_t>(prt) < B_.amps_.size());

  e.set_current(B_.amps_[prt]);
  e.get_receiver().handle(e);
}