/*
 *       File name:  libcn/model.hh
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 * Initial version:  2008-09-02
 *
 *         Purpose:  Main model class.
 *
 *         License:  GPL-2+
 */

/*--------------------------------------------------------------------------

The wrapper class which takes lists of pointers to neurons and synapses
which are networked to a neural system and assembles a common state
vector and handles the derivatives. At the same time it serves the neurons
and synapses their state at any given time and allows them to adjust their
parameters.

--------------------------------------------------------------------------*/


#ifndef CNRUN_LIBCN_MODEL_H_
#define CNRUN_LIBCN_MODEL_H_

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

#include <list>
#include <vector>
#include <string>

#include "libxml/parser.h"
#include "libxml/tree.h"

#include "gsl/gsl_rng.h"

#include "libstilton/misc.hh"
#include "forward-decls.hh"
#include "base-neuron.hh"
#include "base-synapse.hh"
#include "hosted-neurons.hh"
#include "hosted-synapses.hh"
#include "standalone-neurons.hh"
#include "standalone-synapses.hh"
#include "integrate-rk65.hh"


using namespace std;

namespace cnrun {

struct SModelOptions {
        bool    listen_1varonly:1,
                listen_deferwrite:1,
                listen_binary:1,
                log_dt:1,
                log_spikers:1,
                log_spikers_use_serial_id:1,
                log_sdf:1,
                display_progress_percent:1,
                display_progress_time:1;
        int     precision;
        double  spike_threshold,
                spike_lapse,
                listen_dt;
        double  //discrete_dt,
                integration_dt_max,
                integration_dt_min,
                integration_dt_cap;
        double  sxf_start_delay,
                sxf_period,
                sdf_sigma;
        int     verbosely;

        SModelOptions ()
              : listen_1varonly (true), listen_deferwrite (false), listen_binary (false),
                log_dt (false),
                log_spikers (false), log_spikers_use_serial_id (false),
                log_sdf (false),
                display_progress_percent (true),
                display_progress_time (false),
                precision (8),
                spike_threshold (0.), spike_lapse (3.),
                listen_dt(1.),
                //discrete_dt(.5),
                integration_dt_max (.5), integration_dt_min (1e-5), integration_dt_cap (5.),
                sxf_start_delay (0.), sxf_period (0.), sdf_sigma (0.),
                verbosely (1)
                {}

        SModelOptions (const SModelOptions& rv)
                {
                        memmove(this, &rv, sizeof(SModelOptions));
                }
};


class CModel : public cnrun::stilton::C_verprintf {

    public:
      // ctor, dtor
        CModel (const string& name, CIntegrate_base*, const SModelOptions&);
        virtual ~CModel ();

        string  name;

        SModelOptions
                options;

      // Unit list and lookup
        vector<C_BaseUnit*>
        list_units() const
                {  return move(vector<C_BaseUnit*> (units.begin(), units.end()));  }
        vector<C_BaseUnit*>
        list_units( const string& label) const;
        C_BaseUnit    *unit_by_label( const string&) const;
        C_BaseNeuron  *neuron_by_label( const string&) const;
        C_BaseSynapse *synapse_by_label( const string&) const;
        unsigned short longest_label() const
                {  return _longest_label;  }

      // Unit tally
        size_t n_hosted_units() const
                {  return hosted_neurons.size() + hosted_synapses.size();                }
        size_t n_standalone_units() const
                {  return standalone_neurons.size() + standalone_synapses.size();        }
        size_t n_ddtbound_units() const
                {  return ddtbound_neurons.size() + ddtbound_synapses.size();            }
        size_t n_total_neurons() const
                {
                        return hosted_neurons.size()
                                + standalone_neurons.size()
                                + ddtbound_neurons.size();
                }
        size_t n_total_synapses() const
                {
                        return hosted_synapses.size()
                                + standalone_synapses.size()
                                + ddtbound_synapses.size();
                }

      // 0. Model composition

      // There are two ways of adding units:
      // - create units outside, then 'include' them in a model;
      // - specify which unit you want, by type, and creating
      //   them directly in the model ('add').

        //enum class TIncludeOption { is_last, is_notlast, };  // defined in hosted-unit.hh
        // if option == is_last, do allocations of hosted units' vars immediately
        // otherwise defer until addition is done with option == is_notlast
        // or the user calls finalize_additions
        int include_unit( C_HostedNeuron*, TIncludeOption option = TIncludeOption::is_last);
        int include_unit( C_HostedSynapse*, TIncludeOption option = TIncludeOption::is_last);
        int include_unit( C_StandaloneNeuron*);
        int include_unit( C_StandaloneSynapse*);

        C_BaseNeuron*
        add_neuron_species( TUnitType, const string& label,
                            TIncludeOption = TIncludeOption::is_last,
                            double x = 0., double y = 0., double z = 0.);
        C_BaseNeuron*
        add_neuron_species( const string& type, const string& label,
                            TIncludeOption = TIncludeOption::is_last,
                            double x = 0., double y = 0., double z = 0.);

        enum class TSynapseCloningOption { yes, no, };
        C_BaseSynapse*
        add_synapse_species( const string& type, const string& src, const string& tgt,
                             double g,
                             TSynapseCloningOption = TSynapseCloningOption::yes,
                             TIncludeOption = TIncludeOption::is_last);
        void finalize_additions();

        C_BaseSynapse*
        add_synapse_species( TUnitType, C_BaseNeuron *src, C_BaseNeuron *tgt,
                             double g,
                             TSynapseCloningOption = TSynapseCloningOption::yes,
                             TIncludeOption = TIncludeOption::is_last);

        enum class TExcludeOption { with_delete, no_delete, };
        C_BaseUnit*
        exclude_unit( C_BaseUnit*, TExcludeOption option = TExcludeOption::no_delete);
        // return nullptr if option == do_delete, the excluded unit otherwise, even if it was not owned
        void delete_unit( C_BaseUnit* u)
                {  exclude_unit( u, TExcludeOption::with_delete);  }

      // 1. NeuroMl interface
        enum class TNMLImportOption { merge, reset, };
        enum TNMLIOResult {
                ok = 0, nofile, noelem, badattr, badcelltype, biglabel, structerror,
        };
        int import_NetworkML( const string& fname, TNMLImportOption);
        int import_NetworkML( xmlDoc*, const string& fname, TNMLImportOption);  // fname is merely informational here
        int export_NetworkML( const string& fname);
        int export_NetworkML( xmlDoc*);

      // 2. Bulk operations
        enum class TResetOption { with_params, no_params, };
        void reset( TResetOption = TResetOption::no_params);
        void reset_state_all_units();

        void cull_deaf_synapses();  // those with gsyn == 0
        void cull_blind_synapses(); // those with _source == nullptr

      // 3. Informational
        size_t vars() const  { return _var_cnt; }
        void dump_metrics( FILE *strm = stdout) const;
        void dump_state( FILE *strm = stdout) const;
        void dump_units( FILE *strm = stdout) const;

      // 4. Set unit parameters
      // high-level functions to manipulate unit behaviour, set params, & connect sources
        struct STagGroup {
                string pattern;
                enum class TInvertOption { yes, no, };
                TInvertOption invert_option;
                STagGroup( const string& a,
                           TInvertOption b = STagGroup::TInvertOption::no)
                      : pattern (a), invert_option (b)
                        {}
        };
        struct STagGroupListener : STagGroup {
                int bits;
                STagGroupListener( const string& a,
                                   int c = 0,
                                   STagGroup::TInvertOption b = STagGroup::TInvertOption::no)
                      : STagGroup (a, b), bits (c)
                        {}
        };
        size_t process_listener_tags( const list<STagGroupListener>&);

        struct STagGroupSpikelogger : STagGroup {
                double period, sigma, from;
                STagGroupSpikelogger( const string& a,
                                      double c = 0., double d = 0., double e = 0.,  // defaults disable sdf computation
                                      STagGroup::TInvertOption b = STagGroup::TInvertOption::no)
                      : STagGroup (a, b), period (c), sigma (d), from (e)
                        {}
        };
        size_t process_spikelogger_tags( const list<STagGroupSpikelogger>&);
        size_t process_putout_tags( const list<STagGroup>&);

        struct STagGroupDecimate : STagGroup {
                float fraction;
                STagGroupDecimate( const string& a, double c)
                      : STagGroup (a, TInvertOption::no), fraction (c)
                        {}
        };
        size_t process_decimate_tags( const list<STagGroupDecimate>&);

        struct STagGroupNeuronParmSet : STagGroup {
                string parm;
                double value;
                STagGroupNeuronParmSet( const string& a,
                                        const string& c, double d,
                                        STagGroup::TInvertOption b = STagGroup::TInvertOption::no)
                      : STagGroup (a, b),
                        parm (c), value (d)
                        {}
        };
        struct STagGroupSynapseParmSet : STagGroupNeuronParmSet {
                string target;
                STagGroupSynapseParmSet( const string& a,
                                         const string& z, const string& c, double d,
                                         STagGroup::TInvertOption b = STagGroup::TInvertOption::no)
                      : STagGroupNeuronParmSet (a, c, d, b), target (z)
                        {}
        };
        size_t process_paramset_static_tags( const list<STagGroupNeuronParmSet>&);
        size_t process_paramset_static_tags( const list<STagGroupSynapseParmSet>&);

        struct STagGroupSource : STagGroup {
                string parm;
                C_BaseSource *source;
                STagGroupSource( const string& a,
                                 const string& c, C_BaseSource *d,
                                 STagGroup::TInvertOption b = STagGroup::TInvertOption::no)  // b == false to revert to stock
                      :  STagGroup (a, b), parm (c), source (d)
                        {}
        };
        size_t process_paramset_source_tags( const list<STagGroupSource>&);

        C_BaseSource*
        source_by_id( const string& id) const
                {
                        for ( auto& S : _sources )
                                if ( id == S->name() )
                                        return S;
                        return nullptr;
                }
        const list<C_BaseSource*>&
        sources() const
                {  return _sources;  }
        void
        add_source( C_BaseSource* s)
                {
                        _sources.push_back( s);
                }
        // no (straight) way to delete a source

      // 5. Running
        unsigned advance( double dist, double *cpu_time_p = nullptr) __attribute__ ((hot));
        double model_time() const  { return V[0]; }

        double dt() const      { return _integrator->dt; }
        double dt_min() const  { return _integrator->_dt_min; }
        double dt_max() const  { return _integrator->_dt_max; }
        double dt_cap() const  { return _integrator->_dt_cap; }
        void set_dt(double v)  { _integrator->dt = v; }
        void set_dt_min(double v)  { _integrator->_dt_min = v; }
        void set_dt_max(double v)  { _integrator->_dt_max = v; }
        void set_dt_cap(double v)  { _integrator->_dt_cap = v; }

        unsigned long cycle()         const { return _cycle;          }
        double model_discrete_time()  const { return _discrete_time;  }
        double discrete_dt()          const { return _discrete_dt;    }

      // 9. misc
        gsl_rng *rng() const
                {  return _rng;  }
        double rng_sample() const
                {
                        return gsl_rng_uniform_pos( _rng);
                }
    private:
        friend class C_BaseUnit;
        friend class C_BaseNeuron;
        friend class C_BaseSynapse;
        friend class C_HostedNeuron;
        friend class C_HostedConductanceBasedNeuron;
        friend class C_HostedRateBasedNeuron;
        friend class C_HostedSynapse;
        friend class CNeuronMap;
        friend class CSynapseMap;
        friend class CSynapseMxAB_dd;
        friend class SSpikeloggerService;

        friend class CIntegrate_base;
        friend class CIntegrateRK65;

      // supporting functions
        void register_listener( C_BaseUnit*);
        void unregister_listener( C_BaseUnit*);
        void register_spikelogger( C_BaseNeuron*);
        void unregister_spikelogger( C_BaseNeuron*);
        void register_mx_synapse( C_BaseSynapse*);
        void unregister_mx_synapse( C_BaseSynapse*);

        void register_unit_with_sources( C_BaseUnit*);
        void unregister_unit_with_sources( C_BaseUnit*);
        void _include_base_unit( C_BaseUnit*);

        int _process_populations( xmlNode*);
        int _process_population_instances( xmlNode*, const xmlChar*, const xmlChar*);

        int _process_projections( xmlNode*);
        int _process_projection_connections( xmlNode*, const xmlChar*, const xmlChar*,
                                             const xmlChar *src_grp_prefix,
                                             const xmlChar *tgt_grp_prefix);

        void _setup_schedulers();
        void coalesce_synapses();
        void prepare_advance();
        unsigned _do_advance_on_pure_hosted( double, double*)  __attribute__ ((hot));
        unsigned _do_advance_on_pure_standalone( double, double*) __attribute__ ((hot));
        unsigned _do_advance_on_pure_ddtbound( double, double*) __attribute__ ((hot));
        unsigned _do_advance_on_mixed( double, double*) __attribute__ ((hot));

        void make_listening_units_tell()
                {
                        for ( auto& U : listening_units )
                                U -> tell();
                }
        void make_conscious_neurons_possibly_fire()
                {
                        for ( auto& U : conscious_neurons )
                                U->possibly_fire();
                }
        void make_units_with_periodic_sources_apprise_from_sources()
                {
                        for ( auto& U : units_with_periodic_sources )
                                U->apprise_from_sources();
                }
        void make_units_with_continuous_sources_apprise_from_sources()
                {
                        for ( auto& U : units_with_continuous_sources )
                                U->apprise_from_sources();
                }
        void make_spikeloggers_sync_history()
                {
                        for ( auto& N : spikelogging_neurons )
                                N->sync_spikelogging_history();
                }

        static double
        model_time( vector<double> &x)
                {
                        return x[0];
                }

      // contents
        list<C_BaseUnit*>
                units; // all units together
      // these have derivative(), are churned in _integrator->cycle()
        list<C_HostedNeuron*>
                hosted_neurons;
        list<C_HostedSynapse*>
                hosted_synapses;
      // these need preadvance() and fixate()
        list<C_StandaloneNeuron*>
                standalone_neurons;
        list<C_StandaloneSynapse*>
                standalone_synapses;
      // ... also these, but at discrete dt only
      // (only the standalone map units currently)
        list<C_StandaloneNeuron*>
                ddtbound_neurons;
        list<C_StandaloneSynapse*>
                ddtbound_synapses;

      // neurons that can possibly_fire() (various oscillators), and
      // have no inputs, and hence not dependent on anything else
        list<C_BaseNeuron*>
                conscious_neurons;

      // various lists to avoid traversing all of them in units:
      // listeners, spikeloggers & readers
        list<C_BaseUnit*>
                listening_units;
      // uses a meaningful do_spikelogging_or_whatever
        list<C_BaseNeuron*>
                spikelogging_neurons;
      // `Multiplexing AB' synapses are treated very specially
        list<C_BaseSynapse*>
                multiplexing_synapses;

      // those for which apprise_from_source( model_time()) will be called
        list<C_BaseUnit*>
                units_with_continuous_sources;
      // same, but not every cycle
        list<C_BaseUnit*>
                units_with_periodic_sources;
        list<double>
                regular_periods;
        list<unsigned>
                regular_periods_last_checked;

        unsigned long
                _global_unit_id_reservoir;

      // the essential mechanical parts: ----
      // hosted unit variables
        vector<double> V,        // contains catenated var vectors of all constituent neurons and synapses
                       W;        // V and W alternate in the capacity of the main vector, so avoiding many a memcpy
        size_t  _var_cnt;        // total # of variables (to be) allocated in V an W, plus one for model_time

      // integrator interface
        CIntegrate_base
                *_integrator;

        unsigned long
                _cycle;
        double  _discrete_time;
        double  _discrete_dt;

        list<C_BaseSource*>
                _sources;

        ofstream
                *_dt_logger,
                *_spike_logger;

        bool    is_ready:1,
                is_diskless:1,
                have_ddtb_units:1;

        unsigned short
                _longest_label;

        gsl_rng *_rng;

        int verbose_threshold() const
                {
                        return options.verbosely;
                }
};





inline void
CIntegrateRK65::fixate()
{
        swap( model->V, model->W);
}


// various CUnit & CNeuron methods accessing CModel members
// that we want to have inline

inline double
C_BaseUnit::model_time() const
{
        return M->model_time();
}

inline void
C_BaseUnit::pause_listening()
{
        if ( !M )
                throw "pause_listening() called on NULL model";
        M->unregister_listener( this);
}

inline void
C_BaseUnit::resume_listening()
{
        if ( !M )
                throw "resume_listening() called on NULL model";
        M->register_listener( this);
}



template <class T>
void
C_BaseUnit::attach_source( T *s, TSinkType t, unsigned short idx)
{
        _sources.push_back( SSourceInterface<T>( s, t, idx));
        M->register_unit_with_sources(this);
}





inline SSpikeloggerService*
C_BaseNeuron::
enable_spikelogging_service( int s_mask)
{
        if ( !_spikelogger_agent )
                _spikelogger_agent =
                        new SSpikeloggerService( this, s_mask);
        M->register_spikelogger( this);
        return _spikelogger_agent;
}
inline SSpikeloggerService*
C_BaseNeuron::
enable_spikelogging_service( double sample_period,
                             double sigma,
                             double from, int s_mask)
{
        if ( !_spikelogger_agent )
                _spikelogger_agent =
                        new SSpikeloggerService( this, sample_period, sigma, from, s_mask);
        M->register_spikelogger( this);
        return _spikelogger_agent;
}

inline void
C_BaseNeuron::
disable_spikelogging_service()
{
        if ( _spikelogger_agent && !(_spikelogger_agent->_status & CN_KL_PERSIST)) {
                _spikelogger_agent->sync_history();
                M->unregister_spikelogger( this);

                delete _spikelogger_agent;
                _spikelogger_agent = nullptr;
        }
}






inline void
C_HostedNeuron::
reset_vars()
{
        if ( M && idx < M->_var_cnt )
                memcpy( &M->V[idx],
                        __CNUDT[_type].stock_var_values,
                        __CNUDT[_type].vno * sizeof(double));
}

inline double&
C_HostedNeuron::
var_value( size_t v)
{
        return M->V[idx + v];
}

inline const double&
C_HostedNeuron::
get_var_value( size_t v) const
{
        return M->V[idx + v];
}



inline size_t
C_HostedConductanceBasedNeuron::
n_spikes_in_last_dt() const
{
        return E() >= M->options.spike_threshold;
}

inline size_t
C_HostedRateBasedNeuron::
n_spikes_in_last_dt() const
{
        return round(E() * M->dt() * M->rng_sample());
}


inline size_t
C_StandaloneConductanceBasedNeuron::
n_spikes_in_last_dt() const
{
        return E() >= M->options.spike_threshold;
}

inline size_t
C_StandaloneRateBasedNeuron::
n_spikes_in_last_dt() const
{
        return round(E() * M->dt() * M->rng_sample());
}





inline void
C_HostedSynapse::
reset_vars()
{
        if ( M && M->_var_cnt > idx )
                memcpy( &M->V[idx],
                        __CNUDT[_type].stock_var_values,
                        __CNUDT[_type].vno * sizeof(double));
}



inline double&
C_HostedSynapse::
var_value( size_t v)
{
        return M->V[idx + v];
}

inline const double&
C_HostedSynapse::
get_var_value( size_t v) const
{
        return M->V[idx + v];
}



inline double
C_HostedConductanceBasedNeuron::
E() const
{
        return M->V[idx+0];
}

// F is computed on the fly, so far usually


inline double
C_HostedSynapse::
S() const
{
        return M->V[idx+0];
}




inline void
CSynapseMap::
preadvance()
{
        V_next[0] = S() * exp( -M->discrete_dt() / P[_tau_])
                + (_source->n_spikes_in_last_dt() ? P[_delta_] : 0);
}



inline void
CSynapseMxMap::
preadvance()
{
        V_next[0] = S() * exp( -M->discrete_dt() / P[_tau_]) + q() * P[_delta_];
}

}

#endif

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