/*
 *       File name:  libcn/base-unit.hh
 *         Project:  cnrun
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 *                   building on original work by Thomas Nowotny <tnowotny@ucsd.edu>
 * Initial version:  2008-08-02
 *
 *         Purpose:  unit base class
 *
 *         License:  GPL-2+
 */

#ifndef CNRUN_LIBCN_BASEUNIT_H_
#define CNRUN_LIBCN_BASEUNIT_H_

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

#include <fstream>
#include <cstring>
#include <vector>
#include <list>

#include "libstilton/lang.hh"
#include "libstilton/string.hh"
#include "forward-decls.hh"
#include "types.hh"
#include "sources.hh"


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

namespace cnrun {

namespace global {
extern unsigned short precision;
extern int verbosely;
}

// for all units
#define CN_UERROR                        (1 << 0)
#define CN_UOWNED                        (1 << 1)
#define CN_UHASPARAMRANGE                (1 << 2)
#define CN_ULISTENING_MEM                (1 << 3)
#define CN_ULISTENING_DISK               (1 << 4)
#define CN_ULISTENING_1VARONLY           (1 << 5)
#define CN_ULISTENING_DEFERWRITE         (1 << 6)
#define CN_ULISTENING_BINARY             (1 << 7)
//#define CN_NDYNPARAMS                        (1 << 8)

// only for neurons
#define CN_NFIRING                       (1 <<  9)  // firing now
#define CN_NREFRACT                      (1 << 10)  // in refractory phase now


// the base unit provides the methods for the following:
// * classification;
// * access to parameters, tape reader and range interface;
// * attachment to the mother model;
// * listening, i.e., keeping a history of vars along a timeline;
class C_BaseUnit {

        DELETE_DEFAULT_METHODS (C_BaseUnit)

        friend class CModel;
        friend class SSpikeloggerService;

    public:
        static const constexpr size_t max_label_size = 40;

    protected:
        C_BaseUnit (TUnitType, const string& label,
                    CModel*, int s_mask);
    public:
        virtual ~C_BaseUnit();  // surely virtual

      // written variables precision
        unsigned short precision;

        int     status() const  {  return _status; }
        TUnitType type() const  {  return _type;   }

      // classification
        int  traits()        const {  return __CNUDT[_type].traits;                  }
        bool is_hostable()   const {  return __CNUDT[_type].traits & UT_HOSTED;      }
        bool is_ddtbound()   const {  return __CNUDT[_type].traits & UT_DDTSET;      }
        bool is_neuron()     const {  return _type >= NT_FIRST && _type <= NT_LAST;  }
        bool is_synapse()    const {  return _type >= YT_FIRST && _type <= YT_LAST;  }
        bool is_oscillator() const {  return __CNUDT[_type].traits & UT_OSCILLATOR;  }
        bool is_conscious()  const {  return is_oscillator();                        }

        unsigned long serial() const
                {  return _serial_id;  }
        const char *label() const  // for synapses, it is "%s:%d", src->label, targets.size()
                {  return _label;  }
        void set_label( const string& new_label)
                {  strncpy( _label, new_label.c_str(), max_label_size-1); }

        const char *class_name() const
                {  return is_neuron() ? "Neuron" : "Synapse";  }
        const char *species() const
                {  return __CNUDT[_type].species;              }
        const char *family() const
                {  return __CNUDT[_type].family;               }
        const char *type_description() const
                {  return __CNUDT[_type].description;          }

      // parent model
        const CModel&
        parent_model() const        { return *M; }
        double
        model_time() const;  // defined in model.h

        bool is_owned() const       { return _status & CN_UOWNED; }

      // parameter & variable names and symbols
        const char *const param_name( size_t i)       const { return __CNUDT[_type].stock_param_names[i]; }
        const char *const param_sym( size_t i)        const { return __CNUDT[_type].stock_param_syms[i];  }
        int param_idx_by_sym( const string&) const __attribute__ ((pure));

        const char *const var_name( size_t i)         const { return __CNUDT[_type].stock_var_names[i];   }
        const char *const var_sym( size_t i)          const { return __CNUDT[_type].stock_var_syms[i];    }
        int var_idx_by_sym( const string&) const __attribute__ ((pure));

        unsigned short v_no() const        { return __CNUDT[_type].vno; }
        unsigned short p_no() const        { return __CNUDT[_type].pno; }

      // purity checks
        bool is_not_altered() const
                {
                        return (memcmp( P.data(), __CNUDT[_type].stock_param_values,
                                       sizeof (double) * p_no()) == 0) &&
                                !has_sources();
                }
        bool has_same_params( const C_BaseUnit &rv) const
                {
                        return _type == rv._type &&
                                memcmp( P.data(), rv.P.data(), sizeof (double) * p_no()) == 0;
                }
        bool has_sources() const __attribute__ ((pure))
                {
                        return not _sources.empty();
                }
        bool has_same_sources( const C_BaseUnit &rv) const __attribute__ ((pure))
                {
                        return _sources == rv._sources;
                        // not sure taking the order of otherwise identical sources should matter
                }
        bool is_identical( const C_BaseUnit &rv) const __attribute__ ((pure))
                {
                        return _type == rv._type && has_same_params(rv) &&
                                ((has_sources() && has_same_sources(rv)) ||
                                 (!has_sources() && !rv.has_sources()));
                }

      // parameters
        double
        get_param_value( size_t p) const
                {  return P[p];  }

        double
        get_param_value( const string& sym) const
                {
                        int id = param_idx_by_sym( sym);
                        if ( unlikely (id == -1) )
                                throw sasprintf( "Bad parameter name \"%s\" for unit \"%s\"", sym.c_str(), _label);
                        return P[id];
                }

        double&
        param_value( size_t p)
                {
                        return P[p];
                }

        double&
        param_value( const string& sym)
                {
                        int id = param_idx_by_sym( sym);
                        if ( unlikely (id == -1) )
                                throw sasprintf( "Bad parameter name \"%s\" for unit \"%s\"",
                                                 sym.c_str(), _label);
                        return P[id];
                }

        void
        reset_params()
                {
                        P.resize( p_no());
                        memcpy( P.data(), __CNUDT[_type].stock_param_values,
                                sizeof(double) * p_no());
                        param_changed_hook();
                }

      // variables: differs per hosted or standalone
        virtual double &var_value( size_t) = 0;
        virtual const double &get_var_value( size_t) const = 0;
        virtual void reset_vars() = 0;
        virtual void reset_state();

        virtual void dump( bool with_params = false, FILE *strm = stdout) const;

      // state history
        bool is_listening() const
                {
                        return _status & (CN_ULISTENING_DISK | CN_ULISTENING_MEM);
                }
        void start_listening( int mask = 0 | CN_ULISTENING_DISK);
        void stop_listening();
        void restart_listening()
                {
                        int lbits = _status & (CN_ULISTENING_DISK | CN_ULISTENING_MEM
                                               | CN_ULISTENING_1VARONLY | CN_ULISTENING_DEFERWRITE);
                        stop_listening();
                        start_listening( lbits);
                }
        void pause_listening();
        void resume_listening();

        void tell();

        const vector<double>*
        listener_mem() const
                { return _listener_mem; }

      // source interface
        enum TSinkType { SINK_PARAM, SINK_VAR };

        template <class T>
        struct SSourceInterface {
            friend class C_BaseUnit;
            friend class CModel;
            private:
                C_BaseSource *source;
                TSinkType sink_type;
                unsigned short idx;

                SSourceInterface (T *insource, TSinkType insink_type, unsigned short inidx)
                      : source (insource), sink_type (insink_type), idx (inidx)
                        {}
            public:
                bool operator== ( const SSourceInterface &rv) const
                        {
                                return  source    == rv.source &&
                                        sink_type == rv.sink_type &&
                                        idx       == rv.idx;
                        }
        };
        template <class T>
        void attach_source( T *s, TSinkType t, unsigned short idx);
        void detach_source( C_BaseSource*, TSinkType, size_t idx);

        void apprise_from_sources();
        virtual void param_changed_hook()
                {}

    protected:
        TUnitType
                _type;  // will look up p, pno and vno from __CNUDT using _type as index
        int     _status;

        unsigned long
                _serial_id;  // assigned incrementally as read by import_NetworkML
        char    _label[max_label_size];

        CModel  *M;

      // private copy of params
        vector<double> P;

        list<SSourceInterface<C_BaseSource>>
                _sources;

    private:
      // where vars are written by tell()
        int _binwrite_handle;
        ofstream *_listener_disk;
      // ... and/or stored, in a diskless model
        vector<double> *_listener_mem;
};

}

#endif

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