/*
 *  ac_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 <cmath>

#include "ac_generator.h"
#include "network.h"
#include "dict.h"
#include "integerdatum.h"
#include "doubledatum.h"
#include "dictutils.h"
#include "numerics.h"


/* ---------------------------------------------------------------- 
 * Default constructors defining default parameters and state
 * ---------------------------------------------------------------- */

nest::ac_generator::Parameters_::Parameters_()
  : amp_    (0.0),  // pA
    offset_ (0.0),  // pA
    freq_   (0.0),  // Hz
    phi_deg_(0.0)   // degree
{}

nest::ac_generator::State_::State_()
  : y_0_ (0.0),
    y_1_ (0.0)  // pA
{}


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

void nest::ac_generator::Parameters_::get(DictionaryDatum &d) const
{
  (*d)[names::amplitude] = amp_;
  (*d)[names::offset   ] = offset_;
  (*d)[names::phase    ] = phi_deg_;
  (*d)[names::frequency] = freq_;
}
 
void nest::ac_generator::State_::get(DictionaryDatum &d) const
{
  (*d)["y_0"] = y_0_;
  (*d)["y_1"] = y_1_;
}  

void nest::ac_generator::Parameters_::set(const DictionaryDatum& d)
{
  updateValue<double_t>(d, names::amplitude, amp_);
  updateValue<double_t>(d, names::offset   , offset_);
  updateValue<double_t>(d, names::frequency, freq_);
  updateValue<double_t>(d, names::phase    , phi_deg_);
}


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

nest::ac_generator::ac_generator()
  : Node(),
    device_(), 
    P_(),
    S_()
{}

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


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

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

  device_.init_state(pr.device_);
  S_ = pr.S_;
}

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

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

  const double_t h = Time::get_resolution().get_ms();
  const double_t t = network()->get_time().get_ms();

  // scale Hz to ms
  const double_t omega   = 2.0 * numerics::pi * P_.freq_ / 1000.0;       
  const double_t phi_rad = P_.phi_deg_ * 2.0 * numerics::pi / 360.0;

  // initial state
  S_.y_0_ = P_.amp_ * std::cos(omega * t + phi_rad);
  S_.y_1_ = P_.amp_ * std::sin(omega * t + phi_rad);

  // matrix elements
  V_.A_00_ =  std::cos(omega * h);
  V_.A_01_ = -std::sin(omega * h);
  V_.A_10_ =  std::sin(omega * h);
  V_.A_11_ =  std::cos(omega * h);
}

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

  CurrentEvent ce;
  for ( long_t lag = from ; lag < to ; ++lag )
    if( device_.is_active(Time::step(start+lag) ))
      {
        const double_t y_0 = S_.y_0_;
        S_.y_0_ = V_.A_00_ * y_0 + V_.A_01_ * S_.y_1_;
        S_.y_1_ = V_.A_10_ * y_0 + V_.A_11_ * S_.y_1_;
        ce.set_current(S_.y_1_ + P_.offset_);
        network()->send(*this, ce, lag);
      }
}