/*
 *  stdp_connection_facetshw_hom.h
 *
 *  This file is part of NEST
 *
 *  Copyright (C) 2004 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.
 *
 */

#ifndef STDP_CONNECTION_FACETSHW_HOM_H
#define STDP_CONNECTION_FACETSHW_HOM_H

/* BeginDocumentation
  Name: stdp_facetshw_synapse_hom - Synapse type for spike-timing dependent
   plasticity using homogeneous parameters, i.e. all synapses have the same parameters.

  Description:
   stdp_facetshw_synapse is a connector to create synapses with spike-timing
   dependent plasticity (as defined in [1]).
   This connector is a modified version of stdp_synapse.
   It includes constraints of the hardware developed in the FACETS (BrainScaleS) project [2,3],
   as e.g. 4-bit weight resolution, sequential updates of groups of synapses
   and reduced symmetric nearest-neighbor spike pairing scheme. For details see [3].
   The modified spike pairing scheme requires the calculation of tau_minus_
   within this synapse and not at the neuron site via Kplus_ like in stdp_connection_hom.

  Parameters:
   Common properties:
    tau_plus        double - Time constant of STDP window, causal branch in ms 
    tau_minus_stdp  double - Time constant of STDP window, anti-causal branch in ms 
    Wmax            double - Maximum allowed weight

    no_synapses                    long - total number of synapses
    synapses_per_driver            long - number of synapses updated at once
    driver_readout_time          double - time for processing of one synapse row (synapse line driver)
    readout_cycle_duration       double - duration between two subsequent updates of same synapse (synapse line driver)
    lookuptable_0          vector<long> - three look-up tables (LUT)
    lookuptable_1          vector<long>
    lookuptable_2          vector<long>
    configbit_0            vector<long> - configuration bits for evaluation function.
                                          For details see code in function eval_function_ and [4]
                                          (configbit[0]=e_cc, ..[1]=e_ca, ..[2]=e_ac, ..[3]=e_aa).
                                          Depending on these two sets of configuration bits
                                          weights are updated according LUTs (out of three: (1,0), (0,1), (1,1)).
                                          For (0,0) continue without reset.
    configbit_1            vector<long>
    reset_pattern          vector<long> - configuration bits for reset behaviour.
                                          Two bits for each LUT (reset causal and acausal).
                                          In hardware only (all false; never reset)
                                          or (all true; always reset) is allowed.

   Individual properties:
    a_causal     double - causal and anti-causal spike pair accumulations
    a_acausal    double
    a_thresh_th  double - two thresholds used in evaluation function.
                          No common property, because variation of analog synapse circuitry can be applied here
    a_thresh_tl  double
    synapse_id   long   - synapse ID, used to assign synapses to groups (synapse drivers)

  Notes:
   The synapse IDs are assigned to each synapse in an ascending order (0,1,2, ...) according their first
   presynaptic activity and is used to group synapses that are updated at once.
   It is possible to avoid activity dependent synapse ID assignments by manually setting the no_synapses
   and the synapse_id(s) before running the simulation.
   The weights will be discretized after the first presynaptic activity at a synapse.

  Transmits: SpikeEvent
   
  References:
   [1] Morrison, A., Diesmann, M., and Gerstner, W. (2008).
       Phenomenological models of synaptic plasticity based on
       spike-timing, Biol. Cybern., 98,459--478

   [2] Schemmel, J., Gruebl, A., Meier, K., and Mueller, E. (2006).
       Implementing synaptic plasticity in a VLSI spiking neural
       network model, In Proceedings of the 2006 International
       Joint Conference on Neural Networks, pp.1--6, IEEE Press

   [3] Pfeil, T., Potjans, T. C., Schrader, S., Potjans, W., Schemmel, J., Diesmann, M., & Meier, K. (2012).
       Is a 4-bit synaptic weight resolution enough? - 
       constraints on enabling spike-timing dependent plasticity in neuromorphic hardware.
       Front. Neurosci. 6 (90).

   [4] Friedmann, S. in preparation


  FirstVersion: July 2011
  Author: Thomas Pfeil (TP), Moritz Helias, Abigail Morrison
  SeeAlso: stdp_synapse, synapsedict, tsodyks_synapse, static_synapse
*/

#include "connection_het_wd.h"
#include "archiving_node.h"
#include <cmath>

namespace nest
{

  /**
   * Class containing the common properties for all synapses of type STDPFACETSHWConnectionHom.
   */
  class STDPFACETSHWHomCommonProperties : public CommonSynapseProperties
    {
      friend class STDPFACETSHWConnectionHom;

    public:

      /**
       * Default constructor.
       * Sets all property values to defaults.
       */
      STDPFACETSHWHomCommonProperties();

      /**
       * Get all properties and put them into a dictionary.
       */
      void get_status(DictionaryDatum & d) const;

      /**
       * Set properties from the values given in dictionary.
       */
      void set_status(const DictionaryDatum & d, ConnectorModel& cm);

      // overloaded for all supported event types
      void check_event(SpikeEvent&) {}

    private:
      /**
       * Calculate the readout cycle duration
       */
      void calc_readout_cycle_duration_();


      // data members common to all connections
      double_t tau_plus_;
      double_t tau_minus_;
      double_t Wmax_;
      double_t weight_per_lut_entry_;

      //STDP controller parameters
      long_t no_synapses_;
      long_t synapses_per_driver_;
      double_t driver_readout_time_;
      double_t readout_cycle_duration_;
      std::vector<long_t> lookuptable_0_; //TODO: TP: size in memory could be reduced
      std::vector<long_t> lookuptable_1_;
      std::vector<long_t> lookuptable_2_; //TODO: TP: to save memory one could introduce vector<bool> & BoolVectorDatum
      std::vector<long_t> configbit_0_;
      std::vector<long_t> configbit_1_;
      std::vector<long_t> reset_pattern_;
    };



  /**
   * Class representing an STDP connection with homogeneous parameters, i.e. parameters are the same for all synapses.
   */
  class STDPFACETSHWConnectionHom : public ConnectionHetWD
  {

  public:
  /**
   * Default Constructor.
   * Sets default values for all parameters. Needed by GenericConnectorModel.
   */
  STDPFACETSHWConnectionHom();

  /**
   * Copy constructor from a property object.
   * Needs to be defined properly in order for GenericConnector to work.
   */
  STDPFACETSHWConnectionHom(const STDPFACETSHWConnectionHom &);

  /**
   * Default Destructor.
   */
  virtual ~STDPFACETSHWConnectionHom() {}

  /*
   * This function calls check_connection on the sender and checks if the receiver
   * accepts the event type and receptor type requested by the sender.
   * Node::check_connection() will either confirm the receiver port by returning
   * true or false if the connection should be ignored.
   * We have to override the base class' implementation, since for STDP
   * connections we have to call register_stdp_connection on the target neuron
   * to inform the Archiver to collect spikes for this connection.
   *
   * \param s The source node
   * \param r The target node
   * \param receptor_type The ID of the requested receptor type
   * \param t_lastspike last spike produced by presynaptic neuron (in ms)
   */
  void check_connection(Node & s, Node & r, rport receptor_type, double_t t_lastspike);

  /**
   * Get all properties of this connection and put them into a dictionary.
   */
  void get_status(DictionaryDatum & d) const;

  /**
   * Set properties of this connection from the values given in dictionary.
   */
  void set_status(const DictionaryDatum & d, ConnectorModel &cm);

  /**
   * Set properties of this connection from position p in the properties
   * array given in dictionary.
   */
  void set_status(const DictionaryDatum & d, index p, ConnectorModel &cm);

  /**
   * Create new empty arrays for the properties of this connection in the given
   * dictionary. It is assumed that they are not existing before.
   */
  void initialize_property_arrays(DictionaryDatum & d) const;

  /**
   * Append properties of this connection to the given dictionary. If the
   * dictionary is empty, new arrays are created first.
   */
  void append_properties(DictionaryDatum & d) const;

  /**
   * Send an event to the receiver of this connection.
   * \param e The event to send
   * \param t_lastspike Point in time of last spike sent.
   */
  void send(Event& e, double_t t_lastspike, STDPFACETSHWHomCommonProperties &);

  // overloaded for all supported event types
  using Connection::check_event;
  void check_event(SpikeEvent&) {}

 private:
  bool eval_function_(double_t a_causal, double_t a_acausal, double_t a_thresh_th, double_t a_thresh_tl, std::vector<long_t> configbit);

  // transformation biological weight <-> discrete weight (represented in index of look-up table)
  uint_t weight_to_entry_(double_t weight, double_t weight_per_lut_entry);
  double_t entry_to_weight_(uint_t discrete_weight, double_t weight_per_lut_entry);

  uint_t lookup_(uint_t discrete_weight_, std::vector<long_t> table);

  // data members of each connection
  double_t a_causal_;
  double_t a_acausal_;
  double_t a_thresh_th_;
  double_t a_thresh_tl_;

  bool init_flag_;
  long_t synapse_id_;
  double_t next_readout_time_;
  uint_t discrete_weight_; //TODO: TP: only needed in send, move to common properties or "static"?
  };

inline
bool STDPFACETSHWConnectionHom::eval_function_(double_t a_causal, double_t a_acausal, double_t a_thresh_th, double_t a_thresh_tl, std::vector<long_t> configbit)
{
  // compare charge on capacitors with thresholds and return evaluation bit
  return (a_thresh_tl + configbit[2] * a_causal + configbit[1] * a_acausal)
          / (1 + configbit[2] + configbit[1])
          > (a_thresh_th + configbit[0] * a_causal + configbit[3] * a_acausal)
          / (1 + configbit[0] + configbit[3]);
}

inline
uint_t STDPFACETSHWConnectionHom::weight_to_entry_(double_t weight, double_t weight_per_lut_entry)
{
  // returns the discrete weight in terms of the look-up table index
  return round(weight / weight_per_lut_entry);
}

inline
double_t STDPFACETSHWConnectionHom::entry_to_weight_(uint_t discrete_weight, double_t weight_per_lut_entry)
{
  // returns the continuous weight
  return discrete_weight * weight_per_lut_entry;
}

inline
uint_t STDPFACETSHWConnectionHom::lookup_(uint_t discrete_weight_, std::vector<long_t> table)
{
  // look-up in table
  return table[discrete_weight_];
}

inline
  void STDPFACETSHWConnectionHom::check_connection(Node & s, Node & r, rport receptor_type, double_t t_lastspike)
{
  ConnectionHetWD::check_connection(s, r, receptor_type, t_lastspike);
  r.register_stdp_connection(t_lastspike - Time(Time::step(delay_)).get_ms());
}

/**
 * Send an event to the receiver of this connection.
 * \param e The event to send
 * \param p The port under which this connection is stored in the Connector.
 * \param t_lastspike Time point of last spike emitted
 */
inline
void STDPFACETSHWConnectionHom::send(Event& e, double_t t_lastspike, STDPFACETSHWHomCommonProperties &cp)
{
  // synapse STDP dynamics

  double_t t_spike = e.get_stamp().get_ms();

  //init the readout time
  if(init_flag_ == false){
    synapse_id_ = cp.no_synapses_;
    ++cp.no_synapses_;
    cp.calc_readout_cycle_duration_();
    next_readout_time_ = int(synapse_id_ / cp.synapses_per_driver_) * cp.driver_readout_time_;
    std::cout << "init synapse " << synapse_id_ << " - first readout time: " << next_readout_time_ << std::endl;
    init_flag_ = true;
  }

  //STDP controller is processing this synapse (synapse driver)?
  if(t_spike > next_readout_time_)
  {
    //transform weight to discrete representation
    discrete_weight_ = weight_to_entry_(weight_, cp.weight_per_lut_entry_);

    //obtain evaluation bits
    bool eval_0 = eval_function_(a_causal_, a_acausal_, a_thresh_th_, a_thresh_tl_, cp.configbit_0_);
    bool eval_1 = eval_function_(a_causal_, a_acausal_, a_thresh_th_, a_thresh_tl_, cp.configbit_1_);

    //select LUT, update weight and reset capacitors
    if(eval_0 == true && eval_1 == false){
      discrete_weight_ = lookup_(discrete_weight_, cp.lookuptable_0_);
      if(cp.reset_pattern_[0]) a_causal_ = 0;
      if(cp.reset_pattern_[1]) a_acausal_ = 0;
    } else if(eval_0 == false && eval_1 == true){
      discrete_weight_ = lookup_(discrete_weight_, cp.lookuptable_1_);
      if(cp.reset_pattern_[2]) a_causal_ = 0;
      if(cp.reset_pattern_[3]) a_acausal_ = 0;
    } else if(eval_0 == true && eval_1 == true){
      discrete_weight_ = lookup_(discrete_weight_, cp.lookuptable_2_);
      if(cp.reset_pattern_[4]) a_causal_ = 0;
      if(cp.reset_pattern_[5]) a_acausal_ = 0;
    }
    //do nothing, if eval_0 == false and eval_1 == false

    while(t_spike > next_readout_time_){
      next_readout_time_ += cp.readout_cycle_duration_;
    }
    //std::cout << "synapse " << synapse_id_ << " updated at " << t_spike << ", next readout time: " << next_readout_time_ << std::endl;

    //back-transformation to continuous weight space
    weight_ = entry_to_weight_(discrete_weight_, cp.weight_per_lut_entry_);
  }

  // t_lastspike_ = 0 initially

  double_t dendritic_delay = Time(Time::step(delay_)).get_ms();

  //get spike history in relevant range (t1, t2] from post-synaptic neuron
  std::deque<histentry>::iterator start;
  std::deque<histentry>::iterator finish;
  target_->get_history(t_lastspike - dendritic_delay, t_spike - dendritic_delay,
			       &start, &finish);
  //facilitation due to post-synaptic spikes since last pre-synaptic spike
  double_t minus_dt = 0;
  double_t plus_dt = 0;

  if(start != finish) //take only first postspike after last prespike
  {
    minus_dt = t_lastspike - (start->t_ + dendritic_delay);
  }

  if(start != finish) //take only last postspike before current spike
  {
    --finish;
    plus_dt = (finish->t_ + dendritic_delay) - t_spike;
  }

  if(minus_dt != 0){
    a_causal_ += std::exp(minus_dt / cp.tau_plus_);
  }

  if(plus_dt != 0){
    a_acausal_ += std::exp(plus_dt / cp.tau_minus_);
  }

  e.set_receiver(*target_);
  e.set_weight(weight_);
  e.set_delay(delay_);
  e.set_rport(rport_);
  e();

  }
} // of namespace nest

#endif // of #ifndef STDP_CONNECTION_HOM_H