/*
 *       File name:  libcn/mmodel-struct.cc
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 *                   building on original work by Thomas Nowotny
 * Initial version:  2008-09-02
 *
 *         Purpose:  CModel household.
 *
 *         License:  GPL-2+
 */

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

#include <sys/time.h>
#include <csignal>
#include <iostream>
#include <set>
#include <algorithm>
#include <functional>

#include "libstilton/string.hh"
#include "model.hh"


using namespace std;
using namespace cnrun::stilton::str;


cnrun::CModel::
CModel (const string& inname,
        CIntegrate_base *inintegrator,
        const SModelOptions& inoptions)
      : name (inname),
        options (inoptions),
        _global_unit_id_reservoir (0l),
        V (1),
        W (1),
        _var_cnt (1),                        // reserve [0] for model_time
        _cycle (0),
        _discrete_time (0.),  _discrete_dt (NAN),
        _dt_logger (nullptr),
        _spike_logger (nullptr),        // open these streams at first write instead in prepare_advance()
        is_ready (false),
        is_diskless (false),
        have_ddtb_units (false),
        _longest_label (1)
{
        V[0] = 0.;

        (_integrator = inintegrator) -> model = this;

        {
                const gsl_rng_type * T;
                gsl_rng_env_setup();
                T = gsl_rng_default;
                if ( gsl_rng_default_seed == 0 ) {
                        struct timeval tp = { 0L, 0L };
                        gettimeofday( &tp, nullptr);
                        gsl_rng_default_seed = tp.tv_usec;
                }
                _rng = gsl_rng_alloc( T);
        }

      // don't abort interpreter with ^C
        signal( SIGINT, SIG_IGN);
}


cnrun::CModel::
~CModel()
{
        vp( 4, "Deleting all units...\n");

        while (units.size())
                if ( units.back() -> is_owned() )
                        delete units.back();
                else
                        units.pop_back();

        if ( _integrator->is_owned )
                delete _integrator;

        delete _dt_logger;
        delete _spike_logger;

        while ( _sources.size() ) {
                delete _sources.back();
                _sources.pop_back();
        }

        gsl_rng_free( _rng);
}


void
cnrun::CModel::
reset( TResetOption option)
{
        _cycle = 0;
        V[0] = 0.;

        _integrator->dt = _integrator->_dt_min;

        reset_state_all_units();
        if ( option == TResetOption::with_params )
                for_each ( units.begin(), units.end(),
                           [] (C_BaseUnit* u) { u->reset_params(); });

        regular_periods.clear();
        regular_periods_last_checked.clear();
        // this will cause scheduler_update_periods_* to be recomputed by prepare_advance()

        is_ready = false;

        if ( options.log_dt ) {
                delete _dt_logger;
                _dt_logger = new ofstream( (name + ".dtlog").data());
        }
        if ( options.log_spikers ) {
                delete _spike_logger;
                _spike_logger = new ofstream( (name + ".spikes").data());
        }
}



cnrun::C_BaseUnit*
cnrun::CModel::
unit_by_label( const string& label) const
{
        for ( const auto& U : units )
                if ( label == U->_label )
                        return U;
        return nullptr;
}


cnrun::C_BaseNeuron*
cnrun::CModel::
neuron_by_label( const string& label) const
{
        for ( const auto& U : units )
                if ( U->is_neuron() && label == U->label() )
                        return static_cast<C_BaseNeuron*>(U);
        return nullptr;
}


cnrun::C_BaseSynapse*
cnrun::CModel::
synapse_by_label( const string& label) const
{
        for ( const auto& U : units )
                if ( U->is_synapse() && label == U->label() )
                        return static_cast<C_BaseSynapse*>(U);
        return nullptr;
}





// ----- registering units with core lists
void
cnrun::CModel::
_include_base_unit( C_BaseUnit* u)
{
        if ( any_of( units.begin(), units.end(),
                     bind(equal_to<C_BaseUnit*>(), placeholders::_1, u)) )
                vp( 1, stderr, "Unit %s found already included in model %s\n",
                    u->_label, name.c_str());
        else
                units.push_back( u);

        vp( 5, "  registered base unit %s\n", u->_label);

        if ( u->has_sources() )
                register_unit_with_sources( u);

        if ( u->is_listening() ) {
                if ( count( listening_units.begin(), listening_units.end(), u) )
                        vp( 1, stderr, "Unit \"%s\" already on listening list\n",
                            u->_label);
                else
                        listening_units.push_back( u);
        }

        u->M = this;
        u->_serial_id = _global_unit_id_reservoir++;
}




int
cnrun::CModel::
include_unit( C_HostedNeuron *u, const TIncludeOption option)
{
        _include_base_unit( u);

        u->idx = _var_cnt;
        _var_cnt += u->v_no();

        hosted_neurons.push_back( u);

        // if ( u->_spikelogger_agent  &&  !(u->_spikelogger_agent->_status & CN_KL_IDLE) )
        //         spikelogging_neurons.push_back( u);

        if ( u->is_conscious() )
                conscious_neurons.push_back( u);

        if ( option == TIncludeOption::is_last )
                finalize_additions();

        return 0;
}

int
cnrun::CModel::
include_unit( C_HostedSynapse *u, const TIncludeOption option)
{
        _include_base_unit( u);

        u->idx = _var_cnt;
        _var_cnt += u->v_no();

        hosted_synapses.push_back( u);

        if ( u->traits() & UT_MULTIPLEXING )
                multiplexing_synapses.push_back( u);

        if ( option == TIncludeOption::is_last )
                finalize_additions();

        return 0;
}



int
cnrun::CModel::
include_unit( C_StandaloneNeuron *u)
{
        _include_base_unit( u);

        // if ( u->_spikelogger_agent  &&  !(u->_spikelogger_agent->_status & CN_KL_IDLE) )
        //         spikelogging_neurons.push_back( u);

        if ( u->is_conscious() )
                conscious_neurons.push_back( u);

        if ( u->is_ddtbound() )
                ddtbound_neurons.push_back( u);
        else
                standalone_neurons.push_back( u);

        return 0;
}


int
cnrun::CModel::
include_unit( C_StandaloneSynapse *u)
{
/*
        if ( _check_new_synapse( u) ) {
//                u->enable( false);
                u->M = nullptr;
                return -1;
        }
*/
        _include_base_unit( u);

        if ( u->is_ddtbound() )
                ddtbound_synapses.push_back( u);
        else
                standalone_synapses.push_back( u);

        if ( u->traits() & UT_MULTIPLEXING )
                multiplexing_synapses.push_back( u);

        return 0;
}



// preserve the unit if !do_delete, so it can be re-included again
cnrun::C_BaseUnit*
cnrun::CModel::
exclude_unit( C_BaseUnit *u, const TExcludeOption option)
{
        vp( 5, stderr, "-excluding unit \"%s\"", u->_label);

        if ( u->has_sources() )
                unregister_unit_with_sources( u);

        if ( u->is_listening() )
                u->stop_listening();  // also calls unregister_listener

        if ( u->is_synapse() && u->traits() & UT_MULTIPLEXING )
                multiplexing_synapses.erase( find( multiplexing_synapses.begin(), multiplexing_synapses.end(), u));

        if ( u->is_conscious() )
                conscious_neurons.erase(
                        find( conscious_neurons.begin(), conscious_neurons.end(),
                              u));

        if ( u->is_hostable() ) {
                size_t  our_idx;
                if ( u->is_neuron() ) {
                        hosted_neurons.erase( find( hosted_neurons.begin(), hosted_neurons.end(), u));
                        our_idx = ((C_HostedNeuron*)u) -> idx;
                } else {
                        hosted_synapses.erase( find( hosted_synapses.begin(), hosted_synapses.end(), u));
                        our_idx = ((C_HostedSynapse*)u) -> idx;
                }

              // shrink V
                vp( 5, stderr, " (shrink V by %d)", u->v_no());
                for ( auto& N : hosted_neurons )
                        if ( N->idx > our_idx )
                                N->idx -= u->v_no();
                for ( auto& Y : hosted_synapses )
                        if ( Y->idx > our_idx )
                                Y->idx -= u->v_no();
                memmove( &V[our_idx], &V[our_idx+u->v_no()],
                         (_var_cnt - our_idx - u->v_no()) * sizeof(double));
                V.resize( _var_cnt -= u->v_no());
        }

        if ( u->is_ddtbound() ) {
                if ( u->is_neuron() )
                        ddtbound_neurons.erase( find( ddtbound_neurons.begin(), ddtbound_neurons.end(), u));
                else
                        ddtbound_synapses.erase( find( ddtbound_synapses.begin(), ddtbound_synapses.end(), u));
        }

        if ( !u->is_hostable() ) {
                if ( u->is_neuron() )
                        standalone_neurons.remove(
                                static_cast<C_StandaloneNeuron*>(u));
                else
                        standalone_synapses.remove(
                                static_cast<C_StandaloneSynapse*>(u));
        }

        units.remove( u);

        if ( option == TExcludeOption::with_delete ) {
                delete u;
                u = nullptr;
        } else
                u->M = nullptr;

        vp( 5, stderr, ".\n");
        return u;
}







// listeners & spikeloggers

void
cnrun::CModel::
register_listener( C_BaseUnit *u)
{
        if ( not count( listening_units.begin(), listening_units.end(), u) )
                listening_units.push_back( u);
}

void
cnrun::CModel::
unregister_listener( C_BaseUnit *u)
{
        listening_units.remove( u);
}







void
cnrun::CModel::
register_spikelogger( C_BaseNeuron *n)
{
        spikelogging_neurons.push_back( n);
        spikelogging_neurons.sort();
        spikelogging_neurons.unique();
}

void
cnrun::CModel::
unregister_spikelogger( C_BaseNeuron *n)
{
        spikelogging_neurons.remove(
                static_cast<decltype(spikelogging_neurons)::value_type>(n));
}





// units with sources

void
cnrun::CModel::
register_unit_with_sources( C_BaseUnit *u)
{
        for ( auto& I : u->_sources )
                if ( I.source->is_periodic() )
                        units_with_periodic_sources.push_back( u);
                else
                        units_with_continuous_sources.push_back( u);
        units_with_continuous_sources.unique();
        units_with_periodic_sources.unique();
}

void
cnrun::CModel::
unregister_unit_with_sources( C_BaseUnit *u)
{
        units_with_continuous_sources.remove(
                static_cast<decltype(units_with_continuous_sources)::value_type>(u));
        units_with_periodic_sources.remove(
                static_cast<decltype(units_with_periodic_sources)::value_type>(u));
}








cnrun::C_BaseNeuron*
cnrun::CModel::
add_neuron_species( const string& type_s, const string& label,
                    const TIncludeOption include_option,
                    const double x, const double y, const double z)
{
        TUnitType t = unit_species_by_string( type_s);
        if ( unlikely (t == NT_VOID || !unit_species_is_neuron(type_s)) ) {
                fprintf( stderr, "Unrecognised neuron species: \"%s\"\n", type_s.c_str());
                return nullptr;
        } else
                return add_neuron_species( t, label, include_option, x, y, z);
}

cnrun::C_BaseNeuron*
cnrun::CModel::
add_neuron_species( TUnitType type, const string& label,
                    const TIncludeOption include_option,
                    double x, double y, double z)
{
        C_BaseNeuron *n;
        switch ( type ) {
        case NT_HH_D:
                n = new CNeuronHH_d( label, x, y, z, this, CN_UOWNED, include_option);
            break;
        case NT_HH_R:
                n = new CNeuronHH_r( label, x, y, z, this, CN_UOWNED);
            break;

        case NT_HH2_D:
                n = new CNeuronHH2_d( label, x, y, z, this, CN_UOWNED, include_option);
            break;
        // case NT_HH2_R:
        //         n = new CNeuronHH2_r( label, x, y, z, this, CN_UOWNED, include_option);
        //     break;
//#ifdef CN_WANT_MORE_NEURONS
        case NT_EC_D:
                n = new CNeuronEC_d( label, x, y, z, this, CN_UOWNED, include_option);
            break;
        case NT_ECA_D:
                n = new CNeuronECA_d( label, x, y, z, this, CN_UOWNED, include_option);
            break;
/*
        case NT_LV:
                n = new COscillatorLV( label, x, y, z, this, CN_UOWNED, include_option);
            break;
 */
        case NT_COLPITTS:
                n = new COscillatorColpitts( label, x, y, z, this, CN_UOWNED, include_option);
            break;
        case NT_VDPOL:
                n = new COscillatorVdPol( label, x, y, z, this, CN_UOWNED, include_option);
            break;
//#endif
        case NT_DOTPOISSON:
                n = new COscillatorDotPoisson( label, x, y, z, this, CN_UOWNED);
            break;
        case NT_POISSON:
                n = new COscillatorPoisson( label, x, y, z, this, CN_UOWNED);
            break;

        case NT_DOTPULSE:
                n = new CNeuronDotPulse( label, x, y, z, this, CN_UOWNED);
            break;

        case NT_MAP:
                n = new CNeuronMap( label, x, y, z, this, CN_UOWNED);
            break;

        default:
                return nullptr;
        }
        if ( n && n->_status & CN_UERROR ) {
                delete n;
                return nullptr;
        }
        return n;
}








cnrun::C_BaseSynapse*
cnrun::CModel::
add_synapse_species( const string& type_s,
                     const string& src_l, const string& tgt_l,
                     const double g,
                     const TSynapseCloningOption cloning_option,
                     const TIncludeOption include_option)
{
        TUnitType ytype = unit_species_by_string( type_s);
        bool    given_species = true;
        if ( ytype == NT_VOID && (given_species = false, ytype = unit_family_by_string( type_s)) == NT_VOID ) {
                vp( 0, stderr, "Unrecognised synapse species or family: \"%s\"\n", type_s.c_str());
                return nullptr;
        }

        C_BaseNeuron
                *src = neuron_by_label( src_l),
                *tgt = neuron_by_label( tgt_l);
        if ( !src || !tgt ) {
                vp( 0, stderr, "Phony source (\"%s\") or target (\"%s\")\n", src_l.c_str(), tgt_l.c_str());
                return nullptr;
        }

        if ( given_species )  // let lower function do the checking
                return add_synapse_species( ytype, src, tgt, g, cloning_option, include_option);

        switch ( ytype ) {
      // catch by first entry in __CNUDT, assign proper species per source and target traits
        case YT_AB_DD:
                if ( src->traits() & UT_RATEBASED && tgt->traits() & UT_RATEBASED )
                        ytype = YT_AB_RR;
                else if ( src->traits() & UT_RATEBASED && !(tgt->traits() & UT_RATEBASED) )
                        ytype = YT_AB_RD;
                else if ( !(src->traits() & UT_RATEBASED) && tgt->traits() & UT_RATEBASED )
                        if ( src->traits() & UT_DOT )
                                ytype = YT_MXAB_DR;
                        else
                                ytype = YT_AB_DR;
                else
                        if ( src->traits() & UT_DOT )
                                ytype = YT_MXAB_DD;
                        else
                                ytype = YT_AB_DD;
            break;

        case YT_ABMINUS_DD:
                if ( src->traits() & UT_RATEBASED && tgt->traits() & UT_RATEBASED )
                        ytype = YT_ABMINUS_RR;
                else if ( src->traits() & UT_RATEBASED && !(tgt->traits() & UT_RATEBASED) )
                        ytype = YT_ABMINUS_RD;
                else if ( !(src->traits() & UT_RATEBASED) && tgt->traits() & UT_RATEBASED )
                        if ( src->traits() & UT_DOT )
                                ytype = YT_MXABMINUS_DR;
                        else
                                ytype = YT_ABMINUS_DR;
                else
                        if ( src->traits() & UT_DOT )
                                ytype = YT_MXABMINUS_DD;
                        else
                                ytype = YT_ABMINUS_DD;
            break;

        case YT_RALL_DD:
                if ( src->traits() & UT_RATEBASED && tgt->traits() & UT_RATEBASED )
                        ytype = YT_RALL_RR;
                else if ( src->traits() & UT_RATEBASED && !(tgt->traits() & UT_RATEBASED) )
                        ytype = YT_RALL_RD;
                else if ( !(src->traits() & UT_RATEBASED) && tgt->traits() & UT_RATEBASED )
                        if ( src->traits() & UT_DOT )
                                ytype = YT_MXRALL_DR;
                        else
                                ytype = YT_RALL_DR;
                else
                        if ( src->traits() & UT_DOT )
                                ytype = YT_MXRALL_DD;
                        else
                                ytype = YT_RALL_DD;
            break;

        case YT_MAP:
                if ( src->traits() & UT_DDTSET)
                        if ( src->traits() & UT_DOT )
                                ytype = YT_MXMAP;
                        else
                                ytype = YT_MAP;
                else {
                        vp( 0, stderr, "Map synapses can only connect Map neurons\n");
                        return nullptr;
                }
            break;
        default:
                vp( 0, stderr, "Bad synapse type: %s\n", type_s.c_str());
                return nullptr;
        }

        return add_synapse_species( ytype, src, tgt, g, cloning_option, include_option);
}




cnrun::C_BaseSynapse*
cnrun::CModel::
add_synapse_species( TUnitType ytype,
                     C_BaseNeuron *src, C_BaseNeuron *tgt,
                     double g,
                     TSynapseCloningOption cloning_option, TIncludeOption include_option)
{
        vp( 5, "add_synapse_species( \"%s\", \"%s\", \"%s\", %g, %d, %d)\n",
            __CNUDT[ytype].species, src->_label, tgt->_label, g, cloning_option, include_option);

        C_BaseSynapse *y = nullptr;

      // consider cloning
        if ( cloning_option == TSynapseCloningOption::yes && src->_axonal_harbour.size() )
                for ( auto& L : src->_axonal_harbour )
                        if ( L->_type == ytype &&
                             L->is_not_altered() )
                                return L->clone_to_target( tgt, g);

        switch ( ytype ) {
      // the __CNUDT entry at first TUnitType element whose
      // 'name' matches the type id supplied, captures all cases for a given synapse family
        case YT_AB_RR:
                if (  src->traits() & UT_RATEBASED &&  tgt->traits() & UT_RATEBASED && !(src->traits() & UT_DOT) )
                        y = new CSynapseAB_rr( src, tgt, g, this, CN_UOWNED, include_option);
            break;
        case YT_AB_RD:
                if (  src->traits() & UT_RATEBASED && !(tgt->traits() & UT_RATEBASED) && !(src->traits() & UT_DOT) )
                        // y = new CSynapseAB_rd( synapse_id, src, tgt, this, CN_UOWNED, false);
                        throw "AB_rd not implemented";
            break;
        case YT_AB_DR:
                if ( !(src->traits() & UT_RATEBASED) &&  tgt->traits() & UT_RATEBASED && !(src->traits() & UT_DOT) )
                        // y = new CSynapseAB_rr( synapse_id, src, tgt, this, CN_UOWNED, false);
                        throw "AB_dr not implemented";
            break;
        case YT_AB_DD:
                if ( !(src->traits() & UT_RATEBASED) && !(tgt->traits() & UT_RATEBASED) && !(src->traits() & UT_DOT) )
                        y = new CSynapseAB_dd( src, tgt, g, this, CN_UOWNED, include_option);
            break;
        case YT_MXAB_DR:
                if ( !(src->traits() & UT_RATEBASED) &&  tgt->traits() & UT_RATEBASED &&  src->traits() & UT_DOT )
                        y = new CSynapseMxAB_dr( src, tgt, g, this, CN_UOWNED, include_option);
            break;
        case YT_MXAB_DD:
                if (  !(src->traits() & UT_RATEBASED) && !(tgt->traits() & UT_RATEBASED) &&  src->traits() & UT_DOT )
                        y = new CSynapseMxAB_dd( src, tgt, g, this, CN_UOWNED, include_option);
            break;


        case YT_ABMINUS_RR:
                if (  src->traits() & UT_RATEBASED &&  tgt->traits() & UT_RATEBASED && !(src->traits() & UT_DOT) )
                        // y = new CSynapseABMINUS_rr( src, tgt, g, this, CN_UOWNED, include_option);
                        throw "ABMINUS_rr not implemented";
            break;
        case YT_ABMINUS_RD:
                if (  src->traits() & UT_RATEBASED && !(tgt->traits() & UT_RATEBASED) && !(src->traits() & UT_DOT) )
                        // y = new CSynapseABMINUS_rd( synapse_id, src, tgt, this, CN_UOWNED, false);
                        throw "ABMINUS_rd not implemented";
            break;
        case YT_ABMINUS_DR:
                if ( !(src->traits() & UT_RATEBASED) &&  tgt->traits() & UT_RATEBASED && !(src->traits() & UT_DOT) )
                        // y = new CSynapseABMINUS_rr( synapse_id, src, tgt, this, CN_UOWNED, false);
                        throw "ABMINUS_dr not implemented";
            break;
        case YT_ABMINUS_DD:
                if ( !(src->traits() & UT_RATEBASED) && !(tgt->traits() & UT_RATEBASED) && !(src->traits() & UT_DOT) )
                        y = new CSynapseABMinus_dd( src, tgt, g, this, CN_UOWNED, include_option);
            break;
        case YT_MXABMINUS_DR:
                if ( !(src->traits() & UT_RATEBASED) &&  tgt->traits() & UT_RATEBASED &&  src->traits() & UT_DOT )
                        // y = new CSynapseMxABMinus_dr( src, tgt, g, this, CN_UOWNED, include_option);
                        throw "MxABMinus_dr not implemented";
            break;
        case YT_MXABMINUS_DD:
                if ( !(src->traits() & UT_RATEBASED) && !(tgt->traits() & UT_RATEBASED) &&  src->traits() & UT_DOT )
                        // y = new CSynapseMxABMinus_dd( src, tgt, g, this, CN_UOWNED, include_option);
                        throw "MxABMinus_dd not implemented";
            break;


        case YT_RALL_RR:
                if (  src->traits() & UT_RATEBASED &&  tgt->traits() & UT_RATEBASED && !(src->traits() & UT_DOT) )
                        // y = new CSynapseRall_rr( src, tgt, g, this, CN_UOWNED, include_option);
                        throw "Rall_rr not implemented";
            break;
        case YT_RALL_RD:
                if (  src->traits() & UT_RATEBASED && !(tgt->traits() & UT_RATEBASED) && !(src->traits() & UT_DOT) )
                        // y = new CSynapseRall_rd( synapse_id, src, tgt, this, CN_UOWNED, false);
                        throw "Rall_rd not implemented";
            break;
        case YT_RALL_DR:
                if ( !(src->traits() & UT_RATEBASED) &&  tgt->traits() & UT_RATEBASED && !(src->traits() & UT_DOT) )
                        // y = new CSynapseRall_rr( synapse_id, src, tgt, this, CN_UOWNED, false);
                        throw "Rall_dr not implemented";
            break;
        case YT_RALL_DD:
                if ( !(src->traits() & UT_RATEBASED) && !(tgt->traits() & UT_RATEBASED) && !(src->traits() & UT_DOT) )
                        y = new CSynapseRall_dd( src, tgt, g, this, CN_UOWNED, include_option);
            break;
        case YT_MXRALL_DR:
                if ( !(src->traits() & UT_RATEBASED) &&  tgt->traits() & UT_RATEBASED &&  src->traits() & UT_DOT )
                        // y = new CSynapseMxRall_dr( src, tgt, g, this, CN_UOWNED, include_option);
                        throw "MxRall_dr not implemented";
            break;
        case YT_MXRALL_DD:
                if ( !(src->traits() & UT_RATEBASED) && !(tgt->traits() & UT_RATEBASED) &&  src->traits() & UT_DOT )
                        // y = new CSynapseMxRall_dd( src, tgt, g, this, CN_UOWNED, include_option);
                        throw "MxRall_dd not implemented";
            break;


        case YT_MAP:
                if ( src->traits() & UT_DDTSET)
                        if ( src->traits() & UT_DOT )
                                y = new CSynapseMxMap( src, tgt, g, this, CN_UOWNED);
                        else
                                y = new CSynapseMap( src, tgt, g, this, CN_UOWNED);
                else
                        throw "Map synapses can only connect Map neurons";
            break;

        default:
                return nullptr;
        }

        if ( !y || y->_status & CN_UERROR ) {
                if ( y )
                        delete y;
                return nullptr;
        }

        vp( 5, "new synapse \"%s->%s\"\n", y->_label, tgt->label());
        y->set_g_on_target( *tgt, g);

        return y;
}


void
cnrun::CModel::
finalize_additions()
{
        V.resize( _var_cnt);
        W.resize( _var_cnt);

        for ( auto& U : hosted_neurons )
                U->reset_vars();
        for ( auto& U : hosted_synapses )
                U->reset_vars();

        // if ( options.sort_units ) {
        //         units.sort(
        //                 [] (C_BaseUnit *&lv, C_BaseUnit *&rv) {
        //                         return strcmp( lv->label(), rv->label()) < 0;
        //                 });
        // }

        _integrator->prepare();
}


void
cnrun::CModel::
cull_deaf_synapses()
{
      // 1. Need to traverse synapses backwards due to shifts its
      //    vector will undergo on element deletions;
      // 2. Omit those with a param reader, scheduler or range, but
      //    only if it is connected to parameter "gsyn"
        auto Yi = hosted_synapses.rbegin();
        while ( Yi != hosted_synapses.rend() ) {
                auto& Y = **Yi;
                if ( Y.has_sources() )
                        continue;
                auto Ti = Y._targets.begin();
                while ( Ti != Y._targets.end() ) {
                        auto& T = **Ti;
                        if ( Y.g_on_target( T) == 0  ) {
                                vp( 3, stderr, " (deleting dendrite to \"%s\" of a synapse \"%s\" with gsyn == 0)\n",
                                    T._label, Y._label);
                                T._dendrites.erase( &Y);
                                ++Ti;
                                Y._targets.erase( prev(Ti));

                                snprintf( Y._label, C_BaseUnit::max_label_size-1,
                                          "%s:%zu", Y._source->_label, Y._targets.size());
                        }
                }
                ++Yi;
                if ( (*prev(Yi))->_targets.empty() )
                        delete *prev(Yi);
        }

        // older stuff
/*
        for_all_synapses_reversed (Y) {
                int gsyn_pidx = (*Y) -> param_idx_by_sym( "gsyn");
                if ( ((*Y)->param_schedulers && device_list_concerns_parm( (*Y)->param_schedulers, gsyn_pidx)) ||
                     ((*Y)->param_readers    && device_list_concerns_parm( (*Y)->param_readers,    gsyn_pidx)) ||
                     ((*Y)->param_ranges     && device_list_concerns_parm( (*Y)->param_ranges,     gsyn_pidx)) ) {
                        vp( 2, " (preserving doped synapse with zero gsyn: \"%s\")\n", (*Y)->_label);
                        continue;
                }
                if ( gsyn_pidx > -1 && (*Y)->param_value( gsyn_pidx) == 0. ) {
                        vp( 2, " (deleting synapse with zero gsyn: \"%s\")\n", (*Y)->_label);
                        delete (*Y);
                        ++cnt;
                }
        }
        if ( cnt )
                vp( 0, "Deleted %zd deaf synapses\n", cnt);
*/
}



// needs to be called after a neuron is put out
void
cnrun::CModel::
cull_blind_synapses()
{
        auto Yi = hosted_synapses.begin();
        // units remove themselves from all lists, including the one
        // iterated here
        while ( Yi != hosted_synapses.end() ) {
                auto& Y = **Yi;
                if ( !Y._source && !Y.has_sources() ) {
                        vp( 3, " (deleting synapse with NULL source: \"%s\")\n", Y._label);
                        delete &Y;  // units are smart, self-erase
                                    // themselves from the list we are
                                    // iterating over here
                } else
                        ++Yi;
        }
        auto Zi = standalone_synapses.begin();
        while ( Zi != standalone_synapses.end() ) {
                auto& Y = **Zi;
                if ( !Y._source && !Y.has_sources() ) {
                        vp( 3, " (deleting synapse with NULL source: \"%s\")\n", Y._label);
                        delete &Y;
                } else
                        ++Zi;
        }
}


void
cnrun::CModel::
reset_state_all_units()
{
        for ( auto& U : units )
                U -> reset_state();
}




void
cnrun::CModel::
coalesce_synapses()
{
startover:
        for ( auto& U1i : units ) {
                if ( not U1i->is_synapse() )
                        continue;
                auto& U1 = *static_cast<C_BaseSynapse*>(U1i);
                for ( auto& U2i : units ) {
                        auto& U2 = *static_cast<C_BaseSynapse*>(U2i);
                        if ( &U2 == &U1 )
                                continue;

                        if ( U1._source == U2._source && U1.is_identical( U2) ) {
                                vp( 5, "coalescing \"%s\" and \"%s\"\n", U1.label(), U2.label());
                                for ( auto& T : U2._targets ) {
                                        U1._targets.push_back( T);
                                        T->_dendrites[&U1] = T->_dendrites[&U2];
                                }
                                snprintf( U1._label, C_BaseUnit::max_label_size-1,
                                          "%s:%zu", U1._source->label(), U1._targets.size());

                                delete &U2;

                                goto startover;  // because we have messed with both iterators
                        }
                }
        }
}





inline const char*
__attribute__ ((pure))
pl_ending( size_t cnt)
{
        return cnt == 1 ? "" : "s";
}

void
cnrun::CModel::
dump_metrics( FILE *strm) const
{
        fprintf( strm,
                 "\nModel \"%s\"%s:\n"
                 "  %5zd unit%s total (%zd Neuron%s, %zd Synapse%s):\n"
                 "    %5zd hosted,\n"
                 "    %5zd standalone\n"
                 "    %5zd discrete dt-bound\n"
                 "  %5zd Listening unit%s\n"
                 "  %5zd Spikelogging neuron%s\n"
                 "  %5zd Unit%s being tuned continuously\n"
                 "  %5zd Unit%s being tuned periodically\n"
                 "  %5zd Spontaneously firing neuron%s\n"
                 "  %5zd Multiplexing synapse%s\n"
                 " %6zd vars on integration vector\n\n",
                 name.c_str(), is_diskless ? " (diskless)" : "",
                 units.size(), pl_ending(units.size()),
                 n_total_neurons(), pl_ending(n_total_neurons()),
                 n_total_synapses(), pl_ending(n_total_synapses()),
                 n_hosted_units(),
                 n_standalone_units(),
                 ddtbound_neurons.size() + ddtbound_synapses.size(),
                 listening_units.size(), pl_ending(listening_units.size()),
                 spikelogging_neurons.size(), pl_ending(spikelogging_neurons.size()),
                 units_with_continuous_sources.size(), pl_ending(units_with_continuous_sources.size()),
                 units_with_periodic_sources.size(), pl_ending(units_with_periodic_sources.size()),
                 conscious_neurons.size(), pl_ending(conscious_neurons.size()),
                 multiplexing_synapses.size(), pl_ending(multiplexing_synapses.size()),
                 _var_cnt-1);
        if ( have_ddtb_units )
                fprintf( strm, "Discrete dt: %g msec\n", discrete_dt());
}

void
cnrun::CModel::
dump_state( FILE *strm) const
{
        fprintf( strm,
                 "Model time: %g msec\n"
                 "Integrator dt_min: %g msec, dt_max: %g msec\n"
                 "Logging at: %g msec\n\n",
                 model_time(),
                 dt_min(), dt_max(),
                 options.listen_dt);
}



void
cnrun::CModel::
dump_units( FILE *strm) const
{
        fprintf( strm, "\nUnit types in the model:\n");

        set<int> found_unit_types;
        unsigned p = 0;

        fprintf( strm, "\n===== Neurons:\n");
        for ( auto& U : units )
                if ( U->is_neuron() && found_unit_types.count( U->type()) == 0 ) {
                        found_unit_types.insert( U->type());

                        fprintf( strm, "--- %s: %s\nParameters: ---\n",
                                 U->species(), U->type_description());
                        for ( p = 0; p < U->p_no(); ++p )
                                if ( *U->param_sym(p) != '.' || options.verbosely > 5 )
                                        fprintf( strm, " %-12s %s %s\n",
                                                 U->param_sym(p),
                                                 double_dot_aligned_s( U->param_value(p), 4, 6).c_str(),
                                                 U->param_name(p));
                        fprintf( strm, "Variables: ---\n");
                        for ( p = 0; p < U->v_no(); ++p )
                                if ( *U->var_sym(p) != '.' || options.verbosely > 5 )
                                        fprintf( strm, "%-12s\t= %s %s\n",
                                                 U->var_sym(p),
                                                 double_dot_aligned_s( U->var_value(p), 4, 6).c_str(),
                                                 U->var_name(p));
                }
        fprintf( strm, "\n===== Synapses:\n");
        for ( auto& U : units )
                if ( U->is_synapse() && found_unit_types.count( U->type()) == 0 ) {
                        found_unit_types.insert( U->type());

                        fprintf( strm, "--- %s: %s\nParameters: ---\n",
                                 U->species(), U->type_description());
                        fprintf( strm, "    parameters:\n");
                        for ( p = 0; p < U->p_no(); ++p )
                                if ( *U->param_sym(p) != '.' || options.verbosely > 5 )
                                        fprintf( strm, "%-12s\t= %s %s\n",
                                                 U->param_sym(p),
                                                 double_dot_aligned_s( U->param_value(p), 4, 6).c_str(),
                                                 U->param_name(p));
                        fprintf( strm, "Variables: ---\n");
                        for ( p = 0; p < U->v_no(); ++p )
                                if ( *U->var_sym(p) != '.' || options.verbosely > 5 )
                                        fprintf( strm, "%-12s\t= %s %s\n",
                                                 U->var_sym(p),
                                                 double_dot_aligned_s( U->var_value(p), 4, 6).c_str(),
                                                 U->var_name(p));

                }
        fprintf( strm, "\n");
}

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