/*
 *  spin_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 "spin_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::spin_detector::spin_detector()
        : Node(),
          device_(*this, RecordingDevice::SPIN_DETECTOR, "gdf", true, true, true),  // record time, gid and weight (used for spin)
	  last_in_gid_(0),
	  t_last_in_spike_(Time::neg_inf()),
          user_set_precise_times_(false)
{}

nest::spin_detector::spin_detector(const spin_detector &n)
        : Node(n),
          device_(*this, n.device_),
	  last_in_gid_(0),
	  t_last_in_spike_(Time::neg_inf()),          // mark as not initialized
          user_set_precise_times_(n.user_set_precise_times_)
{}

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

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

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

void nest::spin_detector::calibrate()
{
  if (!user_set_precise_times_ && network()->get_off_grid_communication())
  {
    device_.set_precise(true, 15);
      
    network()->message(SLIInterpreter::M_INFO, "spin_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::spin_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::spin_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::spin_detector::set_status(const DictionaryDatum &d)
{
  if (d->known(names::precise_times))
    user_set_precise_times_ = true;

  device_.set_status(d);
}


void nest::spin_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

    
    // The following logic implements the decoding
    // A single spike signals a transition to 0 state, two spikes in same time step
    // signal the transition to 1 state.
    //
    // Remember the global id of the sender of the last spike being received
    // this assumes that several spikes being sent by the same neuron in the same time step
    // are received consecutively or are conveyed by setting the multiplicity accordingly.
  
    long_t m = e.get_multiplicity();
    index gid = e.get_sender_gid();
    const Time &t_spike = e.get_stamp();

    if (m == 1)
    { //multiplicity == 1, either a single 1->0 event or the first or second of a pair of 0->1 events
      if (gid == last_in_gid_ && t_spike == t_last_in_spike_)
      {
	// received twice the same gid, so transition 0->1
	// revise the last event written to the buffer
	(*(B_.spikes_[dest_buffer].end()-1))->set_weight(1.);
      }
      else
      {
	// count this event negatively, assuming it comes as single event
	// transition 1->0
	Event *event = e.clone();
	// assume it will stay alone, so meaning a down tansition
	event->set_weight(0);
	B_.spikes_[dest_buffer].push_back(event);
      }
    }
    else // multiplicity != 1
      if (m == 2)
      {
	// count this event positively, transition 0->1
	Event *event = e.clone();
	event->set_weight(1.);
	B_.spikes_[dest_buffer].push_back(event);
      }
    
    last_in_gid_ = gid;
    t_last_in_spike_ = t_spike;
  }

}