/*
 *  synth_integrator.cpp
 *
 *  This file is part of NEST
 *
 *  Copyright (C) 2004-2008 by
 *  The NEST Initiative
 *
 *  See the file AUTHORS for details.
 *
 *  Permission is granted to compile and modify
 *  this file for non-commercial use.
 *  See the file LICENSE for details.
 *
 *  (C) 2010 Jan Morén 
 */

//#include "scmodules_names.h"
#include "exceptions.h"
#include "synth_integrator.h"
#include "network.h"
#include "dict.h"
#include "integerdatum.h"
#include "doubledatum.h"
#include "dictutils.h"
#include "numerics.h"
//#include "analog_data_logger_impl.h"
#include "universal_data_logger_impl.h"

#include <limits>

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

nest::RecordablesMap<nest::synth_integrator> nest::synth_integrator::recordablesMap_;

using namespace nest;

namespace nest {
/*
   * Override the create() method with one call to RecordablesMap::insert_() 
   * for each quantity to be recorded.
   */
    template <>
    void RecordablesMap<nest::synth_integrator>::create()
    {
	// use standard names whereever you can for consistency!
	//insert_(nest::names::V_m, &nest::synth_integrator::get_V_m_);
	insert_("weighted_spikes_ex", &nest::synth_integrator::get_weighted_spikes_ex_);
	insert_("weighted_spikes_in", &nest::synth_integrator::get_weighted_spikes_in_);
    }
}
/* ---------------------------------------------------------------- 
 * Default constructors defining default parameters and state
 * ---------------------------------------------------------------- */

nest::synth_integrator::Parameters_::Parameters_()
    : Smax_	    ( 1000.0    ),	// integrated value for 1/ms spike
    Gamma_	    (1.0    ),		//  gamma scale
    Reset_	    (10.0    )		//  reset input threshold
{}

nest::synth_integrator::State_::State_()
    : acc_   (0.0)
{}

/* ---------------------------------------------------------------- 
 * Paramater and state extractions and manipulation functions
 * ---------------------------------------------------------------- */

void nest::synth_integrator::Parameters_::get(DictionaryDatum &d) const
{

    def<double>(d, names::Smax, Smax_);
    def<double>(d, names::Gamma, Gamma_);
    def<double>(d, names::Reset, Reset_);
}

void nest::synth_integrator::Parameters_::set(const DictionaryDatum& d)
{
    updateValue<double>(d, names::Gamma, Gamma_);
    updateValue<double>(d, names::Reset, Reset_);
    updateValue<double>(d, names::Smax, Smax_);
}

void nest::synth_integrator::State_::get(DictionaryDatum &d, const Parameters_& p) const
{
    //  def<double>(d, nest::names::V_m, y3_ + p.U0_); // Membrane potential
}

void nest::synth_integrator::State_::set(const DictionaryDatum& d, const Parameters_& p)
{
    //if ( updateValue<double>(d, nest::names::V_m, y3_) )
    //  y3_ -= p.U0_;
}
nest::synth_integrator::Buffers_::Buffers_(synth_integrator& n)
  : logger_(n)
{}

nest::synth_integrator::Buffers_::Buffers_(const Buffers_&, synth_integrator& n)
  : logger_(n)
{}
/* ---------------------------------------------------------------- 
 * Default and copy constructor for node
 * ---------------------------------------------------------------- */

    nest::synth_integrator::synth_integrator()
: Archiving_Node(), 
    P_(), 
    S_(),
    B_(*this)
{
  recordablesMap_.create();
}

    nest::synth_integrator::synth_integrator(const synth_integrator& n)
: Archiving_Node(n), 
    P_(n.P_), 
    S_(n.S_),
    B_(n.B_, *this)
{}

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

void nest::synth_integrator::init_node_(const Node& proto)
{
    const synth_integrator& pr = downcast<synth_integrator>(proto);
    P_ = pr.P_;
    S_ = pr.S_;
}

void nest::synth_integrator::init_state_(const Node& proto)
{
    const synth_integrator& pr = downcast<synth_integrator>(proto);
    S_ = pr.S_;
}

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

void nest::synth_integrator::calibrate()
{
    B_.logger_.init();  // ensures initialization in case mm connected after Simulate
    V_.t_res = Time::get_resolution().get_ms(); 
}

/* ---------------------------------------------------------------- 
 * Update and spike handling functions
 */

void nest::synth_integrator::update(Time const & origin, const long_t from, const long_t to)
{
    assert(to >= 0 && (delay) from < Scheduler::get_min_delay());
    assert(from < to);

    double rn, prop;

    double resetc = 0.0;

    for ( long_t lag = from ; lag < to ; ++lag )
    {

	V_.weighted_spikes_ex_ = B_.ex_spikes_.get_value(lag);	
	V_.weighted_spikes_in_ = B_.in_spikes_.get_value(lag);

	S_.acc_ += V_.weighted_spikes_ex_;
	

	resetc += (-V_.weighted_spikes_in_);
	if (resetc > P_.Reset_)
	    S_.acc_ = 0.0;

	prop = pow((S_.acc_/(P_.Smax_)),(1.0/P_.Gamma_));
	rn = random()/(RAND_MAX+1.0)/V_.t_res;

	if (rn<= prop) { 
	    SpikeEvent se;
	    network()->send(*this, se, lag);
	}
    
	B_.logger_.record_data(origin.get_steps() + lag);
    }                           
}


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

    //printf ("\tSynapse: %d\n", e.get_rport());
    if (e.get_rport()==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 if (e.get_weight() < 0.0)
	    B_.in_spikes_.add_value(e.get_rel_delivery_steps(network()->get_slice_origin()),
		    e.get_weight() * e.get_multiplicity() );
    }
}

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

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

// namespace