/*
 *       File name:  libcn/mmodel-tags.cc
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 *                   building on original work by Thomas Nowotny
 * Initial version:  2014-09-25
 *
 *         Purpose:  CModel household (process_*_tags(), and other methods using regexes).
 *
 *         License:  GPL-2+
 */

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

#include <regex.h>

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


using namespace std;

vector<cnrun::C_BaseUnit*>
cnrun::CModel::
list_units( const string& label) const
{
        vector<C_BaseUnit*> Q;

        regex_t RE;
        if ( 0 != regcomp( &RE, label.c_str(), REG_EXTENDED | REG_NOSUB)) {
                vp( 0, stderr, "Invalid regexp in list_units: \"%s\"\n", label.c_str());
                return move(Q);
        }

        for ( auto& U : units )
                if ( regexec( &RE, U->label(), 0, 0, 0) != REG_NOMATCH )
                        Q.push_back(U);

        return move(Q);
}


// tags

size_t
cnrun::CModel::
process_listener_tags( const list<STagGroupListener> &Listeners)
{
        size_t count = 0;
        regex_t RE;
        for ( auto& P : Listeners ) {
                if (0 != regcomp( &RE, P.pattern.c_str(), REG_EXTENDED | REG_NOSUB)) {
                        vp( 0, stderr, "Invalid regexp in process_listener_tags: \"%s\"\n", P.pattern.c_str());
                        continue;
                }
                for ( auto& Ui : units ) {
                        auto& U = *Ui;
                        if ( regexec( &RE, U._label, 0, 0, 0) == 0 ) {
                                if ( P.invert_option == STagGroup::TInvertOption::no ) {
                                        U.start_listening( P.bits);
                                        vp( 3, " (unit \"%s\" listening%s)\n",
                                            U._label, P.bits & CN_ULISTENING_1VARONLY ? ", to one var only" :"");
                                } else {
                                        U.stop_listening();
                                        vp( 3, " (unit \"%s\" not listening)\n", U._label);
                                }
                                ++count;
                        }
                }
        }

        return count;
}


size_t
cnrun::CModel::
process_spikelogger_tags( const list<STagGroupSpikelogger> &Spikeloggers)
{
        size_t count = 0;
        regex_t RE;
        for ( auto& P : Spikeloggers ) {
                if (0 != regcomp( &RE, P.pattern.c_str(), REG_EXTENDED | REG_NOSUB)) {
                        vp( 0, stderr, "Invalid regexp in process_spikelogger_tags: \"%s\"\n", P.pattern.c_str());
                        continue;
                }
                for ( auto& Ni : standalone_neurons ) {
                        auto& N = *Ni;
                        if ( regexec( &RE, N._label, 0, 0, 0) == 0 ) {
                                if ( P.invert_option == STagGroup::TInvertOption::no ) {
                                        bool log_sdf = !(P.period == 0. || P.sigma == 0.);
                                        if ( ( log_sdf && !N.enable_spikelogging_service(
                                                       P.period, P.sigma, P.from))
                                             or
                                             (!log_sdf && !N.enable_spikelogging_service()) ) {
                                                vp( 0, stderr, "Cannot have \"%s\" log spikes because it is not a conductance-based neuron (of type %s)\n",
                                                         N._label, N.species());
                                                continue;
                                        }
                                } else
                                        N.disable_spikelogging_service();
                                ++count;

                                vp( 3, " (%sabling spike logging for standalone neuron \"%s\")\n",
                                    (P.invert_option == STagGroup::TInvertOption::no) ? "en" : "dis", N._label);
                        }
                }
                for ( auto& Ni : hosted_neurons ) {
                        auto& N = *Ni;
                        if ( regexec( &RE, N._label, 0, 0, 0) == 0 ) {
                                if ( P.invert_option == STagGroup::TInvertOption::no ) {
                                        bool log_sdf = !(P.period == 0. || P.sigma == 0.);
                                        if ( ( log_sdf && !N.enable_spikelogging_service( P.period, P.sigma, P.from))
                                             or
                                             (!log_sdf && !N.enable_spikelogging_service()) ) {
                                                vp( 1, stderr, "Cannot have \"%s\" log spikes because it is not a conductance-based neuron (of type %s)\n",
                                                    N._label, N.species());
                                                return -1;
                                        }
                                } else
                                        N.disable_spikelogging_service();
                                ++count;

                                vp( 3, " (%sabling spike logging for hosted neuron \"%s\")\n",
                                    (P.invert_option == STagGroup::TInvertOption::no) ? "en" : "dis", N._label);
                        }
                }
        }

        return count;
}


size_t
cnrun::CModel::
process_putout_tags( const list<STagGroup> &ToRemove)
{
        size_t count = 0;
      // execute some
        regex_t RE;
        for ( auto& P : ToRemove ) {
                if (0 != regcomp( &RE, P.pattern.c_str(), REG_EXTENDED | REG_NOSUB)) {
                        vp( 0,  stderr, "Invalid regexp in process_putout_tags: \"%s\"\n", P.pattern.c_str());
                        continue;
                }
                auto Ui = units.rbegin();
                while ( Ui != units.rend() ) {
                        ++Ui;
                        auto& U = **prev(Ui);
                        if ( regexec( &RE, U._label, 0, 0, 0) == 0 ) {
                                vp( 2, " (put out unit \"%s\")\n", U._label);
                                delete &U;
                                ++count;
                        }
                }
        }

        cull_blind_synapses();

        return count;
}


size_t
cnrun::CModel::
process_decimate_tags( const list<STagGroupDecimate> &ToDecimate)
{
        size_t count = 0;
      // decimate others
        regex_t RE;
        for ( auto& P : ToDecimate ) {
                if (0 != regcomp( &RE, P.pattern.c_str(), REG_EXTENDED | REG_NOSUB)) {
                        vp( 0, stderr, "Invalid regexp in process_decimate_tags: \"%s\"\n", P.pattern.c_str());
                        continue;
                }

              // collect group
                vector<C_BaseUnit*> dcmgroup;
                for ( auto& U : units )
                        if ( regexec( &RE, U->_label, 0, 0, 0) == 0 )
                                dcmgroup.push_back( U);
                random_shuffle( dcmgroup.begin(), dcmgroup.end());

              // execute
                size_t  to_execute = rint( dcmgroup.size() * P.fraction), n = to_execute;
                while ( n-- ) {
                        delete dcmgroup[n];
                        ++count;
                }

                vp( 3, " (decimated %4.1f%% (%zu units) of %s)\n",
                    P.fraction*100, to_execute, P.pattern.c_str());

        }

        cull_blind_synapses();

        return count;
}






size_t
cnrun::CModel::
process_paramset_static_tags( const list<STagGroupNeuronParmSet> &tags)
{
        size_t count = 0;
        regex_t RE;
        for ( auto& P : tags ) {
                if (0 != regcomp( &RE, P.pattern.c_str(), REG_EXTENDED | REG_NOSUB)) {
                        vp( 0, stderr, "Invalid regexp in process_paramset_static_tags: \"%s\"\n", P.pattern.c_str());
                        continue;
                }

                vector<string> current_tag_assigned_labels;

                for ( auto& Ui : units ) {
                        if ( not Ui->is_neuron() )
                                continue;
                        auto& N = *static_cast<C_BaseNeuron*>(Ui);
                        if ( regexec( &RE, N.label(), 0, 0, 0) == REG_NOMATCH )
                                continue;
                      // because a named parameter can map to a different param_id in different units, rather
                      // do lookup every time

                        int p_d = -1;
                        C_BaseUnit::TSinkType kind = (C_BaseUnit::TSinkType)-1;
                        if ( (p_d = N.param_idx_by_sym( P.parm)) != -1 )
                                kind = C_BaseUnit::SINK_PARAM;
                        else if ( (p_d = N.var_idx_by_sym( P.parm)) != -1 )
                                kind = C_BaseUnit::SINK_VAR;
                        if ( p_d == -1 ) {
                                vp( 1, stderr, "%s \"%s\" (type \"%s\") has no parameter or variable named \"%s\"\n",
                                    N.class_name(), N.label(), N.species(), P.parm.c_str());
                                continue;
                        }

                        switch ( kind ) {
                        case C_BaseUnit::SINK_PARAM:
                                N.param_value(p_d) = (P.invert_option == STagGroup::TInvertOption::no)
                                        ? P.value : __CNUDT[N.type()].stock_param_values[p_d];
                                N.param_changed_hook();
                            break;
                        case C_BaseUnit::SINK_VAR:
                                N.var_value(p_d) = P.value;
                            break;
                        }
                        ++count;

                        current_tag_assigned_labels.push_back( N.label());
                }

                if ( current_tag_assigned_labels.empty() ) {
                        vp( 1,  stderr, "No neuron labelled matching \"%s\"\n", P.pattern.c_str());
                        continue;
                }

                vp( 3, " set [%s]{%s} = %g\n",
                    stilton::str::join(current_tag_assigned_labels, ", ").c_str(),
                    P.parm.c_str(), P.value);
        }

        return count;
}





size_t
cnrun::CModel::
process_paramset_static_tags( const list<STagGroupSynapseParmSet> &tags)
{
        size_t count = 0;
        auto process_tag = [&] (const STagGroupSynapseParmSet& P,
                                regex_t& REsrc, regex_t& REtgt) -> void {
                vector<string> current_tag_assigned_labels;

                bool do_gsyn = (P.parm == "gsyn");

                vp( 5, "== setting %s -> %s {%s} = %g...\n",
                    P.pattern.c_str(), P.target.c_str(), P.parm.c_str(), P.value);

                for ( auto& Uai : units ) {
                        if ( not Uai->is_neuron() )
                                continue;
                        if ( regexec( &REsrc, Uai->label(), 0, 0, 0) == REG_NOMATCH )
                                continue;
                        auto& Ua = *static_cast<C_BaseNeuron*>(Uai);

                        for ( auto& Ubi : units ) {
                                if ( not Ubi->is_neuron() )
                                        continue;
                                if ( regexec( &REtgt, Ubi->label(), 0, 0, 0) == REG_NOMATCH ) /* || Ua == Ub */
                                        continue;
                                auto& Ub = *static_cast<C_BaseNeuron*>(Ubi);
                                auto y = Ua.connects_via(Ub);
                                if ( !y )
                                        continue;

                                if ( do_gsyn ) {
                                        y->set_g_on_target( Ub, P.value);
                                        current_tag_assigned_labels.push_back( y->label());
                                        continue;
                                }

                                int p_d = -1;
                                C_BaseUnit::TSinkType kind = (C_BaseUnit::TSinkType)-1;
                                if ( (p_d = y->param_idx_by_sym( P.parm)) > -1 )
                                        kind = C_BaseUnit::SINK_PARAM;
                                else if ( (p_d = y->var_idx_by_sym( P.parm)) > -1 )
                                        kind = C_BaseUnit::SINK_VAR;
                                if ( p_d == -1 ) {
                                        vp( 1, stderr, "%s \"%s\" (type \"%s\") has no parameter or variable named \"%s\"\n",
                                            y->class_name(), y->label(), y->species(), P.parm.c_str());
                                        continue;
                                }

                                switch ( kind ) {
                                case C_BaseUnit::SINK_PARAM:
                                        if ( y->_targets.size() > 1 )
                                                y = y->make_clone_independent(
                                                        &Ub);  // lest brethren synapses to other targets be clobbered
                                        y->param_value(p_d) = (P.invert_option == STagGroup::TInvertOption::no)
                                                ? P.value : __CNUDT[y->type()].stock_param_values[p_d];
                                        y->param_changed_hook();
                                    break;
                                case C_BaseUnit::SINK_VAR:
                                        y->var_value(p_d) = P.value;
                                    break;
                                }
                                ++count;

                                current_tag_assigned_labels.push_back( y->label());
                        }
                }
                if ( current_tag_assigned_labels.empty() ) {
                        vp( 1, stderr, "No synapse connecting any of \"%s\" to \"%s\"\n", P.pattern.c_str(), P.target.c_str());
                        return;
                }

                vp( 3, " set [%s]{%s} = %g\n",
                    stilton::str::join(current_tag_assigned_labels, ", ").c_str(),
                    P.parm.c_str(), P.value);
        };

        for ( auto& P : tags ) {
                regex_t REsrc, REtgt;
                if (0 != regcomp( &REsrc, P.pattern.c_str(), REG_EXTENDED | REG_NOSUB) ) {  // P->pattern acting as src
                        vp( 0, stderr, "Invalid regexp in process_paramset_static_tags (src): \"%s\"\n", P.pattern.c_str());
                        continue;
                }
                if (0 != regcomp( &REtgt, P.target.c_str(), REG_EXTENDED | REG_NOSUB) ) {
                        vp( 0, stderr, "Invalid regexp in process_paramset_static_tags (tgt): \"%s\"\n", P.target.c_str());
                        continue;
                }

                process_tag( P, REsrc, REtgt);
        }

        coalesce_synapses();

        return count;
}



size_t
cnrun::CModel::
process_paramset_source_tags( const list<STagGroupSource> &tags)
{
        size_t count = 0;
        regex_t RE;
        for ( auto& P : tags ) {
                if (0 != regcomp( &RE, P.pattern.c_str(), REG_EXTENDED | REG_NOSUB)) {
                        vp( 0, stderr, "Invalid regexp in process_paramset_source_tags: \"%s\"\n", P.pattern.c_str());
                        continue;
                }

                for ( auto& U : units ) {
                        if ( regexec( &RE, U->label(), 0, 0, 0) == REG_NOMATCH )
                                continue;

                        int p_d = -1;
                        C_BaseUnit::TSinkType kind = (C_BaseUnit::TSinkType)-1;
                        if ( (p_d = U->param_idx_by_sym( P.parm)) > -1 )
                                kind = C_BaseUnit::SINK_PARAM;
                        else if ( (p_d = U->var_idx_by_sym( P.parm)) > -1 )
                                kind = C_BaseUnit::SINK_VAR;
                        if ( p_d == -1 ) {
                                vp( 1, stderr, "%s \"%s\" (type \"%s\") has no parameter or variable named \"%s\"\n",
                                    U->class_name(), U->label(), U->species(), P.parm.c_str());
                                continue;
                        }

                        if ( P.invert_option == STagGroup::TInvertOption::no ) {
                                U -> attach_source( P.source, kind, p_d);
                                vp( 3, "Connected source \"%s\" to \"%s\"{%s}\n",
                                    P.source->name(), U->label(), P.parm.c_str());
                        } else {
                                U -> detach_source( P.source, kind, p_d);
                                vp( 3, "Disconnected source \"%s\" from \"%s\"{%s}\n",
                                    P.source->name(), U->label(), P.parm.c_str());
                        }
                        ++count;
                }
        }

        return count;
}

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