/*
 *  subnet.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 SUBNET_H
#define SUBNET_H

#include <vector>
#include <string>
#include "node.h"
#include "dictdatum.h"
#include "multirange.h"

/* BeginDocumentation

Name: subnet - Root node for subnetworks.

Description:
A network node of type subnet serves as a root node for subnetworks

Parameters:
Parameters that can be accessed via the GetStatus and SetStatus functions:

customdict (dictionarytype) -
   A user-defined dictionary, which may be used to store additional
   data.
label (stringtype) -
  A user-defined string, which may be used to give a symbolic name to
  the node.
number_of_children (integertype) -
  The number of direct children of the subnet

  SeeAlso: modeldict, Node
*/

namespace nest{

  using std::vector;

  class Node;

  /**
   * Base class for all subnet nodes.
   * This class can be used
   * - to group other Nodes into "sub-networks"
   * - to construct Node classes which are composed of multiple 
   *   subnodes.
   */
  class Subnet: public Node
  {
  public:

    Subnet();   

    Subnet(const Subnet &);

    virtual ~Subnet(){}
   
    void set_status(const DictionaryDatum&);
    void get_status(DictionaryDatum&) const;

    bool has_proxies() const;
          
    size_t global_size() const;  //!< Returns total number of children.
    size_t local_size() const; //!< Returns number of childern in local process.
    bool   global_empty() const; //!< returns true if subnet is empty *globally*
    bool   local_empty() const; //!< returns true if subnet has no local nodes

    void   reserve(size_t);

    /**
     * Add a local node to the subnet.
     * This function adds a node to the subnet and returns its local id.
     * The node is appended to the subnet child-list. 
     */ 
    index add_node(Node *);

    /**
     * Add a remote node to the subnet.
     * This function increments the next local id to be assigned.
     */ 
    index add_remote_node(index gid, index mid);

    /**
     * Return iterator to the first local child node.
     */
    vector<Node*>::iterator local_begin();

    /**
     * Return iterator to the end of the local child-list.
     */
    vector<Node*>::iterator local_end();

    /**
     * Return const iterator to the first local child node.
     */
    vector<Node*>::const_iterator local_begin() const;

    /**
     * Return const iterator to the end of the local child-list.
     */
    vector<Node*>::const_iterator local_end() const;

    /**
     * Return pointer to Node at given LID if it is local.
     * @note Defined for dense subnets only (all children local)
     */
    Node* at_lid(index) const;

    /**
     * Return the subnets's user label.
     * Each subnet can be given a user-defined string as a label, which
     * may be used to give a symbolic name to the node. From the SLI
     * level, the GetGlobalNodes command may be used to find a subnet's
     * GID from its label.
     */
    std::string get_label() const;
    
    /**
     * Set the subnet's user label.
     * Each subnet can be given a user-defined string as a label, which
     * may be used to give a symbolic name to the node. From the SLI
     * level, the GetGlobalNodes command may be used to find a subnet's
     * GID from its label.
     */
    void set_label(std::string const);

    /**
     * Set the subnet's custom dictionary.
     * Each subnet can be given a user-defined dictionary, which
     * may be used to store additional data. From the SLI
     * level, the SetStatus command may be used to set a subnet's
     * custom dictionary.
     */
    DictionaryDatum get_customdict() const;
    
    /**
     * Return pointer to the subnet's custom dictionary.
     * Each subnet contains a user-definable dictionary, which
     * may be used to store additional data. From the SLI
     * level, the SetStatus command may be used to set a subnet's
     * custom dictionary.
     */
    void set_customdict(DictionaryDatum const dict);
    
    std::string print_network(int , int, std::string = "");

    bool get_children_on_same_vp() const;
    void set_children_on_same_vp(bool);

    thread get_children_vp() const;
    void set_children_vp(thread);
    
    bool allow_entry() const;

    bool is_homogeneous() const; 

  protected:
    void init_node_(const Node&) {}
    void init_state_(const Node&) {}
    void init_buffers_() {}

    void calibrate() {}
    void update(Time const &, const long_t, const long_t) {}
    
    /**
     * Pointer to child nodes.
     * This vector contains the pointers to the child nodes.
     * Since deletion of Nodes is possible, entries in this
     * vector may be NULL. Note that all code must handle
     * this case gracefully.
     */
    vector<Node *> nodes_;

    /**
     * GIDs of global child nodes.
     * This Multirange contains the GIDs of all child nodes on all
     * processes.
     */
    Multirange gids_;

    /**
     * flag indicating if all children of this subnet have to
     * be created on the same virtual process or not. Use with
     * care. This may lead to severe performance problems!
     */
    bool children_on_same_vp_;
    thread children_vp_;
    
  private:
    void get_dimensions_(std::vector<int>&) const;

    std::string     label_;      //!< user-defined label for this node.
    DictionaryDatum customdict_; //!< user-defined dictionary for this node.
    // note that DictionaryDatum is a pointer and must be initialized in the constructor.
    bool homogeneous_;           //!< flag which indicates if the subnet contains different kinds of models.
    index last_mid_;             //!< model index of last child
  };

  /**
   * Add a local node to the subnet.
   */
  inline
  index Subnet::add_node(Node *n)
  {
    const index lid = gids_.size();
    const index mid = n->get_model_id();
    if ((homogeneous_) && (lid > 0))
      if (mid != last_mid_)
	homogeneous_ = false;
    n->set_lid_(lid);
    n->set_subnet_index_(nodes_.size());
    nodes_.push_back(n);
    n->set_parent_(this);
    gids_.push_back(n->get_gid());
    last_mid_ = mid;
    return lid;
  }
  /**
   * Add a remote node to the subnet.
   */
  inline
  index Subnet::add_remote_node(index gid, index mid)
  {
    const index lid = gids_.size();
    if((homogeneous_) && (lid > 0))
      if (mid != last_mid_)
	homogeneous_ = false;
    last_mid_ = mid;
    gids_.push_back(gid);
    return lid;
  }
  
  inline
  vector<Node*>::iterator Subnet::local_begin()
  {
    return nodes_.begin();
  }

  inline
  vector<Node*>::iterator Subnet::local_end()
  {
    return nodes_.end();
  }

  inline
  vector<Node*>::const_iterator Subnet::local_begin() const
  {
    return nodes_.begin();
  }

  inline
  vector<Node*>::const_iterator Subnet::local_end() const
  {
    return nodes_.end();
  }

  inline
  bool Subnet::local_empty() const
  {
    return nodes_.empty();
  }

  inline
  bool Subnet::global_empty() const
  {
    return gids_.empty();
  }

  inline
  size_t Subnet::global_size() const
  {
    return gids_.size();
  }

  inline
  size_t Subnet::local_size() const
  {
    return nodes_.size();
  }

  inline
  Node* Subnet::at_lid(index lid) const
  {
    // defined for "dense" subnets only
    assert(local_size() == global_size());

    if ( lid >= nodes_.size() )
      throw UnknownNode();

    return nodes_[lid];
  }

  inline 
  void Subnet::reserve(size_t n)
  {
    nodes_.reserve(n);
  }

  inline 
  std::string Subnet::get_label() const
  {
    return label_;
  }

  inline 
  DictionaryDatum Subnet::get_customdict() const
  {
    return customdict_;
  }
  
  inline 
  void Subnet::set_customdict(DictionaryDatum const d)
  {
    customdict_=d;
  }
  
  inline
  bool Subnet::has_proxies() const
  {
    return false;
  }

  inline
  bool Subnet::get_children_on_same_vp() const
  {
    return children_on_same_vp_;
  }

  inline
  void Subnet::set_children_on_same_vp(bool children_on_same_vp)
  {
    children_on_same_vp_ = children_on_same_vp;
  }

  inline
  thread Subnet::get_children_vp() const
  {
    return children_vp_;
  }

  inline
  void Subnet::set_children_vp(thread children_vp)
  {
    children_vp_ = children_vp;
  }

  inline
  bool Subnet::is_homogeneous() const
  {
    return homogeneous_;
  }
  
} // namespace

#endif