/****************************************************************************
 *   This file is part of the aGrUM/pyAgrum library.                        *
 *                                                                          *
 *   Copyright (c) 2005-2026 by                                             *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *                                                                          *
 *   The aGrUM/pyAgrum library is free software; you can redistribute it    *
 *   and/or modify it under the terms of either :                           *
 *                                                                          *
 *    - the GNU Lesser General Public License as published by               *
 *      the Free Software Foundation, either version 3 of the License,      *
 *      or (at your option) any later version,                              *
 *    - the MIT license (MIT),                                              *
 *    - or both in dual license, as here.                                   *
 *                                                                          *
 *   (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html)    *
 *                                                                          *
 *   This aGrUM/pyAgrum library is distributed in the hope that it will be  *
 *   useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,          *
 *   INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
 *   FOR A PARTICULAR PURPOSE  AND NONINFRINGEMENT. IN NO EVENT SHALL THE   *
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,        *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  *
 *   OTHER DEALINGS IN THE SOFTWARE.                                        *
 *                                                                          *
 *   See LICENCES for more details.                                         *
 *                                                                          *
 *   SPDX-FileCopyrightText: Copyright 2005-2026                            *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *   SPDX-License-Identifier: LGPL-3.0-or-later OR MIT                      *
 *                                                                          *
 *   Contact  : info_at_agrum_dot_org                                       *
 *   homepage : http://agrum.gitlab.io                                      *
 *   gitlab   : https://gitlab.com/agrumery/agrum                           *
 *                                                                          *
 ****************************************************************************/

#pragma once


/**
 * @file
 * @brief Template methods of MultiDimFunctionGraph.
 *
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) & Christophe GONZALES(_at_AMU)
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) and Jean-Christophe MAGNAN and Christophe
 * GONZALES(_at_AMU)
 */
#include <agrum/base/multidim/implementations/multiDimFunctionGraph.h>

namespace gum {

  // Default constructor.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::MultiDimFunctionGraph(bool isReduced) :
      MultiDimImplementation< GUM_ELEMENT >(), _name_("MultiDimFunctionGraph"),
      _tableName_("NO NAME"), _model_(500, true), _manager_(nullptr), _root_(0),
      _internalNodeMap_(500, true, false), _var2NodeIdMap_(500, true, false),
      _isReduced_(isReduced) {
    GUM_CONSTRUCTOR(MultiDimFunctionGraph);
    _manager_ = nullptr;
    // Pop up a first node so that id 0 is unavailable
    _model_.addNode();
  }

  // Copy constructor.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::MultiDimFunctionGraph(
      const MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >& from) :
      MultiDimImplementation< GUM_ELEMENT >(), _name_("MultiDimFunctionGraph"),
      _tableName_("No NAME"), _model_(500, true), _manager_(nullptr), _root_(0),
      _internalNodeMap_(500, true, false), _var2NodeIdMap_(500, true, false),
      _isReduced_(from.isReducedAndOrdered()) {
    GUM_CONS_CPY(MultiDimFunctionGraph);
    copy(from);
  }

  // Copy Operator.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >&
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::operator=(
          const MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >& from) {
    copy(from);
    return *this;
  }

  // Destructor.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::~MultiDimFunctionGraph() {
    // Manager deletion
    GUM_DESTRUCTOR(MultiDimFunctionGraph);
    if (_manager_ != nullptr) delete _manager_;
    this->clear();
  }

  // GCC false positive: deep inlining of newFactory() through Tensor copy-ctor loses virtual
  // dispatch, making GCC believe a MultiDimFunctionGraph (larger) is accessed on a MultiDimArray
  // allocation (512 bytes). The access is safe at runtime because the virtual call is correct.
#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Warray-bounds"
#endif
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimContainer< GUM_ELEMENT >*
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::newFactory() const {
    if (_isReduced_)
      return MultiDimFunctionGraph< GUM_ELEMENT,
                                    TerminalNodePolicy >::getReducedAndOrderedInstance();
    else return MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::getTreeInstance();
  }
#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  const std::string& MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::name() const {
    return _name_;
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::set(
      const Instantiation& i,
      const GUM_ELEMENT&   value) const {
    GUM_ERROR(OperationNotAllowed,
              "Function Graph can't be edited so "
              "easily.\nMultiDimFunctionGraphManager "
              "provides the framework to edit a "
              "Function Graph.")
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::fill(const GUM_ELEMENT& d) const {
    GUM_ERROR(OperationNotAllowed,
              "Function Graph can't be edited so "
              "easily.\nMultiDimFunctionGraphManager "
              "provides the framework to edit a "
              "Function Graph.")
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::populate(
      const std::vector< GUM_ELEMENT >& v) const {
    GUM_ERROR(OperationNotAllowed,
              "Function Graph can't be edited so "
              "easily.\nMultiDimFunctionGraphManager "
              "provides the framework to editaa "
              "Function Graph.")
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::populate(
      std::initializer_list< GUM_ELEMENT > l) const {
    GUM_ERROR(OperationNotAllowed,
              "Function Graph can't be edited so "
              "easily.\nMultiDimFunctionGraphManager "
              "provides the framework to edit a "
              "Function Graph.")
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::add(const DiscreteVariable& v) {
    if (!this->variablesSequence().exists(&v)) MultiDimImplementation< GUM_ELEMENT >::add(v);

    if (!this->_var2NodeIdMap_.exists(&v)) _var2NodeIdMap_.insert(&v, new LinkedList< NodeId >());
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::erase(const DiscreteVariable& v) {
    if (this->_var2NodeIdMap_.exists(&v)) {
      while (_var2NodeIdMap_[&v]->list() != nullptr) {
        manager()->eraseNode(_var2NodeIdMap_[&v]->list()->element());
      }
      delete _var2NodeIdMap_[&v];
      _var2NodeIdMap_.erase(&v);
    }

    if (this->variablesSequence().exists(&v)) MultiDimImplementation< GUM_ELEMENT >::erase(v);
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  Size MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::realSize() const {
    return _internalNodeMap_.size();   // +  _valueMap_.size();
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::changeNotification(
      const Instantiation&          i,
      const DiscreteVariable* const var,
      Idx                           oldval,
      Idx                           newval) {}

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::setFirstNotification(
      const Instantiation& i) {}

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::setLastNotification(
      const Instantiation& i) {}

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::setIncNotification(
      const Instantiation& i) {}

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::setDecNotification(
      const Instantiation& i) {}

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::setChangeNotification(
      const Instantiation& i) {}

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  std::string MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::toString(
      const Instantiation* i) const {
    std::stringstream sBuff;
    sBuff << (*i) << " = " << this->get(*i);
    return sBuff.str();
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::copyFrom(
      const MultiDimContainer< GUM_ELEMENT >& src,
      Instantiation*                          p_i) const {
    GUM_ERROR(OperationNotAllowed,
              "You cannot copy another type of multiDim "
              "into a MultiDimFunctionGraph.");
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::copy(
      const MultiDimContainer< GUM_ELEMENT >& src) {
    GUM_ERROR(OperationNotAllowed,
              "You cannot copy another type of multiDim "
              "into a MultiDimFunctionGraph.");
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::copy(
      const MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >& src) {
    if (this->_isReduced_ != src.isReducedAndOrdered())
      GUM_ERROR(OperationNotAllowed,
                "Cannot copy a Reduced and Ordered "
                "function graph into Tree function graph "
                "(or vice-versa).")

    this->clear();

    // New variables insertion
    for (SequenceIteratorSafe< const DiscreteVariable* > varIter
         = src.variablesSequence().beginSafe();
         varIter != src.variablesSequence().endSafe();
         ++varIter)
      this->add(**varIter);

    std::vector< NodeId >       lifo;
    Bijection< NodeId, NodeId > src2dest;

    if (src.isTerminalNode(src.root()))
      this->manager()->setRootNode(this->manager()->addTerminalNode(src.nodeValue(src.root())));
    else {
      this->manager()->setRootNode(
          this->manager()->addInternalNode(src.node(src.root())->nodeVar()));
      src2dest.insert(src.root(), this->root());
      lifo.push_back(src.root());
    }

    // Depth-first exploration and copy
    while (!lifo.empty()) {
      NodeId currentSrcNodeId = lifo.back();
      lifo.pop_back();

      const InternalNode* currentSrcNode = src.node(currentSrcNodeId);

      for (Idx index = 0; index < currentSrcNode->nbSons(); ++index) {
        if (!src2dest.existsFirst(currentSrcNode->son(index))) {
          NodeId srcSonNodeId = currentSrcNode->son(index), destSonNodeId = 0;
          if (src.isTerminalNode(srcSonNodeId)) {
            destSonNodeId = this->manager()->addTerminalNode(src.nodeValue(srcSonNodeId));
          } else {
            destSonNodeId = this->manager()->addInternalNode(src.node(srcSonNodeId)->nodeVar());
            lifo.push_back(srcSonNodeId);
          }
          src2dest.insert(srcSonNodeId, destSonNodeId);
        }
        this->manager()->setSon(src2dest.second(currentSrcNodeId),
                                index,
                                src2dest.second(currentSrcNode->son(index)));
      }
    }

    manager()->clean();
  }

  // Copies src diagrams structure into this diagrams.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::copyAndReassign(
      const MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >&      src,
      const Bijection< const DiscreteVariable*, const DiscreteVariable* >& reassign) {
    if (this->_isReduced_ != src.isReducedAndOrdered())
      GUM_ERROR(OperationNotAllowed,
                "Cannot copy a Reduced and Ordered "
                "function graph into Tree function graph "
                "(or vice-versa).")

    this->clear();

    // New variables insertion
    for (SequenceIteratorSafe< const DiscreteVariable* > varIter
         = src.variablesSequence().beginSafe();
         varIter != src.variablesSequence().endSafe();
         ++varIter) {
      if ((*varIter)->domainSize() != reassign.second(*varIter)->domainSize())
        GUM_ERROR(InvalidArgument,
                  "Var " << (*varIter)->name() << " and var " << reassign.second(*varIter)->name()
                         << " have different domain sizes (" << (*varIter)->domainSize()
                         << "!=" << reassign.second(*varIter)->domainSize() << ")")
      this->add(*(reassign.second(*varIter)));
    }

    std::vector< NodeId >       lifo;
    Bijection< NodeId, NodeId > src2dest;

    if (src.isTerminalNode(src.root())) {
      this->manager()->setRootNode(this->manager()->addTerminalNode(src.nodeValue(src.root())));
    } else {
      this->manager()->setRootNode(
          this->manager()->addInternalNode(reassign.second(src.node(src.root())->nodeVar())));
      src2dest.insert(src.root(), this->root());
      lifo.push_back(src.root());
    }

    // Depth-first exploration and copy
    while (!lifo.empty()) {
      NodeId currentSrcNodeId = lifo.back();
      lifo.pop_back();

      const InternalNode* currentSrcNode = src.node(currentSrcNodeId);

      for (Idx index = 0; index < currentSrcNode->nbSons(); ++index) {
        if (!src2dest.existsFirst(currentSrcNode->son(index))) {
          NodeId srcSonNodeId = currentSrcNode->son(index), destSonNodeId = 0;
          if (src.isTerminalNode(srcSonNodeId)) {
            destSonNodeId = this->manager()->addTerminalNode(src.nodeValue(srcSonNodeId));
          } else {
            destSonNodeId = this->manager()->addInternalNode(
                reassign.second(src.node(srcSonNodeId)->nodeVar()));
            lifo.push_back(srcSonNodeId);
          }
          src2dest.insert(srcSonNodeId, destSonNodeId);
        }
        this->manager()->setSon(src2dest.second(currentSrcNodeId),
                                index,
                                src2dest.second(currentSrcNode->son(index)));
      }
    }

    manager()->clean();
  }

  // Copies src diagrams and multiply every value by the given scalar.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::copyAndMultiplyByScalar(
      const MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >& src,
      GUM_ELEMENT                                                     gamma) {
    if (this->_isReduced_ != src.isReducedAndOrdered())
      GUM_ERROR(OperationNotAllowed,
                "Cannot copy a Reduced and Ordered "
                "function graph into Tree function graph "
                "(or vice-versa).")

    this->clear();

    // New variables insertion
    for (SequenceIteratorSafe< const DiscreteVariable* > varIter
         = src.variablesSequence().beginSafe();
         varIter != src.variablesSequence().endSafe();
         ++varIter)
      this->add(**varIter);

    std::vector< NodeId >       lifo;
    HashTable< NodeId, NodeId > src2dest;

    if (src.isTerminalNode(src.root()))
      this->manager()->setRootNode(
          this->manager()->addTerminalNode(gamma * src.nodeValue(src.root())));
    else {
      this->manager()->setRootNode(
          this->manager()->addInternalNode(src.node(src.root())->nodeVar()));
      src2dest.insert(src.root(), this->root());
      lifo.push_back(src.root());
    }

    // Depth-first exploration an copy
    while (!lifo.empty()) {
      NodeId currentSrcNodeId = lifo.back();
      lifo.pop_back();

      const InternalNode* currentSrcNode = src.node(currentSrcNodeId);

      for (Idx index = 0; index < currentSrcNode->nbSons(); ++index) {
        if (!src2dest.exists(currentSrcNode->son(index))) {
          NodeId srcSonNodeId = currentSrcNode->son(index), destSonNodeId = 0;
          if (src.isTerminalNode(srcSonNodeId)) {
            destSonNodeId = this->manager()->addTerminalNode(gamma * src.nodeValue(srcSonNodeId));
          } else {
            destSonNodeId = this->manager()->addInternalNode(src.node(srcSonNodeId)->nodeVar());
            lifo.push_back(srcSonNodeId);
          }
          src2dest.insert(srcSonNodeId, destSonNodeId);
        }
        this->manager()->setSon(src2dest[currentSrcNodeId],
                                index,
                                src2dest[currentSrcNode->son(index)]);
      }
    }

    manager()->clean();
  }

  // Clears the function graph
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::clear() {
    _model_.clear();
    // Always discard the nodeId 0
    _model_.addNode();

    this->clearAllTerminalNodes();

    // Nodes cleaning
    for (HashTableIterator< NodeId, InternalNode* > nodeIter = _internalNodeMap_.begin();
         nodeIter != _internalNodeMap_.end();
         ++nodeIter) {
      delete nodeIter.val();
    }
    _internalNodeMap_.clear();

    // Cleaning the list of nodes for each variables
    for (HashTableIterator< const DiscreteVariable*, LinkedList< NodeId >* > varIter
         = _var2NodeIdMap_.begin();
         varIter != _var2NodeIdMap_.end();
         ++varIter) {
      delete varIter.val();
    }
    _var2NodeIdMap_.clear();

    for (SequenceIteratorSafe< const DiscreteVariable* > varIter
         = this->variablesSequence().rbeginSafe();
         varIter != this->variablesSequence().rendSafe();
         --varIter) {
      this->erase(**varIter);
    }
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  std::string
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::toDot(bool withBackArcs) const {
    std::stringstream output;
    std::stringstream terminalStream;
    std::stringstream nonTerminalStream;
    std::stringstream arcstream;
    //      std::stringstream defaultarcstream;
    output << '\n' << "digraph \" " << _tableName_ << "\" {" << '\n';

    terminalStream << "node [shape = box];" << '\n';
    nonTerminalStream << "node [shape = ellipse];" << '\n';
    std::string tab = "  ";

    for (NodeGraphPart::NodeIterator nodeIter = _model_.begin(); nodeIter != _model_.end();
         ++nodeIter)
      if (*nodeIter != 0) {
        if (this->isTerminalNode((NodeId)*nodeIter))
          terminalStream << tab << *nodeIter << ";" << tab << *nodeIter << " [label=\"" << *nodeIter
                         << " - " << std::setprecision(30) << this->terminalNodeValue(*nodeIter)
                         << "\"]"
                         << ";" << '\n';
        else {
          InternalNode* currentNode = _internalNodeMap_[*nodeIter];
          nonTerminalStream << tab << *nodeIter << ";" << tab << *nodeIter << " [label=\""
                            << *nodeIter << " - " << currentNode->nodeVar()->name() << "\"]"
                            << ";" << '\n';

          //              if (arcMap_[*nodeIter] != NULL)
          HashTable< NodeId, LinkedList< Idx >* > sonMap;
          for (Idx sonIter = 0; sonIter < currentNode->nbSons(); ++sonIter) {
            if (!sonMap.exists(currentNode->son(sonIter)))
              sonMap.insert(currentNode->son(sonIter), new LinkedList< Idx >());
            sonMap[currentNode->son(sonIter)]->addLink(sonIter);
          }

          for (auto sonIter = sonMap.beginSafe(); sonIter != sonMap.endSafe(); ++sonIter) {
            arcstream << tab << *nodeIter << " -> " << sonIter.key() << " [label=\" ";
            Link< Idx >* modaIter = sonIter.val()->list();
            while (modaIter) {
              arcstream << currentNode->nodeVar()->label(modaIter->element()) << ", ";
              modaIter = modaIter->nextLink();
            }
            arcstream << "\",color=\"#0000ff\"]"
                      << ";" << '\n';
            delete sonIter.val();
          }

          if (withBackArcs) {
            Link< Parent >* parentIter = currentNode->parents();
            while (parentIter != nullptr) {
              arcstream << tab << *nodeIter << " -> " << parentIter->element().parentId
                        << " [label=\"" << parentIter->element().modality << "\",color=\"#ff0000\"]"
                        << ";" << '\n';
              parentIter = parentIter->nextLink();
            }
          }
        }
      }

    output << terminalStream.str() << '\n'
           << nonTerminalStream.str() << '\n'
           << arcstream.str() << '\n'
           << "}" << '\n';

    return output.str();
  }

  // Returns a const reference to the manager of this diagram
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  const NodeGraphPart& MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::model() const {
    return _model_;
  }

  // Returns a const reference to the manager of this diagram
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimFunctionGraphManager< GUM_ELEMENT, TerminalNodePolicy >*
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::manager() {
    if (_manager_ == nullptr) {
      if (_isReduced_)
        _manager_ = new MultiDimFunctionGraphROManager< GUM_ELEMENT, TerminalNodePolicy >(this);
      else
        _manager_ = new MultiDimFunctionGraphTreeManager< GUM_ELEMENT, TerminalNodePolicy >(this);
    }
    return _manager_;
  }

  // Returns the id of the root node from the diagram
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  const NodeId& MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::root() const {
    return _root_;
  }

  // Indicates if given node is terminal or not
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  bool MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::isTerminalNode(
      const NodeId& node) const {
    return this->existsTerminalNodeWithId(node);
  }

  // Indicates if given node is terminal or not
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  bool MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::isInternalNode(
      const NodeId& node) const {
    return this->_internalNodeMap_.exists(node);
  }

  // Returns value associated to given node.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  const GUM_ELEMENT&
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::nodeValue(NodeId n) const {
    if (!isTerminalNode(n))
      GUM_ERROR(InvalidArgument, "Id " << n << " is not bound to any terminal node")
    return this->terminalNodeValue(n);
  }

  // Returns internalNode structure associated to that nodeId
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  const InternalNode*
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::node(NodeId n) const {
    if (!isInternalNode(n))
      GUM_ERROR(InvalidArgument, "Id " << n << " is not bound to any terminal node")
    return this->_internalNodeMap_[n];
  }

  // Returns the list of node associated to given variable
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  const LinkedList< NodeId >*
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::varNodeListe(
          const DiscreteVariable* var) const {
    if (!this->variablesSequence().exists(var))
      GUM_ERROR(InvalidArgument,
                "Var " << var->name() << " has not been inserted in the function graph")
    return _var2NodeIdMap_[var];
  }

  // Returns the name of the table represented by this structure.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  const std::string& MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::tableName() const {
    return _tableName_;
  }

  // Sets the name of the table represented by this structure.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::setTableName(
      std::string_view name) {
    _tableName_ = name;
  }

  // Returns true if this MultiDimFunctionGraph is reduced and Ordered.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  bool MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::isReducedAndOrdered() const {
    return _isReduced_;
  }

  // Returns a reduced and ordered instance.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >*
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::getReducedAndOrderedInstance() {
    return new MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >();
  }

  // Returns an arborescent instance
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >*
      MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::getTreeInstance() {
    return new MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >(false);
  }

  // Not implemented yet
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  void MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::replace_(
      const DiscreteVariable* x,
      const DiscreteVariable* y) {
    GUM_ERROR(OperationNotAllowed, "Not Implemented Yet")
  }

  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  GUM_ELEMENT& MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::get_(
      const Instantiation& inst) const {
    GUM_ERROR(OperationNotAllowed, "You can't edit a function by other mean than the manager")
  }

  // Return a data, given a Instantiation.
  template < typename GUM_ELEMENT, template < class > class TerminalNodePolicy >
  GUM_ELEMENT MultiDimFunctionGraph< GUM_ELEMENT, TerminalNodePolicy >::get(
      const Instantiation& inst) const {
    NodeId        currentNodeId = _root_;
    InternalNode* currentNode   = nullptr;
    while (!isTerminalNode(currentNodeId)) {
      currentNode   = _internalNodeMap_[currentNodeId];
      currentNodeId = currentNode->son(inst.val(*(currentNode->nodeVar())));
    }
    return this->terminalNodeValue(currentNodeId);
  }

}   // namespace gum
