/*
 *       File name:  tools/hh-latency-estimator.cc
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 * Initial version:  2009-09-12
 *
 *         Purpose:  convenience to estiate latency to first spike of a HH neuron
 *                   in response to continuous Poisson stimulation
 *
 *         License:  GPL-2+
 */

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

#include <unistd.h>

#include "libcnrun/hosted-neurons.hh"
#include "libcnrun/standalone-synapses.hh"

#include "libcnrun/model.hh"
#include "libcnrun/types.hh"

using namespace cnrun;

CModel *Model;

enum TOscillatorType { S_POISSON = 0, S_PULSE = 1 };
enum TIncrOpType { INCR_ADD, INCR_MULT };

struct SOptions {
        double  pulse_f_min,
                pulse_f_max,
                pulse_df,
                syn_g,
                syn_beta,
                syn_trel;
        bool    enable_listening;

        size_t  n_repeats;
        const char
                *irreg_mag_fname,
                *irreg_cnt_fname;

        TOscillatorType
                src_type;
        TIncrOpType
                incr_op;

        SOptions()
               : pulse_f_min (-INFINITY),
                 pulse_f_max (-INFINITY),
                 pulse_df (-INFINITY),
                 syn_g (-INFINITY),
                 syn_beta (-INFINITY),
                 syn_trel (-INFINITY),
                 enable_listening (false),
                 n_repeats (1),
                 irreg_mag_fname (nullptr),
                 irreg_cnt_fname (nullptr),
                 src_type (S_POISSON)
                {}
};

SOptions Options;

const char* const pulse_parm_sel[] = { "lambda", "f" };



static int parse_options( int argc, char **argv);
#define CNRUN_CLPARSE_HELP_REQUEST  -1
#define CNRUN_CLPARSE_ERROR         -2

static void usage( const char *argv0);


#define CNRUN_EARGS       -1
#define CNRUN_ESETUP      -2
#define CNRUN_ETRIALFAIL  -3

int
main( int argc, char *argv[])
{
//        cout << "\nHH latency estimator  compiled " << __TIME__ << " " << __DATE__ << endl;

        if ( argc == 1 ) {
                usage( argv[0]);
                return 0;
        }

        int retval = 0;

        switch ( parse_options( argc, argv) ) {
        case CNRUN_CLPARSE_ERROR:
                cerr << "Problem parsing command line or sanitising values\n"
                        "Use -h for help\n";
                return CNRUN_EARGS;
        case CNRUN_CLPARSE_HELP_REQUEST:
                usage( argv[0]);
                return 0;
        }

      // create and set up the model
        Model = new CModel(
                "hh-latency",
                new CIntegrateRK65(
                        1e-6, .5, 5, 1e-8,  1e-12, 1e-6, true),
                SModelOptions ());
        if ( !Model ) {
                cerr << "Failed to create a model\n";
                return CNRUN_ESETUP;
        }

        Model->options.verbosely = 0;

      // add our three units
        CNeuronHH_d  *hh    = new CNeuronHH_d( "HH", 0.2, 0.1, 0.3, Model, CN_UOWNED);
        C_BaseNeuron *pulse = (Options.src_type == S_PULSE)
                ? static_cast<C_BaseNeuron*>(new CNeuronDotPulse( "Pulse", 0.1, 0.2, 0.3, Model, CN_UOWNED))
                : static_cast<C_BaseNeuron*>(new COscillatorDotPoisson( "Pulse", 0.1, 0.2, 0.3, Model, CN_UOWNED));
        CSynapseMxAB_dd *synapse = new CSynapseMxAB_dd( pulse, hh, Options.syn_g, Model, CN_UOWNED);

      // enable_spikelogging_service
        hh -> enable_spikelogging_service();

        if ( Options.enable_listening ) {
                hh -> start_listening( CN_ULISTENING_DISK | CN_ULISTENING_1VARONLY);
                pulse -> start_listening( CN_ULISTENING_DISK);
                synapse -> start_listening( CN_ULISTENING_DISK);
                Model->options.listen_dt = 0.;
        }

      // assign user-supplied values to parameters: invariant ones first
        if ( Options.syn_beta != -INFINITY )
                synapse->param_value("beta") = Options.syn_beta;
        if ( Options.syn_trel != -INFINITY )
                synapse->param_value("trel") = Options.syn_trel;

      // do trials
        size_t  n_spikes;
        double  warmup_time = 30;

        size_t  i;

        size_t  n_steps = 1 + ((Options.incr_op == INCR_ADD)
                               ? (Options.pulse_f_max - Options.pulse_f_min) / Options.pulse_df
                               : log(Options.pulse_f_max / Options.pulse_f_min) / log(Options.pulse_df));

        double  frequencies[n_steps];
        for ( i = 0; i < n_steps; i++ )
                frequencies[i] = (Options.incr_op == INCR_ADD)
                        ? Options.pulse_f_min + i*Options.pulse_df
                        : Options.pulse_f_min * pow( Options.pulse_df, (double)i);
        vector<double>
                irreg_mags[n_steps];
        size_t  irreg_counts[n_steps];
        memset( irreg_counts, 0, n_steps*sizeof(size_t));

        double  latencies[n_steps];

        for ( size_t trial = 0; trial < Options.n_repeats; trial++ ) {
                memset( latencies, 0, n_steps*sizeof(double));

                for ( i = 0; i < n_steps; i++ ) {

                        if ( Options.enable_listening ) {
                                char label[C_BaseUnit::max_label_size];
                                snprintf( label, C_BaseUnit::max_label_size, "pulse-%06g", frequencies[i]);
                                pulse->set_label( label);
                                snprintf( label, C_BaseUnit::max_label_size, "hh-%06g", frequencies[i]);
                                hh->set_label( label);
                                snprintf( label, C_BaseUnit::max_label_size, "synapse-%06g", frequencies[i]);
                                synapse->set_label( label);
                        }
                        Model->reset();  // will reset model_time, preserve params, and is a generally good thing

                        pulse->param_value( pulse_parm_sel[Options.src_type]) = 0;

                      // warmup
                        Model->advance( warmup_time);
                        if ( hh->spikelogger_agent()->spike_history.size() )
                                printf( "What? %zd spikes already?\n", hh->spikelogger_agent()->spike_history.size());
                      // calm down the integrator
                        Model->set_dt( Model->dt_min());
                      // assign trial values
                        pulse->param_value(pulse_parm_sel[Options.src_type]) = frequencies[i];
                      // go
                        Model->advance( 100);

                      // collect latency: that is, the time of the first spike
                        latencies[i] = (( n_spikes = hh->spikelogger_agent()->spike_history.size() )
                                   ? *(hh->spikelogger_agent()->spike_history.begin()) - warmup_time
                                   : 999);

                        printf( "%g\t%g\t%zu\n", frequencies[i], latencies[i], n_spikes);
                }

                printf( "\n");
                for ( i = 1; i < n_steps; i++ )
                        if ( latencies[i] > latencies[i-1] ) {
                                irreg_mags[i].push_back( (latencies[i] - latencies[i-1]) / latencies[i-1]);
                                irreg_counts[i]++;
                        }
        }


        {
                ostream *irrmag_strm = Options.irreg_mag_fname ? new ofstream( Options.irreg_mag_fname) : &cout;

                (*irrmag_strm) << "#<at>\t<irreg_mag>\n";
                for ( i = 0; i < n_steps; i++ )
                        if ( irreg_mags[i].size() )
                                for ( size_t j = 0; j < irreg_mags[i].size(); j++ )
                                        (*irrmag_strm) << frequencies[i] << '\t' << irreg_mags[i][j] << endl;

                if ( Options.irreg_mag_fname )
                        delete irrmag_strm;
        }
        {
                ostream *irrcnt_strm = Options.irreg_cnt_fname ? new ofstream( Options.irreg_cnt_fname) : &cout;

                (*irrcnt_strm) << "#<at>\t<cnt>\n";
                for ( i = 0; i < n_steps; i++ )
                        (*irrcnt_strm) << frequencies[i] << '\t' << irreg_counts[i] << endl;

                if ( Options.irreg_cnt_fname )
                        delete irrcnt_strm;
        }
        delete Model;

        return retval;
}







static void
usage( const char *argv0)
{
        cout << "Usage: " << argv0 << "-f...|-l...  [-y...]\n" <<
                "Stimulation intensity to estimate the response latency for:\n"
                " -f <double f_min>:<double f_incr>:<double f_max>\n"
                "\t\t\tUse a DotPulse oscillator, with these values for f, or\n"
                " -l <double f_min>:<double f_incr>:<double f_max>\n"
                "\t\t\tUse a DotPoisson oscillator, with these values for lambda\n"
                "Synapse parameters:\n"
                " -yg <double>\tgsyn (required)\n"
                " -yb <double>\tbeta\n"
                " -yr <double>\ttrel\n"
                "\n"
                " -o\t\t\tWrite unit variables\n"
                " -c <uint>\t\tRepeat this many times\n"
                " -T <fname>\tCollect stats on irreg_cnt to fname\n"
                " -S <fname>\tCollect stats on irreg_mags to fname\n"
                "\n"
                " -h \t\tDisplay this help\n"
                "\n";
}






static int
parse_options( int argc, char **argv)
{
        int             c;

        while ( (c = getopt( argc, argv, "f:l:y:oc:S:T:h")) != -1 )
                switch ( c ) {
                case 'y':
                        switch ( optarg[0] ) {
                        case 'g':
                                if ( sscanf( optarg+1, "%lg", &Options.syn_g) != 1 ) {
                                        cerr << "-yg takes a double\n";
                                        return CNRUN_CLPARSE_ERROR;
                                }
                            break;
                        case 'b':
                                if ( sscanf( optarg+1, "%lg", &Options.syn_beta) != 1 ) {
                                        cerr << "-yb takes a double\n";
                                        return CNRUN_CLPARSE_ERROR;
                                }
                            break;
                        case 'r':
                                if ( sscanf( optarg+1, "%lg", &Options.syn_trel) != 1 ) {
                                        cerr << "-yr takes a double\n";
                                        return CNRUN_CLPARSE_ERROR;
                                }
                            break;
                        default:
                                cerr << "Unrecognised option modifier for -y\n";
                            return CNRUN_CLPARSE_ERROR;
                        }
                    break;

                case 'f':
                case 'l':
                        if ( (Options.incr_op = INCR_ADD,
                              (sscanf( optarg, "%lg:%lg:%lg",
                                       &Options.pulse_f_min, &Options.pulse_df, &Options.pulse_f_max) == 3))
                                ||
                             (Options.incr_op = INCR_MULT,
                              (sscanf( optarg, "%lg*%lg:%lg",
                                       &Options.pulse_f_min, &Options.pulse_df, &Options.pulse_f_max) == 3)) ) {

                                Options.src_type = (c == 'f') ? S_PULSE : S_POISSON;

                        } else {
                                cerr << "Expecting all three parameter with -{f,l} min{:,*}incr:max\n";
                                return CNRUN_CLPARSE_ERROR;
                        }
                    break;

                case 'o':
                        Options.enable_listening = true;
                    break;

                case 'c':
                        Options.n_repeats = strtoul( optarg, nullptr, 10);
                    break;

                case 'S':
                        Options.irreg_mag_fname = optarg;
                    break;
                case 'T':
                        Options.irreg_cnt_fname = optarg;
                    break;

                case 'h':
                        return CNRUN_CLPARSE_HELP_REQUEST;
                case '?':
                default:
                        return CNRUN_CLPARSE_ERROR;
                }

        if ( Options.pulse_f_min == -INFINITY ||
             Options.pulse_f_max == -INFINITY ||
             Options.pulse_df    == -INFINITY ) {
                cerr << "Oscillator type (with -f or -l) not specified\n";
                return CNRUN_EARGS;
        }

        return 0;
}

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