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

#include <numeric>

nest::spike_detector::spike_detector()
        : Node(),
          device_(*this, RecordingDevice::SPIKE_DETECTOR, "gdf", true, true),  // record time and gid
          user_set_precise_times_(false)
{}

nest::spike_detector::spike_detector(const spike_detector &n)
        : Node(n),
          device_(*this, n.device_),
          user_set_precise_times_(n.user_set_precise_times_)
{}

void nest::spike_detector::init_state_(const Node& np)
{
  const spike_detector& sd = dynamic_cast<const spike_detector&>(np);
  device_.init_state(sd.device_);
  init_buffers_();
}

void nest::spike_detector::init_buffers_()
{
  device_.init_buffers();

  std::vector<std::vector<Event*> > tmp(2, std::vector<Event*>());
  B_.spikes_.swap(tmp);
}

void nest::spike_detector::calibrate()
{
  if (!user_set_precise_times_ && network()->get_off_grid_communication())
  {
    device_.set_precise(true, 15);
      
    network()->message(SLIInterpreter::M_INFO, "spike_detector::calibrate",
		       String::compose("Precise neuron models exist: the property precise_times "
				       "of the %1 with gid %2 has been set to true, precision has "
				       "been set to 15.", get_name(), get_gid()));
  }

  device_.calibrate();
}

void nest::spike_detector::update(Time const&, const long_t, const long_t)
{
  for(std::vector<Event*>::iterator 
      e = B_.spikes_[network()->read_toggle()].begin();
      e != B_.spikes_[network()->read_toggle()].end(); ++e)
  {
    assert(*e != 0);
    device_.record_event(**e);
    delete *e;
  }
  
  // do not use swap here to clear, since we want to keep the reserved()
  // memory for the next round
  B_.spikes_[network()->read_toggle()].clear();  
} 

void nest::spike_detector::get_status(DictionaryDatum &d) const
{
  // get the data from the device
  device_.get_status(d);

  // if we are the device on thread 0, also get the data from the
  // siblings on other threads
  if (get_thread() == 0)
  {
    const SiblingContainer* siblings = network()->get_thread_siblings(get_gid());
    std::vector<Node*>::const_iterator sibling;
    for (sibling = siblings->begin() + 1; sibling != siblings->end(); ++sibling)
      (*sibling)->get_status(d);
  }
}

void nest::spike_detector::set_status(const DictionaryDatum &d)
{
  if (d->known(names::precise_times))
    user_set_precise_times_ = true;

  device_.set_status(d);
}

void nest::spike_detector::handle(SpikeEvent & e)
{
  // accept spikes only if detector was active when spike was
  // emitted
  if ( device_.is_active(e.get_stamp()) )
  {
    assert(e.get_multiplicity() > 0);

    long_t dest_buffer;
    if (network()->get_model_of_gid(e.get_sender_gid())->has_proxies())
      dest_buffer = network()->read_toggle();   // events from central queue
    else
      dest_buffer = network()->write_toggle();  // locally delivered events

    for (int_t i = 0; i < e.get_multiplicity(); ++i)
    {
      // We store the complete events
      Event *event = e.clone();
      B_.spikes_[dest_buffer].push_back(event);
    }
  }
}