/*
 *  network.h
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  NEST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef NETWORK_H
#define NETWORK_H
#include "config.h"
#include <vector>
#include <string>
#include <typeinfo>
#include "nest.h"
#include "model.h"
#include "scheduler.h"
#include "exceptions.h"
#include "proxynode.h"
#include "connection_manager.h"
#include "event.h"
#include "modelrangemanager.h"
#include "compose.hpp"
#include "dictdatum.h"
#include <ostream>

#include "dirent.h"
#include "errno.h"

#include "sparsetable.h"

#ifdef M_ERROR
#undef M_ERROR
#endif

#ifdef HAVE_MUSIC
#include "music_event_handler.h"
#endif

/**
 * @file network.h
 * Declarations for class Network.
 */
class TokenArray;
class SLIInterpreter;

namespace nest
{
  class Subnet;
  class SiblingContainer;
  class Event;
  class Node;

  /**
   * @defgroup network Network access and administration
   * @brief Network administration and scheduling.
   * This module contains all classes which are involved in the
   * administration of the Network and the scheduling during
   * simulation.
   */

  /**
   * Main administrative interface to the network.
   * Class Network is responsible for
   * -# Administration of Model objects.
   * -# Administration of network Nodes.
   * -# Administration of the simulation time.
   * -# Update and scheduling during simulation.
   * -# Memory cleanup at exit.
   *
   * @see Node
   * @see Model
   * @ingroup user_interface
   * @ingroup network
   */
  
/* BeginDocumentation
Name: kernel - Global properties of the simulation kernel.

Description:
(start here.)

Parameters:
  The following parameters can be set in the status dictionary.

  communicate_allgather    booltype    - Whether to use MPI_Allgather for communication (otherwise use CPEX)
  data_path                stringtype  - A path, where all data is written to (default is the current directory)
  data_prefix              stringtype  - A common prefix for all data files
  dict_miss_is_error       booltype    - Whether missed dictionary entries are treated as errors
  local_num_threads        integertype - The local number of threads (cf. global_num_virt_procs)
  max_delay                doubletype  - The maximum delay in the network
  min_delay                doubletype  - The minimum delay in the network
  ms_per_tic               doubletype  - The number of miliseconds per tic (cf. tics_per_ms, tics_per_step)
  network_size             integertype - The number of nodes in the network
  num_connections          integertype - The number of connections in the network
  num_processes            integertype - The number of MPI processes
  off_grid_spiking         booltype    - Whether to transmit precise spike times in MPI communicatio
  overwrite_files          booltype    - Whether to overwrite existing data files
  print_time               booltype    - Whether to print progress information during the simulation
  resolution               doubletype  - The resolution of the simulation (in ms)
  rng_buffsize             integertype - The buffer size of the random number generators
  tics_per_ms              doubletype  - The number of tics per milisecond (cf. ms_per_tic, tics_per_step)
  tics_per_step            integertype - The number of tics per simulation time step (cf. ms_per_tic, tics_per_ms)
  time                     doubletype  - The current simulation time
  total_num_virtual_procs  integertype - The total number of virtual processes (cf. local_num_threads)
  to_do                    integertype - The number of steps yet to be simulated
  T_max                    doubletype  - The largest representable time value
  T_min                    doubletype  - The smallest representable time value
SeeAlso: Simulate, Node
*/
  
  class Network
  {
    friend class Scheduler;

  public:

    Network(SLIInterpreter&);
    ~Network();

    /**
     * Reset deletes all nodes and reallocates all memory pools for
     * nodes.
     */
    void reset();

    /**
     * Reset number of threads to one, reset device prefix to the
     * empty string and call reset(). 
     */
    void reset_kernel();    
    
    /**
     * Reset the network to the state at T = 0.
     */
    void reset_network();
    
    /**
     * Registers a fundamental model for use with the network.
     * @param   m     Model object.
     * @param   private_model  If true, model is not entered in modeldict.
     * @return void
     * @note The Network calls the Model object's destructor at exit.
     * @see register_model, unregister_model, register_user_model
     */
    void register_basis_model(Model& m, bool private_model = false);

    /**
     * Register a built-in model for use with the network.
     * Also enters the model in modeldict, unless private_model is true.
     * @param   m     Model object.
     * @param   private_model  If true, model is not entered in modeldict.
     * @return Model ID assigned by network
     * @note The Network calls the Model object's destructor at exit.
     * @see unregister_model, register_user_model
     */
    index register_model(Model& m, bool private_model = false);

    /**
     * Unregister a previously registered model.
     */
    void unregister_model(index m_id);

    /**
     * Try unregistering model prototype.
     * Throws ModelInUseException, if not possible, does not unregister.
     */
    void try_unregister_model(index m_id);

    /**
     * Copy an existing model and register it as a new model.
     * This function allows users to create their own, cloned models.
     * @param old_id The id of the existing model.
     * @param new_name The name of the new model.
     * @retval Index, identifying the new Model object.
     * @see copy_synapse_prototype()
     * @todo Not fully compatible with thread number changes and
     * unregister_model() yet.
     */
    index copy_model(index old_id, std::string new_name);

    /**
     * Register a synapse prototype at the connection manager.
     */
    index register_synapse_prototype(ConnectorModel * cf);

    /**
     * Unregister a synapse prototype at the connection manager.
     * syn_id: id which was obtained when registering (returned by register_synapse_prototype)
     */
    void unregister_synapse_prototype(index syn_id);

    /**
     * Try unregistering synapse prototype. Throws ModelInUseException, if not possible, does not unregister.
     */
    void try_unregister_synapse_prototype(index syn_id);

    /**
     * Copy an existing synapse type.
     * @see copy_model(), ConnectionManager::copy_synapse_prototype()
     */    
    int copy_synapse_prototype(index sc, std::string);

    /**
     * Return the model id for a given model name.
     */
    int get_model_id(const char []) const;

    /**
     * Return the Model for a given model ID.
     */
    Model * get_model(index) const;

    /**
     * Return the Model for a given GID.
     */
    Model * get_model_of_gid(index);

    /**
     * Return the Model ID for a given GID.
     */
    index get_model_id_of_gid(index);

    /**
     * Return the contiguous range of ids of nodes with the same model
     * than the node with the given GID.
     */
    const modelrange& get_contiguous_gid_range(index gid) const;

    /**
     * Add a number of nodes to the network.
     * This function creates n Node objects of Model m and adds them
     * to the Network at the current position.
     * @param m valid Model ID.
     * @param n Number of Nodes to be created. Defaults to 1 if not
     * specified.
     * @throws nest::UnknownModelID
     */
    index add_node(index m, long_t n = 1);

    /**
     * Restore nodes from an array of status dictionaries.
     * The following entries must be present in each dictionary:
     * /model - with the name or index of a neuron mode.
     * 
     * The following entries are optional:
     * /parent - the node is created in the parent subnet
     * 
     * Restore nodes uses the current working node as root. Thus, all
     * GIDs in the status dictionaties are offset by the GID of the current
     * working node. This allows entire subnetworks to be copied.
     */
    void restore_nodes(ArrayDatum &);

    /**
     * Set the state (observable dynamic variables) of a node to model defaults.
     * @see Node::init_state()
     */
    void init_state(index);

    /**
     * Return total number of network nodes.
     * The size also includes all Subnet objects.
     */
    index size() const;

    /**
     * Connect two nodes. The two nodes are defined by their global IDs.
     * The connection is established on the thread/process that owns the
     * target node.
     * \param s Address of the sending Node.
     * \param r Address of the receiving Node.
     * \param syn The synapse model to use.
     */ 
    void connect(index s, index r, index syn);

    /**
     * Connect two nodes. The two nodes are defined by their global IDs.
     * The connection is established on the thread/process that owns the
     * target node.
     * \param s Address of the sending Node.
     * \param r Address of the receiving Node.
     * \param w Weight of the connection.
     * \param d Delay of the connection (in ms).
     * \param syn The synapse model to use.
     */ 
    void connect(index s, index r, double_t w, double_t d, index syn);

    /**
     * Connect two nodes. The two nodes are defined by their global IDs.
     * The connection is established on the thread/process that owns the
     * target node.
     * \param s Address of the sending Node.
     * \param r Address of the receiving Node.
     * \param d A parameter dictionary for the connection.
     * \param syn The synapse model to use.
     * \returns true if a connection was made, false if operation was terminated 
     *          because source or target was a proxy.
     */ 
    bool connect(index s, index r, DictionaryDatum& d, index syn);

    void subnet_connect(Subnet &, Subnet &, int, index syn);

    /**
     * Connect from an array of dictionaries.
     */
    void connect(ArrayDatum& connectome);

    void count_connections();

    void divergent_connect(index s, const TokenArray r, const TokenArray weights, const TokenArray delays, index syn);
    /**
     * Connect one source node with many targets.
     * The dictionary d contains arrays for all the connections of type syn.
     */

    void divergent_connect(index s,  DictionaryDatum d, index syn);
    void random_divergent_connect(index s, const TokenArray r, index n, const TokenArray w, const TokenArray d, bool, bool, index syn);
    
    void convergent_connect(const TokenArray s, index r, const TokenArray weights, const TokenArray delays, index syn);

    void convergent_connect(const std::vector<index> &s_id, const std::vector<Node*> &s, index r, const TokenArray &weight, const TokenArray &delays, index syn);

    void random_convergent_connect(const TokenArray s, index t, index n, const TokenArray w, const TokenArray d, bool, bool, index syn);

    /**
     * Use openmp threaded parallelization to speed up connection.
     * Parallelize over target list. 
     */
    void random_convergent_connect(TokenArray s, TokenArray t, TokenArray n, TokenArray w, TokenArray d, bool, bool, index syn);
 
    DictionaryDatum get_connector_defaults(index sc);
    void set_connector_defaults(index sc, DictionaryDatum& d);

    DictionaryDatum get_synapse_status(index gid, index syn, port p, thread tid);
    void set_synapse_status(index gid, index syn, port p, thread tid, DictionaryDatum& d);

    DictionaryDatum get_connector_status(const Node& node, index sc);
    DictionaryDatum get_connector_status(index gid, index sc);
    void set_connector_status(Node& node, index sc, thread tid, DictionaryDatum& d);

    ArrayDatum find_connections(DictionaryDatum dict);
    ArrayDatum get_connections(DictionaryDatum dict);

    Subnet * get_root() const;        ///< return root subnet.
    Subnet * get_cwn() const;         ///< current working node.

    /**
     * Change current working node. The specified node must
     * exist and be a subnet.
     * @throws nest::IllegalOperation Target is no subnet.
     */
    void  go_to(index);

    void simulate(Time const &);
    /**
     * Resume the simulation after it was terminated.
     */
    void resume();

    /** 
     * Terminate the simulation after the time-slice is finished.
     */
    void terminate();

    /**
     * Return true if NEST will be quit because of an error, false otherwise.
     */
    bool quit_by_error() const;
    
    /**
     * Return the exitcode that would be returned to the calling shell
     * if NEST would quit now.
     */
    int get_exitcode() const;

    void memory_info();

    void print(index, int);

    /**
     * Standard routine for sending events. This method decides if
     * the event has to be delivered locally or globally. It exists
     * to keep a clean and unitary interface for the event sending
     * mechanism.
     * @note Only specialization for SpikeEvent does remote sending.
     *       Specialized for DSSpikeEvent to avoid that these events
     *       are sent to remote processes. 
     * \see send_local()
     */
    template <class EventT>
    void send(Node& source, EventT& e, const long_t lag = 0);
    
    /**
     * Send event e to all targets of node source on thread t
     */
    void send_local(thread t, Node& source, Event& e);

    /**
     * Send event e directly to its target node. This should be
     * used only where necessary, e.g. if a node wants to reply
     * to a *RequestEvent immediately.
     */
    void send_to_node(Event& e);

    /**
     * Return minimal connection delay.
     */
    delay get_min_delay() const;

    /**
     * Return maximal connection delay.
     */
    delay get_max_delay() const;
 
    /**
     * Get the time at the beginning of the current time slice.
     */
    Time const& get_slice_origin() const;

    /**
     * Get the time at the beginning of the previous time slice.
     */
    Time get_previous_slice_origin() const;

    /**
     * Get the current simulation time.
     * Defined only while no simulation in progress. 
     */
    Time const get_time() const;

    /**
     * Get random number client of a thread.
     * Defaults to thread 0 to allow use in non-threaded
     * context.  One may consider to introduce an additional
     * RNG just for the non-threaded context.
     */
    librandom::RngPtr get_rng(thread thrd = 0) const;

    /**
     * Get global random number client.
     * This grng must be used synchronized from all threads.
     */
    librandom::RngPtr get_grng() const;

    /**
     * Get number of threads.
     * This function returns the total number of threads per process.
     */
    thread get_num_threads() const;

    /**
     * Suggest a VP for a given global node ID
     */
    thread suggest_vp(index) const;

    /**
     * Convert a given VP ID to the corresponding thread ID
     */
    thread vp_to_thread(thread vp) const;

    /**
     * Convert a given thread ID to the corresponding VP ID
     */
    thread thread_to_vp(thread t) const;

    /**
     * Get number of processes.
     */
    thread get_num_processes() const;

    /**
     * Return true, if the given Node is on the local machine
     */
    bool is_local_node(Node*) const;

    /**
     * Return true, if the given gid is on the local machine
     */
    bool is_local_gid(index gid) const;

    /**
     * Return true, if the given VP is on the local machine
     */
    bool is_local_vp(thread) const;

    /**
     * See Scheduler::get_simulated()
     */
    bool get_simulated() const;
    
    /**
     * Return true, if all Nodes are updated.
     */
    bool is_updated() const;

    /**
     * Get reference signal from the network.
     * Node objects can use this function to determine their update
     * state with respect to the remaining network.
     * If the return value of this function is equal to the value of
     * the Node's local updated flag, then the Node has already been
     * update.
     * For example:
     * @code
     *  bool Node::is_updated() const
     *  {
     *    return stat_.test(updated)==net_->update_reference();
     *  }
     * @endcode
     */
    bool update_reference() const; ///< needed to check update state of nodes.

    /**
     * @defgroup net_access Network access
     * Functions to access network nodes.
     */

    /**
     * Return pointer of the specified Node.
     * @param i Index of the specified Node.
     * @param thr global thread index of the Node.
     *
     * @throws nest::UnknownNode       Target does not exist in the network.
     *
     * @ingroup net_access
     */
    Node*  get_node(index, thread thr = 0);

    /**
     * Return the Subnet that contains the thread siblings.
     * @param i Index of the specified Node.
     *
     * @throws nest::NoThreadSiblingsAvailable     Node does not have thread siblings.
     *
     * @ingroup net_access
     */
    const SiblingContainer* get_thread_siblings(index n) const;

    /**
     * Check, if there are instances of a given model.
     * @param i index of the model to check for
     * @return true, if model is instantiated at least once.
     */
    bool model_in_use(index i);

    /**
     * The prefix for files written by devices.
     * The prefix must not contain any part of a path.
     * @see get_data_dir(), overwrite_files()
     */
    const std::string& get_data_prefix() const;

    /**
     * The path for files written by devices.
     * It may be the empty string (use current directory).
     * @see get_data_prefix(), overwrite_files()
     */
    const std::string& get_data_path() const;
    
    /**
     * Indicate if existing data files should be overwritten.
     * @return true if existing data files should be overwritten by devices. Default: false.
     */
    bool overwrite_files() const;
    
    /**
     * return current communication style.
     * A result of true means off_grid, false means on_grid communication.
     */
    bool get_off_grid_communication() const;

    /**
     * Set properties of a Node. The specified node must exist.
     * @throws nest::UnknownNode       Target does not exist in the network.
     * @throws nest::UnaccessedDictionaryEntry  Non-proxy target did not read dict entry.
     * @throws TypeMismatch            Array is not a flat & homogeneous array of integers.
     */
    void set_status(index, const DictionaryDatum&);

    /**
     * Get properties of a node. The specified node must exist.
     * @throws nest::UnknownNode       Target does not exist in the network.
     */
    DictionaryDatum get_status(index);

    /**
     * Execute a SLI command in the neuron's namespace.
     */
    int execute_sli_protected(DictionaryDatum, Name);

    /**
     * Return a reference to the model dictionary.
     */
    const Dictionary &get_modeldict();
    
    /**
     * Return the synapse dictionary
     */
    const Dictionary &get_synapsedict() const;
    
    /**
     * Recalibrate scheduler clock.
     */
    void calibrate_clock();
    
    /**
     * Return 0 for even, 1 for odd time slices.
     *
     * This is useful for buffers that need to be written alternatingly
     * by time slice. The value is given by Scheduler::get_slice_() % 2.
     * @see read_toggle
     */
    size_t write_toggle() const;

    /**
     * Return 1 - write_toggle().
     *
     * This is useful for buffers that need to be read alternatingly
     * by slice. The value is given by 1-write_toggle().
     * @see write_toggle
     */
    size_t read_toggle() const;  

    /**
     * Does the network contain copies of models created using CopyModel?
     */
    bool has_user_models() const;

    /** Display a message. This function displays a message at a 
     *  specific error level. Messages with an error level above
     *  M_ERROR will be written to std::cerr in addition to
     *  std::cout.
     *  \n
     *  \n
     *  The message will ony be displayed if the current verbosity level
     *  is greater than or equal to the input level.
     *
     *  @ingroup SLIMessaging
     */
    void message(int level, const char from[], const char text[]);
    void message(int level, const std::string& loc, const std::string& msg);

    /**
     * Returns true if unread dictionary items should be treated as error.
     */
    bool dict_miss_is_error() const;

#ifdef HAVE_MUSIC
  public:  
    /**
     * Register a MUSIC input port (portname) with the port list.
     * This will increment the counter of the respective entry in the
     * music_in_portlist.
     */
    void register_music_in_port(std::string portname);

    /**
     * Unregister a MUSIC input port (portname) from the port list.
     * This will decrement the counter of the respective entry in the
     * music_in_portlist and remove the entry if the counter is 0
     * after decrementing it.
     */
    void unregister_music_in_port(std::string portname);

    /**
     * Register a node (of type music_input_proxy) with a given MUSIC
     * port (portname) and a specific channel. The proxy will be
     * notified, if a MUSIC event is being received on the respective
     * channel and port.
     */
    void register_music_event_in_proxy(std::string portname, int channel, nest::Node *mp);

    /**
     * Set the acceptable latency (latency) for a music input port (portname).
     */
    void set_music_in_port_acceptable_latency(std::string portname, double_t latency);

    /**
     * The mapping between MUSIC input ports identified by portname
     * and the corresponding acceptable latency (second component of
     * the pair). The first component of the pair is a counter that is
     * used to track how many music_input_proxies are connected to the
     * port.
     * @see register_music_in_port()
     * @see unregister_music_in_port()
     */
    std::map< std::string, std::pair<size_t, double_t> > music_in_portlist_;
    
    /**
     * The mapping between MUSIC input ports identified by portname
     * and the corresponding MUSIC event handler.
     */
    std::map< std::string, MusicEventHandler > music_in_portmap_;

    /**
     * Publish all MUSIC input ports that were registered using
     * Network::register_music_event_in_proxy().
     */
    void publish_music_in_ports_();

    /**
     * Call update() for each of the registered MUSIC event handlers
     * to deliver all queued events to the target music_in_proxies.
     */
    void update_music_event_handlers_(Time const &, const long_t, const long_t);
#endif
    
  private:
    void connect(Node& s, Node& r, index sgid, thread t, index syn);
    void connect(Node& s, Node& r, index sgid, thread t, double_t w, double_t d, index syn);
    void connect(Node& s, Node& r, index sgid, thread t, DictionaryDatum& d, index syn);

    /**
     * Initialize the network data structures.
     * init_() is used by the constructor and by reset().
     * @see reset()
     */
    void init_();
    void destruct_nodes_();
    void clear_models_();
        
    /**
     * Helper function to set properties on single node.
     * @param node to set properties for
     * @param dictionary containing properties
     * @param if true (default), access flags are called before
     *        each call so Node::set_status_()
     * @throws UnaccessedDictionaryEntry
     */
    void set_status_single_node_(Node&, const DictionaryDatum&, bool clear_flags = true);  

    //! Helper function to set device data path and prefix.
    void set_data_path_prefix_(const DictionaryDatum& d);

    Scheduler scheduler_;
    SLIInterpreter &interpreter_;
    ConnectionManager connection_manager_;
    
    Subnet *root_;               //!< Root node.
    Subnet *current_;            //!< Current working node (for insertion).

    /* BeginDocumentation
       Name: synapsedict - Dictionary containing all synapse models.
       Description:
       'synapsedict info' shows the contents of the dictionary
       FirstVersion: October 2005
       Author: Jochen Martin Eppler
       SeeAlso: info
    */
    Dictionary* synapsedict_;      //!< Dictionary for synapse models.

    /* BeginDocumentation
       Name: modeldict - dictionary containing all devices and models of NEST
       Description:
       'modeldict info' shows the contents of the dictionary
       SeeAlso: info, Device, RecordingDevice, iaf_neuron, subnet
    */
    Dictionary* modeldict_;        //!< Dictionary for models.

    Model* siblingcontainer_model; //!< The model for the SiblingContainer class
    
    std::string data_path_;        //!< Path for all files written by devices 
    std::string data_prefix_;      //!< Prefix for all files written by devices
    bool        overwrite_files_;  //!< If true, overwrite existing data files. 

    /**
     * The list of clean models. The first component of the pair is a
     * pointer to the actual Model, the second is a flag indicating if
     * the model is private. Private models are not entered into the
     * modeldict.
     */
    std::vector< std::pair<Model *, bool> > pristine_models_;

    std::vector<Model *> models_;            //!< The list of available models
    std::vector<Node*> proxy_nodes_;         //!< Placeholders for remote nodes, one per thread
    std::vector<Node*> dummy_spike_sources_; //!< Placeholders for spiking remote nodes, one per thread

    google::sparsetable<Node *> nodes_;  //!< The network as flat list of nodes
    Modelrangemanager node_model_ids_;   //!< Records the model id of each neuron in the network

    bool dict_miss_is_error_;  //!< whether to throw exception on missed dictionary entries
  };

  inline 
  void Network::terminate()
  {
    scheduler_.terminate();
  }

  inline
  bool Network::quit_by_error() const
  {    
    Token t = interpreter_.baselookup(Name("systemdict"));
    DictionaryDatum systemdict = getValue<DictionaryDatum>(t);
    t = systemdict->lookup(Name("errordict"));
    DictionaryDatum errordict = getValue<DictionaryDatum>(t);
    return getValue<bool>(errordict, "quitbyerror");
  }

  inline
  int Network::get_exitcode() const
  {
    Token t = interpreter_.baselookup(Name("statusdict"));
    DictionaryDatum statusdict = getValue<DictionaryDatum>(t);
    return getValue<long>(statusdict, "exitcode");
  }

  inline
  index Network::size() const
  {
    //return node_locs_.size();
    return nodes_.size();
  }

  inline
  void Network::connect(Node& s, Node& r, index sgid, thread t, index syn)
  {
    connection_manager_.connect(s, r, sgid, t, syn);
  }

  inline
  void Network::connect(Node& s, Node& r, index sgid, thread t, double_t w, double_t d, index syn)
  {
    connection_manager_.connect(s, r, sgid, t, w, d, syn);
  }

  inline
  void Network::connect(Node& s, Node& r, index sgid, thread t, DictionaryDatum& p, index syn)
  {
    connection_manager_.connect(s, r, sgid, t, p, syn);
  }

  inline
  void Network::connect(ArrayDatum &connectome)
  {
    connection_manager_.connect(connectome);
  }

  inline
  void Network::count_connections()
  {
    connection_manager_.count_connections();
  }

  inline
  DictionaryDatum Network::get_synapse_status(index gid, index syn, port p, thread tid)
  {
    return connection_manager_.get_synapse_status(gid, syn, p, tid);
  }

  inline
  void Network::set_synapse_status(index gid, index syn, port p, thread tid, DictionaryDatum& d)
  {
    connection_manager_.set_synapse_status(gid, syn, p, tid, d);
  }

  inline
  DictionaryDatum Network::get_connector_status(const Node& node, index sc)
  {
    return connection_manager_.get_connector_status(node, sc);
  }

  inline
  DictionaryDatum Network::get_connector_status(index gid, index sc)
  {
    return connection_manager_.get_connector_status(gid, sc);
  }

  inline
  void Network::set_connector_status(Node& node, index sc, thread tid, DictionaryDatum& d)
  {
    connection_manager_.set_connector_status(node, sc, tid, d);
  }

  inline
  ArrayDatum Network::find_connections(DictionaryDatum params)
  {
    return connection_manager_.find_connections(params);
  }

  inline
  ArrayDatum Network::get_connections(DictionaryDatum params)
  {
    return connection_manager_.get_connections(params);
  }
  
  inline
  void Network::set_connector_defaults(index sc, DictionaryDatum& d)
  {
    connection_manager_.set_prototype_status(sc, d);
  }

  inline
  DictionaryDatum Network::get_connector_defaults(index sc)
  {
   return connection_manager_.get_prototype_status(sc);
  }
  
  inline
  index Network::register_synapse_prototype(ConnectorModel * cm)
  {
    return connection_manager_.register_synapse_prototype(cm);
  }
  
  inline
  void Network::unregister_synapse_prototype(index syn_id)
  {
    connection_manager_.unregister_synapse_prototype(syn_id);
  }

  inline
  void Network::try_unregister_synapse_prototype(index syn_id)
  {
    connection_manager_.try_unregister_synapse_prototype(syn_id);
  }

  inline
  int Network::copy_synapse_prototype(index sc, std::string name)
  {
    return connection_manager_.copy_synapse_prototype(sc, name);
  }
  
  inline
  Time const & Network::get_slice_origin() const
  {
    return scheduler_.get_slice_origin();
  }

  inline
  Time Network::get_previous_slice_origin() const
  {
    return scheduler_.get_previous_slice_origin();
  }

  inline
  Time const Network::get_time() const
  {
    return scheduler_.get_time();
  }

  inline
  Subnet * Network::get_root() const
  {
    return root_;
  }

  inline
  Subnet* Network::get_cwn(void) const
  {
    return current_;
  }

  inline
  thread Network::get_num_threads() const
  {
    return scheduler_.get_num_threads();
  }

  inline
  thread Network::get_num_processes() const
  {
    return scheduler_.get_num_processes();
  }

  inline
  bool Network::is_local_node(Node* n) const
  {
    return !(n->is_proxy());
  }

  inline
  bool Network::is_local_gid(index gid) const
  {
    /* if(gid >= node_locs_.size() || nodes_[node_locs_[gid]] == 0) */
    /*   throw UnknownNode(gid); */
    /* return (node_locs_[gid] != -1); */
    if ( gid >= nodes_.size() )
      throw UnknownNode(gid);
    return ( nodes_.test(gid) ); // test if local
  }

  inline
  bool Network::is_local_vp(thread t) const
  {
    return scheduler_.is_local_vp(t);
  }

  inline
  int Network::suggest_vp(index gid) const
  {
    return scheduler_.suggest_vp(gid);
  }

  inline
  thread Network::vp_to_thread(thread vp) const
  {
    return scheduler_.vp_to_thread(vp);
  }

  inline
  thread Network::thread_to_vp(thread t) const
  {
    return scheduler_.thread_to_vp(t);
  }

  inline
  bool Network::get_simulated() const
  {
    return scheduler_.get_simulated();
  }

  inline
  bool Network::is_updated() const
  {
    return scheduler_.is_updated();
  }

  inline
  bool Network::update_reference() const
  {
    return scheduler_.update_reference();
  }

  inline
  delay Network::get_min_delay() const
  {
    return scheduler_.get_min_delay();
  }

  inline
  delay Network::get_max_delay() const
  {
    return scheduler_.get_max_delay();
  }
  
  template <class EventT>
  inline
  void Network::send(Node& source, EventT& e, const long_t lag)
  {
    e.set_stamp(get_slice_origin() + Time::step(lag + 1));
    e.set_sender(source);
    thread t = source.get_thread();
    index gid = source.get_gid();

    //std::cout << "Network::send 1 " << gid << " " << e.get_sender().get_gid() << std::endl;

    assert(!source.has_proxies());
    connection_manager_.send(t, gid, e);
  }

  template <>
  inline
  void Network::send<SpikeEvent>(Node& source, SpikeEvent& e, const long_t lag)
  {
    e.set_stamp(get_slice_origin() + Time::step(lag + 1));
    e.set_sender(source);
    thread t = source.get_thread();

    if (source.has_proxies())
    {
      if ( source.is_off_grid() )
        scheduler_.send_offgrid_remote(t, e, lag);
      else
        scheduler_.send_remote(t, e, lag);
    }
    else
      send_local(t, source, e);
  }

  template <>
  inline
  void Network::send<DSSpikeEvent>(Node& source, DSSpikeEvent& e, const long_t lag)
  {
    e.set_stamp(get_slice_origin() + Time::step(lag + 1));
    e.set_sender(source);
    thread t = source.get_thread();

    assert(!source.has_proxies());
    send_local(t, source, e);
  } 

  inline
  void Network::send_local(thread t, Node& source, Event& e)
  {
    index sgid = source.get_gid();
    e.set_sender_gid(sgid);
    connection_manager_.send(t, sgid, e);
  }

  inline
  void Network::send_to_node(Event& e)
  {
    e();
  }

  inline
  void Network::calibrate_clock() 
  {
    scheduler_.calibrate_clock();
  }

  inline
  size_t Network::write_toggle() const
  {
    return scheduler_.get_slice() % 2;
  } 

  inline
  size_t Network::read_toggle() const
  {
    // define in terms of write_toggle() to ensure consistency
    return 1 - write_toggle();
  } 

  inline
  librandom::RngPtr Network::get_rng(thread t) const
  {
    return scheduler_.get_rng(t);
  }

  inline
  librandom::RngPtr Network::get_grng() const
  {
    return scheduler_.get_grng();
  }

  inline
  Model* Network::get_model(index m) const
  {
    if (m < models_.size() && models_[m] != 0)
      return models_[m];
    else
      throw UnknownModelID(m);

    return 0; // this never happens
  }

  inline 
  Model* Network::get_model_of_gid(index gid)
  {
     return models_[get_model_id_of_gid(gid)];
  }

  inline
  index Network::get_model_id_of_gid(index gid)
  {
    if (node_model_ids_.is_in_range(gid))
      return node_model_ids_.get_model_id(gid);
    else
      throw UnknownNode(gid);
  }

  inline 
  const modelrange& Network::get_contiguous_gid_range(index gid) const
  {
    return node_model_ids_.get_range(gid);
  }

  inline
  const std::string& Network::get_data_path() const
  {
    return data_path_;
  }

  inline
  const std::string& Network::get_data_prefix() const
  {
    return data_prefix_;
  }

  inline
  bool Network::overwrite_files() const
  {
    return overwrite_files_;
  }

  inline
  bool Network::get_off_grid_communication() const
  {
    return scheduler_.get_off_grid_communication();
  }
  
  inline
  const Dictionary& Network::get_modeldict()
  {
    assert(modeldict_ != 0);
    return *modeldict_;
  }
  
  inline
  const Dictionary& Network::get_synapsedict() const
  {
    assert(synapsedict_ != 0);
    return *synapsedict_;
  }

  inline
  bool Network::has_user_models() const
  {
    return models_.size() > pristine_models_.size();
  }

  inline
  bool Network::dict_miss_is_error() const
  {
    return dict_miss_is_error_;
  }

  typedef lockPTR<Network> NetPtr;

  //!< Functor to compare Models by their name.
  class ModelComp : public std::binary_function<int, int, bool>
  {
    const std::vector<Model*> &models;

    public:
      ModelComp(const vector<Model*> &nmodels) : models(nmodels) {}
      bool operator()(int a, int b)
      {
        return models[a]->get_name() < models[b]->get_name();
      }
  };

} // namespace

#endif