/*
 *       File name:  libcnlua/commands.cc
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 *                   building on original work by Thomas Nowotny <tnowotny@ucsd.edu>
 * Initial version:  2014-10-09
 *
 *         Purpose:  libcn and some host-side state, exported for use in your lua code.
 *
 *         License:  GPL-2+
 */

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

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

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

using namespace std;
using namespace cnrun;

namespace {

// supporting functions:

const int TWO_ARGS_FOR_ERROR = 2;

int make_error( lua_State *L, const char *fmt, ...)  __attribute__ ((format (printf, 2, 3)));
int make_error( lua_State *L, const char *fmt, ...)
{
        va_list ap;
        va_start (ap, fmt);
        auto s = stilton::str::svasprintf( fmt, ap);
        va_end (ap);

        return  lua_pushnil(L),
                lua_pushstring(L, s.c_str()),
                TWO_ARGS_FOR_ERROR;
}

int check_signature( lua_State* L, const char* fun, const char* sig)
{
        size_t  siglen = strlen(sig),
                nargsin = lua_gettop( L);
        if ( nargsin != siglen )
                return make_error(
                        L, "%s: Expected %zu arg(s), got %zu",
                        fun, siglen, nargsin);

        for ( size_t i = 1; i <= siglen; ++i )
                switch ( sig[i-1] ) {
                case 's':
                        if ( !lua_isstring( L, i) )
                                return make_error(
                                        L, "%s(\"%s\"): Expected a string arg at position %zu",
                                        fun, sig, i);
                    break;
                case 'g':
                case 'd':
                        if ( !lua_isnumber( L, i) )
                                return make_error(
                                        L, "%s(\"%s\"): Expected a numeric arg at position %zu",
                                        fun, sig, i);
                    break;
                case 'p':
                        if ( !lua_islightuserdata( L, i) )
                                return make_error(
                                        L, "%s(\"%s\"): Expected a light user data arg at position %zu",
                                        fun, sig, i);
                    break;
                case 'b':
                        if ( !lua_isboolean( L, i) )
                                return make_error(
                                        L, "%s(\"%s\"): Expected a boolean arg at position %zu",
                                        fun, sig, i);
                    break;
                }
        return 0;
}

}


// here be the commands:
namespace {

#define INTRO_CHECK_SIG(sig)                            \
        if ( check_signature( L, __FUNCTION__, sig) )   \
                return TWO_ARGS_FOR_ERROR;

// the only command not requiring or dealing with context:
int dump_available_units( lua_State *L)
{
        INTRO_CHECK_SIG("");
        cnmodel_dump_available_units();
        return  lua_pushinteger( L, 1),
                lua_pushstring( L, "fafa"),
                2;
}


int get_context( lua_State *L)
{
        INTRO_CHECK_SIG("");

        auto Cp = new CHost (SHostOptions ());

        return  lua_pushinteger( L, 1),
                lua_pushlightuserdata( L, Cp),
                2;
}

#define INTRO_WITH_CONTEXT(sig) \
        INTRO_CHECK_SIG(sig) \
        auto& C = *(CHost*)lua_topointer( L, 1);

int drop_context( lua_State *L)
{
        INTRO_WITH_CONTEXT("p");

        delete &C;  // come what may

        return  lua_pushinteger( L, 1),
                lua_pushstring( L, "fafa"),
                2;
}


#define INTRO_WITH_MODEL_NAME(sig) \
        INTRO_WITH_CONTEXT(sig) \
        const char* model_name = lua_tostring( L, 2);

#define VOID_RETURN \
        return  lua_pushinteger( L, 1), \
                lua_pushstring( L, model_name), \
                2;

#define NUMVAL_RETURN(v) \
        return  lua_pushinteger( L, 1), \
                lua_pushnumber( L, v), \
                2;

int new_model( lua_State *L)
{
        INTRO_WITH_MODEL_NAME("ps");

        if ( C.have_model( model_name) )
                return make_error(
                        L, "%s(): Model named %s already exists",
                        __FUNCTION__, model_name);

        auto M = new CModel(
                model_name,
                new CIntegrateRK65(
                        C.options.integration_dt_min,
                        C.options.integration_dt_max,
                        C.options.integration_dt_cap),
                C.options);
        if ( !M )
                return make_error(
                        L, "%s(): Failed to create a model (%s)",
                        __FUNCTION__, model_name);

        C.new_model(*M);

        return  lua_pushinteger( L, 1),
                lua_pushlightuserdata( L, M),
                2;
}


int delete_model( lua_State *L)
{
        INTRO_WITH_MODEL_NAME("ps");

        C.del_model( model_name);

        VOID_RETURN;
}


int list_models( lua_State *L)
{
        INTRO_WITH_CONTEXT("p");

        lua_pushinteger( L, 1);
        auto MM = C.list_models();
        for ( auto& M : MM )
                lua_pushstring( L, M);
        return  lua_pushinteger( L, MM.size() + 1),
                2;
}


#define INTRO_WITH_MODEL(sig) \
        INTRO_WITH_MODEL_NAME(sig) \
        if ( not C.have_model( model_name) ) \
                return make_error( \
                        L, "%s(): No model named %s", \
                        __FUNCTION__, model_name); \
        auto& M = *C.get_model(model_name);

int import_nml( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char* fname = lua_tostring( L, 3);
        string fname2 = stilton::str::tilda2homedir(fname);

        int ret = M.import_NetworkML( fname2, CModel::TNMLImportOption::merge);
        if ( ret < 0 )
                return make_error(
                        L, "%s(%s): NML import failed from \"%s\" (%d)",
                        __FUNCTION__, model_name, fname, ret);

        M.cull_blind_synapses();

        VOID_RETURN;
}


int export_nml( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char* fname = lua_tostring( L, 3);
        string fname2 = stilton::str::tilda2homedir(fname);

        if ( M.export_NetworkML( fname2) )
                return make_error(
                        L, "%s(%s): NML export failed to \"%s\"",
                        __FUNCTION__, model_name, fname);

        VOID_RETURN;
}


int reset_model( lua_State *L)
{
        INTRO_WITH_MODEL("ps");

        M.reset( CModel::TResetOption::no_params);
        // for with_params, there is revert_unit_parameters()

        VOID_RETURN;
}


int cull_deaf_synapses( lua_State *L)
{
        INTRO_WITH_MODEL("ps");

        M.cull_deaf_synapses();

        VOID_RETURN;
}


int describe_model( lua_State *L)
{
        INTRO_WITH_MODEL("ps");

        M.dump_metrics();
        M.dump_units();
        M.dump_state();

        VOID_RETURN;
}


int get_model_parameter( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const string
                P (lua_tostring( L, 3));

        double g = NAN;
        string s;
        if ( P == "verbosely" ) {
                g = M.options.verbosely;
        } else if ( P == "integration_dt_min" ) {
                g = M.dt_min();
        } else if ( P == "integration_dt_max" ) {
                g = M.dt_min();
        } else if ( P == "integration_dt_cap" ) {
                g = M.dt_min();
        } else if ( P == "listen_dt" ) {
                g = M.options.listen_dt;
        } else if ( P == "listen_mode" ) {
                auto F = [] (bool v) -> char { return v ? '+' : '-'; };
                s = stilton::str::sasprintf(
                        "1%cd%cb%c",
                        F(M.options.listen_1varonly),
                        F(M.options.listen_deferwrite),
                        F(M.options.listen_binary));
        } else if ( P == "sxf_start_delay" ) {
                g = M.options.sxf_start_delay;
        } else if ( P == "sxf_period" ) {
                g = M.options.sxf_period;
        } else if ( P == "sdf_sigma" ) {
                g = M.options.sdf_sigma;
        } else
                return make_error(
                        L, "%s(%s): Unrecognized parameter: %s",
                        __FUNCTION__, model_name, P.c_str());

        return  lua_pushinteger( L, 1),
                s.empty() ? lua_pushnumber( L, g) : (void)lua_pushstring( L, s.c_str()),
                2;
}


int set_model_parameter( lua_State *L)
{
        INTRO_WITH_MODEL("psss");

        const char
                *P = lua_tostring( L, 3),
                *V = lua_tostring( L, 4);

#define ERR_RETURN \
        return make_error( \
                L, "%s(%s): bad value for parameter `%s'", \
                __FUNCTION__, model_name, P)

        if ( 0 == strcmp( P, "verbosely") ) {
                int v;
                if ( 1 != sscanf( V, "%d", &v) )
                        ERR_RETURN;
                C.options.verbosely = M.options.verbosely = v;

        } else if ( 0 == strcmp( P, "integration_dt_min") ) {
                double v;
                if ( 1 != sscanf( V, "%lg", &v) )
                        ERR_RETURN;
                M.set_dt_min( C.options.integration_dt_min = v);

        } else if ( 0 == strcmp( P, "integration_dt_max" ) ) {
                double v;
                if ( 1 != sscanf( V, "%lg", &v) )
                        ERR_RETURN;
                M.set_dt_max( C.options.integration_dt_max = v);

        } else if ( 0 == strcmp( P, "integration_dt_cap" ) ) {
                double v;
                if ( 1 != sscanf( V, "%lg", &v) )
                        ERR_RETURN;
                M.set_dt_cap( C.options.integration_dt_cap = v);

        } else if ( 0 == strcmp( P, "listen_dt") ) {
                double v;
                if ( 1 != sscanf( V, "%lg", &v) )
                        ERR_RETURN;
                C.options.listen_dt = M.options.listen_dt = v;

        } else if ( 0 == strcmp( P, "listen_mode" ) ) {
                const char *p;
                if ( (p = strchr( V, '1')) )
                        M.options.listen_1varonly   = C.options.listen_1varonly   = (*(p+1) != '-');
                if ( (p = strchr( V, 'd')) )
                        M.options.listen_deferwrite = C.options.listen_deferwrite = (*(p+1) != '-');
                if ( (p = strchr( V, 'b')) )
                        M.options.listen_binary     = C.options.listen_binary     = (*(p+1) != '-');
                // better spell out these parameters, ffs

        } else if ( 0 == strcmp( P, "sxf_start_delay" ) ) {
                double v;
                if ( 1 != sscanf( V, "%lg", &v) )
                        ERR_RETURN;
                C.options.sxf_start_delay = M.options.sxf_start_delay = v;

        } else if ( 0 == strcmp( P, "sxf_period" ) ) {
                double v;
                if ( 1 != sscanf( V, "%lg", &v) )
                        ERR_RETURN;
                C.options.sxf_period = M.options.sxf_period = v;

        } else if ( 0 == strcmp( P, "sdf_sigma" ) ) {
                double v;
                if ( 1 != sscanf( V, "%lg", &v) )
                        ERR_RETURN;
                C.options.sdf_sigma = M.options.sdf_sigma = v;
        }
#undef ERR_RETURN

        VOID_RETURN;
}


int advance( lua_State *L)
{
        INTRO_WITH_MODEL("psg");

        const double time_to_go = lua_tonumber( L, 3);
        const double end_time = M.model_time() + time_to_go;
        if ( M.model_time() > end_time )
                return make_error(
                        L, "%s(%s): Cannot go back in time (model is now at %g sec)",
                        __FUNCTION__, model_name, M.model_time());
        if ( !M.advance( time_to_go) )
                return make_error(
                        L, "%s(%s): Failed to advance",
                        __FUNCTION__, model_name);

        VOID_RETURN;
}


int advance_until( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const double end_time = lua_tonumber( L, 3);
        if ( M.model_time() > end_time )
                return make_error(
                        L, "%s(%s): Cannot go back in time (model is now at %g sec)",
                        __FUNCTION__, model_name, M.model_time());
        if ( !M.advance( end_time) )
                return make_error(
                        L, "%s(%s): Failed to advance",
                        __FUNCTION__, model_name);

        VOID_RETURN;
}


// ----------------------------------------

int new_neuron( lua_State *L)
{
        INTRO_WITH_MODEL("psss");

        const char
                *type  = lua_tostring( L, 3),
                *label = lua_tostring( L, 4);

        if ( !M.add_neuron_species(
                     type, label,
                     TIncludeOption::is_last) )
                return make_error(
                        L, "%s(%s): error", __FUNCTION__, model_name);

        VOID_RETURN;
}


int new_synapse( lua_State *L)
{
        INTRO_WITH_MODEL("pssssg");

        const char
                *type = lua_tostring( L, 3),
                *src  = lua_tostring( L, 4),
                *tgt  = lua_tostring( L, 5);
        const double
                g = lua_tonumber( L, 6);

        if ( !M.add_synapse_species(
                     type, src, tgt, g,
                     CModel::TSynapseCloningOption::yes,
                     TIncludeOption::is_last) )
                return make_error(
                        L, "%s(%s): error", __FUNCTION__, model_name);

        VOID_RETURN;
}


int get_unit_properties( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *label = lua_tostring( L, 3);
        auto Up = M.unit_by_label(label);
        if ( Up )
                return  lua_pushnumber( L, 1),
                        lua_pushstring( L, Up->label()),
                        lua_pushstring( L, Up->class_name()),
                        lua_pushstring( L, Up->family()),
                        lua_pushstring( L, Up->species()),
                        lua_pushboolean( L, Up->has_sources()),
                        lua_pushboolean( L, Up->is_not_altered()),
                        7;
        else
                return make_error(
                        L, "%s(%s): No such unit: %s",
                        __FUNCTION__, model_name, label);
}


int get_unit_parameter( lua_State *L)
{
        INTRO_WITH_MODEL("psss");

        const char
                *label = lua_tostring( L, 3),
                *param = lua_tostring( L, 4);
        auto Up = M.unit_by_label(label);
        if ( !Up )
                return make_error(
                        L, "%s(%s): No such unit: %s",
                        __FUNCTION__, model_name, label);
        try {
                return  lua_pushinteger( L, 1),
                        lua_pushnumber( L, Up->get_param_value( param)),
                        2;
        } catch (exception& ex) {
                return make_error(
                        L, "%s(%s): Unit %s (type %s) has no parameter named %s",
                        __FUNCTION__, model_name, label, Up->class_name(), param);
        }
}


int set_unit_parameter( lua_State *L)
{
        INTRO_WITH_MODEL("psssg");

        const char
                *label = lua_tostring( L, 3),
                *param = lua_tostring( L, 4);
        const double
                value = lua_tonumber( L, 5);
        auto Up = M.unit_by_label(label);
        if ( !Up )
                return make_error(
                        L, "%s(%s): No such unit: %s",
                        __FUNCTION__, model_name, label);
        try {
                Up->param_value( param) = value;
        } catch (exception& ex) {
                return make_error(
                        L, "%s(%s): Unit %s (type %s) has no parameter named %s",
                        __FUNCTION__, model_name, label, Up->class_name(), param);
        }

        VOID_RETURN;
}


int get_unit_vars( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *label = lua_tostring( L, 3);
        auto Up = M.unit_by_label(label);
        if ( !Up )
                return make_error(
                        L, "%s(%s): No such unit: %s",
                        __FUNCTION__, model_name, label);

        lua_pushnumber( L, 1);
        for ( size_t i = 0; i < Up->v_no(); ++i )
                lua_pushnumber( L, Up->get_var_value(i));
        return Up->v_no() + 1;
}


int reset_unit( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *label = lua_tostring( L, 3);
        auto Up = M.unit_by_label(label);
        if ( !Up )
                return make_error(
                        L, "%s(%s): No such unit: %s",
                        __FUNCTION__, model_name, label);

        Up -> reset_state();

        VOID_RETURN;
}


// ----------------------------------------

int get_units_matching( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *pattern = lua_tostring( L, 3);
        auto UU = M.list_units( pattern);
        lua_pushinteger( L, 1);
        for ( auto& U : UU )
                lua_pushstring( L, U->label());
        return UU.size() + 1;
}


int get_units_of_type( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *type = lua_tostring( L, 3);
        auto UU = M.list_units();
        lua_pushinteger( L, 1);
        for ( auto& U : UU )
                if ( strcmp( U->species(), type) == 0 )
                        lua_pushstring( L, U->label());
        return UU.size() + 1;
}


int set_matching_neuron_parameter( lua_State *L)
{
        INTRO_WITH_MODEL("psssg");

        const char
                *pattern = lua_tostring( L, 3),
                *param   = lua_tostring( L, 4);
        const double
                value    = lua_tonumber( L, 5);
        list<CModel::STagGroupNeuronParmSet> tags {
                CModel::STagGroupNeuronParmSet (pattern, param, value)};
        int count_set = M.process_paramset_static_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, count_set),
                2;
}


int set_matching_synapse_parameter( lua_State *L)
{
        INTRO_WITH_MODEL("pssssg");

        const char
                *pat_src = lua_tostring( L, 3),
                *pat_tgt = lua_tostring( L, 4),
                *param   = lua_tostring( L, 5);
        const double
                value    = lua_tonumber( L, 6);

        list<CModel::STagGroupSynapseParmSet> tags {
                CModel::STagGroupSynapseParmSet (pat_src, pat_tgt, param, value)};
        int count_set = M.process_paramset_static_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, count_set),
                2;
}


int revert_matching_unit_parameters( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *pattern = lua_tostring( L, 3);

        auto UU = M.list_units( pattern);
        for ( auto& U : UU )
                U->reset_params();

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, UU.size()),
                2;
}


int decimate( lua_State *L)
{
        INTRO_WITH_MODEL("pssg");

        const char
                *pattern = lua_tostring( L, 3);
        const double
                frac     = lua_tonumber( L, 4);
        if ( frac < 0. || frac > 1. )
                return make_error(
                        L, "%s(%s): Decimation fraction (%g) outside [0..1]\n",
                        __FUNCTION__, model_name, frac);

        list<CModel::STagGroupDecimate> tags {{pattern, frac}};
        int affected = M.process_decimate_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


int putout( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *pattern = lua_tostring( L, 3);

        list<CModel::STagGroup> tags {{pattern, CModel::STagGroup::TInvertOption::no}};
        int affected = M.process_putout_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


// ----------------------------------------

int new_tape_source( lua_State *L)
{
        INTRO_WITH_MODEL("psssb");

        const char
                *name  = lua_tostring( L, 3),
                *fname = lua_tostring( L, 4);
        const bool
                looping = lua_toboolean( L, 5);

        if ( M.source_by_id( name) )
                return make_error(
                        L, "%s(%s): Tape source \"%s\" already exists",
                        __FUNCTION__, model_name, name);

        try {
                auto source = new CSourceTape(
                        name, fname,
                        looping ? TSourceLoopingOption::yes : TSourceLoopingOption::no);
                if ( source )
                        M.add_source( source);
                else
                        return make_error(
                                L, "%s(%s): reading from %s, bad data",
                                __FUNCTION__, model_name, fname);
        } catch (exception& ex) {
                return make_error(
                        L, "%s(%s): %s, %s: %s",
                        __FUNCTION__, model_name, name, fname, ex.what());
        }

        VOID_RETURN;
}


int new_periodic_source( lua_State *L)
{
        INTRO_WITH_MODEL("psssbg");

        const char
                *name   = lua_tostring( L, 3),
                *fname  = lua_tostring( L, 4);
        const bool
                looping = lua_toboolean( L, 5);
        const double
                period  = lua_tonumber( L, 6);

        if ( M.source_by_id( name) )
                return make_error(
                        L, "%s(%s): Periodic source \"%s\" already exists",
                        __FUNCTION__, model_name, name);

        try {
                auto source = new CSourcePeriodic(
                        name, fname,
                        looping ? TSourceLoopingOption::yes : TSourceLoopingOption::no,
                        period);
                if ( source )
                        M.add_source( source);
                else
                        return make_error(
                                L, "%s(%s): reading from %s, bad data",
                                __FUNCTION__, model_name, fname);
        } catch (exception& ex) {
                return make_error(
                        L, "%s(%s): %s, %s: %s",
                        __FUNCTION__, model_name, name, fname, ex.what());
        }

        VOID_RETURN;
}


int new_noise_source( lua_State *L)
{
        INTRO_WITH_MODEL("pssgggs");

        const char
                *name   = lua_tostring( L, 3);
        const double
                &min    = lua_tonumber( L, 4),
                &max    = lua_tonumber( L, 5),
                &sigma  = lua_tonumber( L, 6);
        const string
                &distribution = lua_tostring( L, 7);

        if ( M.source_by_id( name) )
                return make_error(
                        L, "%s(%s): Noise source \"%s\" already exists",
                        __FUNCTION__, model_name, name);

        try {
                auto source = new CSourceNoise(
                        name, min, max, sigma, CSourceNoise::distribution_by_name(distribution));
                if ( source )
                        M.add_source( source);
                else
                        return make_error(
                                L, "%s(%s): bad data",
                                __FUNCTION__, model_name);
        } catch (exception& ex) {
                return make_error(
                        L, "%s(%s): %s: %s",
                        __FUNCTION__, model_name, name, ex.what());
        }

        VOID_RETURN;
}


int get_sources( lua_State *L)
{
        INTRO_WITH_MODEL("ps");

        lua_pushinteger( L, 1);
        for ( auto& S : M.sources() )
                lua_pushstring( L, S->name());
        return M.sources().size() + 1;
}


int connect_source( lua_State *L)
{
        INTRO_WITH_MODEL("pssss");

        const char
                *label  = lua_tostring( L, 3),
                *parm   = lua_tostring( L, 4),
                *source = lua_tostring( L, 5);
        C_BaseSource *S = M.source_by_id( source);
        if ( !S )
                return make_error(
                        L, "%s(%s): No such stimulation source: %s",
                        __FUNCTION__, model_name, source);
        // cannot check whether units matching label indeed have a parameter so named
        list<CModel::STagGroupSource> tags {
                {label, parm, S, CModel::STagGroup::TInvertOption::no}};
        int affected = M.process_paramset_source_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


int disconnect_source( lua_State *L)
{
        INTRO_WITH_MODEL("pssss");

        const char
                *label  = lua_tostring( L, 3),
                *parm   = lua_tostring( L, 4),
                *source = lua_tostring( L, 5);
        C_BaseSource *S = M.source_by_id( source);
        if ( !S )
                return make_error(
                        L, "%s(%s): No such stimulation source: %s",
                        __FUNCTION__, model_name, source);
        // cannot check whether units matching label indeed have a parameter so named
        list<CModel::STagGroupSource> tags {
                {label, parm, S, CModel::STagGroup::TInvertOption::yes}};
        int affected = M.process_paramset_source_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


// ----------------------------------------

int start_listen( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *pattern = lua_tostring( L, 3);
        list<CModel::STagGroupListener> tags {
                CModel::STagGroupListener (
                        pattern, (0
                                  | (M.options.listen_1varonly ? CN_ULISTENING_1VARONLY : 0)
                                  | (M.options.listen_deferwrite ? CN_ULISTENING_DEFERWRITE : 0)
                                  | (M.options.listen_binary ? CN_ULISTENING_BINARY : CN_ULISTENING_DISK)),
                        CModel::STagGroup::TInvertOption::no)};
        int affected = M.process_listener_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


int stop_listen( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *pattern = lua_tostring( L, 3);
        list<CModel::STagGroupListener> tags {
                CModel::STagGroupListener (
                        pattern, (0
                                  | (M.options.listen_1varonly ? CN_ULISTENING_1VARONLY : 0)
                                  | (M.options.listen_deferwrite ? CN_ULISTENING_DEFERWRITE : 0)
                                  | (M.options.listen_binary ? CN_ULISTENING_BINARY : CN_ULISTENING_DISK)),
                        CModel::STagGroup::TInvertOption::yes)};
        int affected = M.process_listener_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


int start_log_spikes( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *pattern = lua_tostring( L, 3);
        list<CModel::STagGroupSpikelogger> tags {{
                        pattern,
                        M.options.sxf_period, M.options.sdf_sigma, M.options.sxf_start_delay,
                        CModel::STagGroup::TInvertOption::no}};
        int affected = M.process_spikelogger_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


int stop_log_spikes( lua_State *L)
{
        INTRO_WITH_MODEL("pss");

        const char
                *pattern = lua_tostring( L, 3);
        list<CModel::STagGroupSpikelogger> tags {{
                        pattern,
                        M.options.sxf_period, M.options.sdf_sigma, M.options.sxf_start_delay,
                        CModel::STagGroup::TInvertOption::yes}};
        int affected = M.process_spikelogger_tags( tags);

        return  lua_pushinteger( L, 1),
                lua_pushinteger( L, affected),
                2;
}


// all together now:
const struct luaL_Reg cnlib [] = {
#define BLOOP(X) {#X, X}
        BLOOP(dump_available_units),
        BLOOP(get_context),
        BLOOP(drop_context),
        BLOOP(new_model),
        BLOOP(delete_model),
        BLOOP(list_models),
        BLOOP(import_nml),
        BLOOP(export_nml),
        BLOOP(reset_model),
        BLOOP(cull_deaf_synapses),
        BLOOP(describe_model),
        BLOOP(get_model_parameter),
        BLOOP(set_model_parameter),
        BLOOP(advance),
        BLOOP(advance_until),

        BLOOP(new_neuron),
        BLOOP(new_synapse),
        BLOOP(get_unit_properties),
        BLOOP(get_unit_parameter),
        BLOOP(set_unit_parameter),
        BLOOP(get_unit_vars),
        BLOOP(reset_unit),

        BLOOP(get_units_matching),
        BLOOP(get_units_of_type),
        BLOOP(set_matching_neuron_parameter),
        BLOOP(set_matching_synapse_parameter),
        BLOOP(revert_matching_unit_parameters),
        BLOOP(decimate),
        BLOOP(putout),

        BLOOP(new_tape_source),
        BLOOP(new_periodic_source),
        BLOOP(new_noise_source),
        BLOOP(get_sources),
        BLOOP(connect_source),
        BLOOP(disconnect_source),

        BLOOP(start_listen),
        BLOOP(stop_listen),
        BLOOP(start_log_spikes),
        BLOOP(stop_log_spikes),
#undef BLOOP
        {NULL, NULL}
};

}


extern "C" {

int luaopen_cnrun( lua_State *L)
{
#ifdef HAVE_LUA_52
        printf( "newlib cnrun\n");
        luaL_newlib(L, cnlib);
#else  // this must be 5.1
        printf( "register cnrun\n");
        luaL_register(L, "cnlib", cnlib);
#endif
        return 1;
}

}


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