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

/* ---------------------------------------------------------------- 
 * Default constructors defining default parameter
 * ---------------------------------------------------------------- */
    
nest::step_current_generator::Parameters_::Parameters_()
  : amp_times_(),  // ms
    amp_values_()  // pA
{}

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

void nest::step_current_generator::Parameters_::get(DictionaryDatum &d) const
{
  (*d)["amplitude_times"] = DoubleVectorDatum(new std::vector<double_t>(amp_times_));
  (*d)["amplitude_values"] = DoubleVectorDatum(new std::vector<double_t>(amp_values_));
}  

void nest::step_current_generator::Parameters_::set(const DictionaryDatum& d,
                                                    Buffers_& b)
{
  const bool ut = updateValue<std::vector<double_t> >(d, "amplitude_times", amp_times_);
  const bool uv = updateValue<std::vector<double_t> >(d, "amplitude_values", amp_values_);
  
  if ( ut xor uv )
    throw BadProperty("Amplitude times and values must be reset together.");
    
  if ( amp_times_.size() != amp_values_.size() )
    throw BadProperty("Amplitude times and values have to be the same size.");
    
  // ensure amp times are strictly monotonically increasing
  if ( !amp_times_.empty() )
  { 
    std::vector<double_t>::const_iterator prev = amp_times_.begin();
    for ( std::vector<double_t>::const_iterator next = prev + 1;
          next != amp_times_.end() ; ++next, ++prev )
      if ( *prev >= *next )
        throw BadProperty("Amplitude times must strictly increasing.");
  }
  
  if ( ut && uv )
    b.idx_ = 0;  // reset if we got new data
}


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

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

nest::step_current_generator::step_current_generator(const step_current_generator& n)
  : Node(n), 
    device_(n.device_),
    P_(n.P_)
{}


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

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

  device_.init_state(pr.device_);
}

void nest::step_current_generator::init_buffers_()
{ 
  device_.init_buffers();
  
  B_.idx_ = 0;
  B_.amp_ = 0;
}

void nest::step_current_generator::calibrate()
{
  device_.calibrate();
}


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

void nest::step_current_generator::update(Time const &origin, const long_t from, const long_t to)
{
  assert(P_.amp_times_.size() == P_.amp_values_.size());

  const long_t t0 = origin.get_steps();

  // Skip any times in the past. Since we must send events proactively,
  // idx_ must point to times in the future.
  const long_t first = t0 + from;
  while ( B_.idx_ < P_.amp_times_.size() && Time(Time::ms(P_.amp_times_[B_.idx_])).get_steps() <= first )
    ++B_.idx_;

  for ( long_t offs = from ; offs < to ; ++offs )
  {
    const long_t curr_time = t0 + offs;
    
    // Keep the amplitude up-to-date at all times.
    // We need to change the amplitude one step ahead of time, see comment
    // on class SimulatingDevice.
    if ( B_.idx_ < P_.amp_times_.size() && curr_time + 1 == Time(Time::ms(P_.amp_times_[B_.idx_])).get_steps() )
    {
      B_.amp_ = P_.amp_values_[B_.idx_];
      B_.idx_++;
    }
    
    // but send only if active
    if( device_.is_active(Time::step(curr_time)) )
    {
      CurrentEvent ce;
      ce.set_current(B_.amp_);
      network()->send(*this, ce, offs);
    }
  }
}