/*
 * VariableNetwork.h
 *
 *  Created on: Jun 14, 2016
 *      Author: Martin Hierholzer
 */

#ifndef CHIMERATK_VARIABLE_NETWORK_H
#define CHIMERATK_VARIABLE_NETWORK_H

#include <boost/mpl/for_each.hpp>
#include <iostream>
#include <list>
#include <string>
#include <typeinfo>

#include <ChimeraTK/ControlSystemAdapter/ProcessVariable.h>

#include "Flags.h"
#include "VariableNetworkNode.h"
#include "Visitor.h"

namespace ChimeraTK {

class AccessorBase;

/** This class describes a network of variables all connected to each other. */
class VariableNetwork {

  VariableNetwork(const VariableNetwork &other) =
      delete; // non construction-copyable
  VariableNetwork &operator=(const VariableNetwork &) = delete; // non copyable

public:
  VariableNetwork() {}

  /** Define trigger types. The trigger decides when values are fed into the
   * network and distributed to the consumers. */
  enum class TriggerType {
    feeder, ///< The feeder has an UpdateMode::push and thus decides when new
            ///< values are fed
    pollingConsumer, ///< If there is exacly one consumer with UpdateMode::poll,
                     ///< it will trigger the feeding
    external, ///< another variable network can trigger the feeding of this
              ///< network
    none      ///< no trigger has yet been selected
  };

  /** Add an node to the network. The node must not yet be part of any network.
   */
  void addNode(VariableNetworkNode &a);

  /** Add a trigger receiver node. The node must not yet be part of any network.
   */
  void addNodeToTrigger(VariableNetworkNode &nodeToTrigger);

  /** Remove a node from the network. The node must be part of the given
   * network. */
  void removeNode(VariableNetworkNode &a);

  /** Remove a trigger receiver node from the network. The node must be part of
   * the given network. */
  void removeNodeToTrigger(const VariableNetworkNode &nodeToNoLongerTrigger);

  /** Check if the network already has a feeding node connected to it. */
  bool hasFeedingNode() const;

  /** Count the number of consuming nodes in the network */
  size_t countConsumingNodes() const;

  /** Obtain the type info of the UserType. If the network type has not yet been
   * determined (i.e. if no output accessor has been assigned yet), the typeid
   * of void will be returned. */
  const std::type_info &getValueType() const { return *valueType; }

  /** Return the feeding node */
  VariableNetworkNode getFeedingNode() const;

  /** Return list of consuming nodes */
  std::list<VariableNetworkNode> getConsumingNodes() const;

  /** Check whether the network has a consuming application node */
  bool hasApplicationConsumer() const;

  /** Dump the network structure to std::cout. The optional linePrefix will be
   * prepended to all lines. */
  void dump(const std::string &linePrefix = "",
            std::ostream &stream = std::cout) const;

  void accept(Visitor<VariableNetwork> &visitor) const;

  /** Compare two networks */
  bool operator==(const VariableNetwork &other) const {
    if (other.valueType != valueType)
      return false;
    if (other.nodeList != nodeList)
      return false;
    return true;
  }
  bool operator!=(const VariableNetwork &other) const {
    return !operator==(other);
  }

  /** Return the trigger type. This function will also do some checking if the
   * network confguration is valid under the aspect of the trigger type. The
   * optional argument is only internally used to prevent endless recursive
   * calls if getTriggerType() is called inside dump(). */
  TriggerType getTriggerType(bool verboseExceptions = true) const;

  /** Return the enginerring unit */
  const std::string &getUnit() const { return engineeringUnit; }

  /** Return the description */
  const std::string &getDescription() const { return description; }

  /** Return the network providing the external trigger to this network, if
   * TriggerType::external. If the network has another trigger type, an
   * exception will be thrown. */
  // VariableNetwork& getExternalTrigger();

  /** Add an accessor belonging to another node as an external trigger to this
   * network. Whenever the VariableNetwork of the given node will be fed with a
   * new value, feeding of this network will be triggered as well. */
  // void addTrigger(VariableNetworkNode trigger);

  /** Check if the network is legally configured */
  void check() const;

  /** Check the flag if the network connections has been created already */
  bool isCreated() const { return flagIsCreated; }

  /** Set the flag that the network connections are created */
  void markCreated() { flagIsCreated = true; }

  /** Assign a ProcessVariable as implementation for the external trigger */
  void
  setExternalTriggerImpl(boost::shared_ptr<ChimeraTK::ProcessVariable> impl) {
    externalTriggerImpl = impl;
  }

  /** */
  boost::shared_ptr<ChimeraTK::ProcessVariable> getExternalTriggerImpl() const {
    return externalTriggerImpl;
  }

  /** Merge with another VaraibleNetwork. The other network will become invalid
   * and gets removed from the
   *  application. If merging is not possible, false is returned and no change
   * is made. */
  bool merge(VariableNetwork &other);

protected:
  /** List of nodes in the network */
  std::list<VariableNetworkNode> nodeList;

  /** The network value type id. Since in C++, std::type_info is non-copyable
   * and typeid() returns a reference to
   *  an object with static storage duration, we have to (and can safely) store
   * a pointer here. */
  const std::type_info *valueType{&typeid(AnyType)};

  /** Engineering unit */
  std::string engineeringUnit{ChimeraTK::TransferElement::unitNotSet};

  /** User-provided description */
  std::string description;

  /** Flag if an external trigger has been added to this network */
  // bool hasExternalTrigger{false};

  /** Pointer to the network providing the external trigger */
  // VariableNetwork *externalTrigger{nullptr};

  /** Pointer to ProcessVariable providing the trigger (if external trigger is
   * enabled) */
  boost::shared_ptr<ChimeraTK::ProcessVariable> externalTriggerImpl;

  /** Flag if the network connections have been created already */
  bool flagIsCreated{false};
};

} /* namespace ChimeraTK */

#endif /* CHIMERATK_VARIABLE_NETWORK_H */