/*
 *       File name:  libcn/sources.cc
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 *                   building on original work by Thomas Nowotny <tnowotny@ucsd.edu>
 * Initial version:  2010-02-24
 *
 *         Purpose:  External stimulation sources (periodic, tape, noise).
 *
 *         License:  GPL-2+
 */

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

#include <cmath>
#include <sys/time.h>
#include <iostream>
#include <fstream>
#include <limits>
#include <gsl/gsl_randist.h>

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


using namespace std;

const char* const
cnrun::C_BaseSource::
type_s( TSourceType type)
{
        switch ( type ) {
        case TSourceType::null:       return "Null";
        case TSourceType::tape:       return "Tape";
        case TSourceType::periodic:   return "Periodic";
        case TSourceType::function:   return "Function";
        case TSourceType::noise:      return "Noise";
        }
        return "??";
}



cnrun::CSourceTape::
CSourceTape (const string& name_, const string& fname_, TSourceLoopingOption is_looping_)
      : C_BaseSource (name_, TSourceType::tape), is_looping (is_looping_),
        _fname (fname_)
{
        ifstream ins (stilton::str::tilda2homedir( _fname).c_str());
        if ( !ins.good() ) {
                throw stilton::str::sasprintf(
                        "Tape source file (\"%s\") not good", fname_.c_str());
        }
        skipws(ins);

        while ( !ins.eof() && ins.good() ) {
                while ( ins.peek() == '#' || ins.peek() == '\n' )
                        ins.ignore( numeric_limits<streamsize>::max(), '\n');
                double timestamp, datum;
                ins >> timestamp >> datum;
                _values.push_back( pair<double,double>(timestamp, datum));
        }

        if ( _values.size() == 0 ) {
                fprintf( stderr, "No usable values in \"%s\"\n", _fname.c_str());
                return;
        }

        _I = _values.begin();
}


void
cnrun::CSourceTape::
dump( FILE *strm) const
{
        fprintf( strm, "%s (%s) %zu values from %s%s\n",
                 name(), type_s(),
                 _values.size(), _fname.c_str(),
                 (is_looping == TSourceLoopingOption::yes) ? "" : " (looping)");
}




cnrun::CSourcePeriodic::
CSourcePeriodic (const string& name_, const string& fname_, TSourceLoopingOption is_looping_,
                 double period_)
        : C_BaseSource (name_, TSourceType::periodic), is_looping (is_looping_),
          _fname (fname_),
          _period (period_)
{
        ifstream ins( stilton::str::tilda2homedir(fname_).c_str());
        if ( !ins.good() ) {
                throw stilton::str::sasprintf(
                        "Periodic source file (\"%s\") not good", fname_.c_str());
        }
        skipws(ins);

        while ( ins.peek() == '#' || ins.peek() == '\n' )
                ins.ignore( numeric_limits<streamsize>::max(), '\n');

        if ( !isfinite(_period) || _period <= 0. ) {
                ins >> _period;
                if ( !isfinite(_period) || _period <= 0. ) {
                        throw stilton::str::sasprintf(
                                "Period undefined for source \"%s\"", _fname.c_str());
                }
        }

        while ( true ) {
                while ( ins.peek() == '#' || ins.peek() == '\n' )
                        ins.ignore( numeric_limits<streamsize>::max(), '\n');
                double datum;
                ins >> datum;
                if ( ins.eof() || !ins.good() )
                        break;
                _values.push_back( datum);
        }

        if ( _values.size() < 2 ) {
                throw stilton::str::sasprintf(
                        "Need at least 2 scheduled values in \"%s\"\n", _fname.c_str());
        }
}



void
cnrun::CSourcePeriodic::
dump( FILE *strm) const
{
        fprintf( strm, "%s (%s) %zu values at %g from %s%s\n",
                 name(), type_s(),
                 _values.size(), _period, _fname.c_str(),
                 (is_looping == TSourceLoopingOption::yes) ? "" : " (looping)");
}



void
cnrun::CSourceFunction::
dump( FILE *strm) const
{
        fprintf( strm, "%s (%s) (function)\n",
                 name(), type_s());
}



const char* const
cnrun::CSourceNoise::
distribution_s( TDistribution type)
{
        switch ( type ) {
        case TDistribution::uniform: return "uniform";
        case TDistribution::gaussian: return "gaussian";
        }
        return "??";
}


cnrun::CSourceNoise::TDistribution
cnrun::CSourceNoise::
distribution_by_name( const string& s)
{
        for ( auto d : {TDistribution::uniform, TDistribution::gaussian} )
                if ( s == distribution_s( d) )
                        return d;
        throw stilton::str::sasprintf( "Invalid distribution name: %s", s.c_str());
}


cnrun::CSourceNoise::
CSourceNoise (const string& name_,
              double min_, double max_, double sigma_,
              TDistribution dist_type_,
              int seed)
      : C_BaseSource (name_, TSourceType::noise),
        _min (min_), _max (max_),
        _sigma (sigma_),
        _dist_type (dist_type_)
{
        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);
}

double
cnrun::CSourceNoise::
operator() ( double unused)
{
        switch ( _dist_type ) {
        case TDistribution::uniform:   return gsl_rng_uniform( _rng) * (_max - _min) + _min;
        case TDistribution::gaussian:  return gsl_ran_gaussian( _rng, _sigma) + (_max - _min)/2;
        }
        return 42.;
}


cnrun::CSourceNoise::
~CSourceNoise ()
{
        gsl_rng_free( _rng);
}


void
cnrun::CSourceNoise::
dump( FILE *strm) const
{
        fprintf( strm, "%s (%s) %s in range %g:%g (sigma = %g)\n",
                 name(), type_s(),
                 distribution_s(_dist_type), _min, _max, _sigma);
}


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