/*
 *       File name:  libcn/base-neuron.hh
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 *                   building on original work by Thomas Nowotny <tnowotny@ucsd.edu>
 * Initial version:  2009-03-31
 *
 *         Purpose:  neuron base class
 *
 *         License:  GPL-2+
 */

#ifndef CNRUN_LIBCN_BASENEURON_H_
#define CNRUN_LIBCN_BASENEURON_H_

#if HAVE_CONFIG_H && !defined(VERSION)
#  include "config.h"
#endif

#include <list>
#include <cstring>
#include <cmath>
#include <map>
#include <tuple>

#include "forward-decls.hh"
#include "base-unit.hh"
#include "base-synapse.hh"


using namespace std;

namespace cnrun {

struct SSpikeloggerService;

using SCleft = map<C_BaseSynapse*, double>;
inline double operator+ ( double a, const pair<C_BaseSynapse*, double>& b) { return a + b.second; }

class C_BaseNeuron
  : public C_BaseUnit {

        DELETE_DEFAULT_METHODS (C_BaseNeuron)

        friend class CModel;
        friend class C_BaseSynapse;

    protected:
        C_BaseNeuron (TUnitType intype, const string& inlabel,
                      double inx, double iny, double inz,
                      CModel* inM, int s_mask = 0)
              : C_BaseUnit (intype, inlabel, inM, s_mask),
                pos (inx, iny, inz),
                _spikelogger_agent (nullptr)
                {}

        virtual ~C_BaseNeuron();

        struct SCoord {

                DELETE_DEFAULT_METHODS (SCoord)

                double x, y, z;

                SCoord( double inx, double iny, double inz)
                      : x (inx), y (iny), z (inz)
                        {}

                SCoord& operator= ( tuple<double, double, double> v)
                        {
                                tie(x, y, z) = v;
                                return *this;
                        }

              // distance
                double operator- ( const SCoord &p) const
                        {
                                return sqrt( pow(x - p.x, 2) + pow(y - p.y, 2) + pow(z - p.z, 2));
                        }
                bool too_close( const SCoord& p, double mindist = .42 /* units? */) const
                        {
                                return operator-(p) < mindist;
                        }
        };

    public:
        SCoord  pos;

        size_t axonal_conns() const     { return _axonal_harbour.size(); }
        size_t dendrites() const        { return _dendrites.size(); }

        bool
        connects_to( const C_BaseNeuron &to) const;

        C_BaseSynapse*
        connects_via( const C_BaseNeuron &to,
                      SCleft::mapped_type *g_ptr = nullptr) const;

        void reset_state();

      // even though for rate-based neurons, E is not meaningful
      // leave these here to make the method available to synapses wanting _target-E
        virtual double E() const
                {  return 0;  }
        virtual double E( vector<double>&) const
                {  return 0;  }
      // likewise, for those needing _source->F
        virtual double F() const
                {  return 0;  }
        virtual double F( vector<double>&) const
                {  return 0;  }

        // struct __SCleft_second_plus {
        //         double operator() ( double a, const SCleft::value_type &i) { return a + i.second; }
        // };
        double Isyn() const  // is the sum of Isyn() on all dendrites
                {
                        double I = 0.;
                        for ( auto &Y : _dendrites )
                                I += Y.first->Isyn(*this, Y.second);
                        return I;
                }

        double Isyn( vector<double> &x) const  // an honourable mention
                {
                        double I = 0.;
                        for ( auto &Y : _dendrites )
                                I += Y.first->Isyn(x, *this, Y.second);
                        return I;
                }

        virtual void possibly_fire()
                {}

      // Even though rate-based neurons do not track individual spikes,
      // we can estimate a probability of such a neuron spiking as F*dt*rand().
      // Which makes this method a valid one

      // Note this assumes P[0] is F for all rate-based neurons, and E
      // for those conductance-based, which by now is hard-coded for all neurons.
        virtual size_t n_spikes_in_last_dt() const
                {  return 0;  }
        virtual void do_detect_spike_or_whatever()
                {}

        SSpikeloggerService* spikelogger_agent()  { return _spikelogger_agent;  }
        SSpikeloggerService*
        enable_spikelogging_service( int s_mask = 0);
        SSpikeloggerService*
        enable_spikelogging_service( double sample_period, double sigma, double from = 0.,
                                     int s_mask = 0);
        void disable_spikelogging_service();
        void sync_spikelogging_history() const;

        double distance_to( C_BaseNeuron*) const; // will do on demand

        void dump( bool with_params = false, FILE *strm = stdout) const;

    protected:
        SCleft  _dendrites;
        list<C_BaseSynapse*>
                _axonal_harbour;

        SSpikeloggerService
               *_spikelogger_agent;
};





#define CN_KL_COMPUTESDF        (1 << 0)
#define CN_KL_ISSPIKINGNOW      (1 << 1)
#define CN_KL_PERSIST           (1 << 2)  // should not be deleted at disable_spikelogging_service
#define CN_KL_IDLE              (1 << 3)  // should not be placed on spikelogging_neurons on enable_spikelogging_service


struct SSpikeloggerService {

        DELETE_DEFAULT_METHODS (SSpikeloggerService)

        friend class C_BaseNeuron;
        friend class C_HostedConductanceBasedNeuron;  // accesses _status from do_spikelogging_or_whatever
        friend class COscillatorDotPoisson;  // same
        friend class COscillatorPoisson;  // same
        friend class CModel;  // checks CN_KL_IDLE in include_unit

    public:
        SSpikeloggerService (C_BaseNeuron *client,
                             int s_mask = 0)
              : _client (client),
                t_last_spike_start (-INFINITY), t_last_spike_end (-INFINITY),
                sample_period (42), sigma (42), start_delay (0.),
                _status (s_mask & ~CN_KL_COMPUTESDF)
                {}
        SSpikeloggerService (C_BaseNeuron *client,
                             double insample_period, double insigma, double instart_delay = 0.,
                             int s_mask = 0)
              : _client (client),
                t_last_spike_start (-INFINITY), t_last_spike_end (-INFINITY),
                sample_period (insample_period), sigma (insigma), start_delay (instart_delay),
                _status (s_mask | CN_KL_COMPUTESDF)
                {}

        C_BaseNeuron *_client;

        double  t_last_spike_start,
                t_last_spike_end;

        double  sample_period,
                sigma,
                start_delay;

//        void spike_detect();  // multiplexing units will have a different version
        // replaced by do_spikelogging_or_whatever on the client side

        vector<double> spike_history;

        void reset()
                {
                        _status &= ~CN_KL_ISSPIKINGNOW;
                        t_last_spike_start = t_last_spike_end
                                /*= t_firing_started = t_firing_ended */ = -INFINITY;
                        spike_history.clear();
                }

        size_t n_spikes_since( double since = 0.) const;

      // spike density function
        double sdf( double at, double sample_length, double sigma,
                    size_t* nspikes = nullptr) const;
      // spike homogeneity function
        double shf( double at, double sample_length) const;

      // why not allow custom sampling?
        size_t get_sxf_vector_custom( vector<double> *sdf_buf, vector<double> *shf_buf, vector<size_t> *nsp_buf,
                               double sample_period_custom, double sigma_custom,
                               double from = 0., double to = 0.) const; // "to == 0." for model_time()
        size_t get_sxf_vector( vector<double> *sdf_buf, vector<double> *shf_buf, vector<size_t> *nsp_buf,
                               double from = 0., double to = 0.) const
                {
                        return get_sxf_vector_custom( sdf_buf, shf_buf, nsp_buf,
                                                      sample_period, sigma,
                                                      from, to);
                }

    protected:
        void sync_history() const;

    private:
        int _status;
};




inline void
C_BaseNeuron::reset_state()
{
        C_BaseUnit::reset_state();
        if ( _spikelogger_agent )
                _spikelogger_agent->reset();
}



inline void
C_BaseNeuron::sync_spikelogging_history() const
{
        if ( _spikelogger_agent )
                _spikelogger_agent->sync_history();
}



inline double
C_BaseSynapse::g_on_target( C_BaseNeuron &neuron) const
{
        return neuron._dendrites.at(
                const_cast<C_BaseSynapse*>(this));
}
inline void
C_BaseSynapse::set_g_on_target( C_BaseNeuron &neuron, double g)
{
        neuron._dendrites[this] = g;
}


}

#endif

// Local Variables:
// Mode: c++
// indent-tabs-mode: nil
// tab-width: 8
// c-basic-offset: 8
// End:
