/*
 *  sli_neuron.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 "exceptions.h"
#include "sli_neuron.h"
#include "network.h"
#include "dict.h"
#include "integerdatum.h"
#include "doubledatum.h"
#include "dictutils.h"
#include "numerics.h"
#include "universal_data_logger_impl.h"
#include "dictstack.h"

#include <limits>

/* ---------------------------------------------------------------- 
 * Recordables map
 * ---------------------------------------------------------------- */

nest::RecordablesMap<nest::sli_neuron> nest::sli_neuron::recordablesMap_;

namespace nest
{
  // Override the create() method with one call to RecordablesMap::insert_() 
  // for each quantity to be recorded.
  template <>
  void RecordablesMap<sli_neuron>::create()
  {
    // use standard names whereever you can for consistency!
    insert_(names::V_m, &sli_neuron::get_V_m_);
  }
}

nest::sli_neuron::Buffers_::Buffers_(sli_neuron &n)
  : logger_(n)
{}

nest::sli_neuron::Buffers_::Buffers_(const Buffers_ &, sli_neuron &n)
  : logger_(n)
{}

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

nest::sli_neuron::sli_neuron()
  : Archiving_Node(),
    state_(new Dictionary()),
    B_(*this)
{
  // We add empty defaults for /calibrate and /update, so that the uninitialised node runs without errors.
  state_->insert(names::calibrate,new ProcedureDatum());
  state_->insert(names::update,new ProcedureDatum());
  recordablesMap_.create();
}

nest::sli_neuron::sli_neuron(const sli_neuron& n)
  : Archiving_Node(n),
    state_(new Dictionary(*n.state_)),
    B_(n.B_, *this)
{
  init_state_(n);
}

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

void nest::sli_neuron::init_state_(const Node& proto)
{
  const sli_neuron& pr = downcast<sli_neuron>(proto);
  state_= DictionaryDatum(new Dictionary(*pr.state_));
}

void nest::sli_neuron::init_buffers_()
{
  B_.ex_spikes_.clear();       // includes resize
  B_.in_spikes_.clear();       // includes resize
  B_.currents_.clear();        // includes resize
  B_.logger_.reset(); // includes resize
  Archiving_Node::clear_history();
}



void nest::sli_neuron::calibrate()
{
  B_.logger_.init();

  bool terminate=false;

  if(!state_->known(names::calibrate))
    {
      std::string msg=String::compose("Node %1 has no /calibrate function in its status dictionary.",get_gid());
      net_->message(SLIInterpreter::M_ERROR,"sli_neuron::calibrate",msg.c_str());
      terminate=true;
    }

  if(! state_->known(names::update))
    {
      std::string msg=String::compose("Node %1 has no /update function in its status dictionary. Terminating.",get_gid());
      net_->message(SLIInterpreter::M_ERROR,"sli_neuron::calibrate",msg.c_str());
      terminate=true;
    }

  if(terminate)
    {
      net_->terminate();
      net_->message(SLIInterpreter::M_ERROR,"sli_neuron::calibrate","Terminating.");
      return;
    }

  network()->execute_sli_protected(state_, names::calibrate_node);   // call interpreter
}

/* ---------------------------------------------------------------- 
 * Update and spike handling functions
 */
 
void nest::sli_neuron::update(Time const & origin, const long_t from, const long_t to)
{
  assert(to >= 0 && (delay) from < Scheduler::get_min_delay());
  assert(from < to);
  (*state_)[names::t_origin]=origin.get_steps();

  if (state_->known(names::error))
    {
      std::string msg=String::compose("Node %1 still has its error state set.",get_gid());
      net_->message(SLIInterpreter::M_ERROR,"sli_neuron::update",msg.c_str());
      net_->message(SLIInterpreter::M_ERROR,"sli_neuron::update","Please check /calibrate and /update for errors");
      net_->terminate();
      return;
    }

  for ( long_t lag = from ; lag < to ; ++lag )
  {
    (*state_)[names::in_spikes]=B_.in_spikes_.get_value(lag); // in spikes arriving at right border
    (*state_)[names::ex_spikes]=B_.ex_spikes_.get_value(lag); // ex spikes arriving at right border
    (*state_)[names::currents]=B_.currents_.get_value(lag);   
    (*state_)[names::t_lag]=lag;

    network()->execute_sli_protected(state_, names::update_node);   // call interpreter

    bool spike_emission= false;
    if (state_->known(names::spike))
      spike_emission=(*state_)[names::spike];

    // threshold crossing
    if (spike_emission)
    {
      set_spiketime(Time::step(origin.get_steps()+lag+1));
      SpikeEvent se;
      network()->send(*this, se, lag);
    }

    B_.logger_.record_data(origin.get_steps()+lag);
  }  
}                           
                     

void nest::sli_neuron::handle(SpikeEvent & e)
{
  assert(e.get_delay() > 0);

  if(e.get_weight() > 0.0)
    B_.ex_spikes_.add_value(e.get_rel_delivery_steps(network()->get_slice_origin()),
                            e.get_weight() * e.get_multiplicity() );
  else    
    B_.in_spikes_.add_value(e.get_rel_delivery_steps(network()->get_slice_origin()),
                            e.get_weight() * e.get_multiplicity() );
}

void nest::sli_neuron::handle(CurrentEvent& e)
{
  assert(e.get_delay() > 0);

  const double_t I = e.get_current();
  const double_t w = e.get_weight();

  // add weighted current; HEP 2002-10-04
  B_.currents_.add_value(e.get_rel_delivery_steps(network()->get_slice_origin()), 
		                     w * I);
}

void nest::sli_neuron::handle(DataLoggingRequest& e)
{
  B_.logger_.handle(e);
}