/****************************************************************************
 *   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                           *
 *                                                                          *
 ****************************************************************************/


/**
 * @file
 * @brief Class representing Fragment of Bayesian networks
 *
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) & Christophe GONZALES(_at_AMU)
 *
 */
#ifndef GUM_BAYES_NET_FRAGMENT_H
#define GUM_BAYES_NET_FRAGMENT_H

#include <agrum/agrum.h>

#include <agrum/base/graphs/parts/listeners/diGraphListener.h>
#include <agrum/BN/BayesNet.h>

namespace gum {
  /**
   * @class BayesNetFragment
   * @headerfile BayesNetFragment.h <agrum/BN/BayesNetFragment.h>
   * @brief Portion of a BN identified by the list of nodes and a BayesNet.
   * @ingroup bn_group
   *
   * @author Pierre-Henri WUILLEMIN(_at_LIP6)
   *
   * This class is a decorator of a BayesNet implementing the IBayesNet
   * interface. CPTs can be shared with the BN or can be specific to the
   * Fragment if different.
   *
   * BayesNetFragment is a DiGraphListener in order to be synchronized
   * (especially when removing nodes or arcs).
   *
   * In a BayesNetFragment, one can install or remove nodes. An arc can be in
   * the fragment if and only if its head and tail are installed in the
   * fragment.  *When installing a node, all the arcs that can be added in the
   * fragment are *effectively installed (resp.  *when uninstalling a node,
   * etc.).
   *
   * A BayesNetFragment can redefine tensor for node. The main reason is to
   * be able to install a node without installing all its parents (and its
   * ascendants). So local CPT to the node can be installed. However, it is not
   * done automatically.
   *
   * If a cpt is not locally defined, the fragment uses the cpt defined in the
   * referred BN. The checkConsistency() method verifies that, for all
   * installed nodes, either all the parents are installed or a local CPT is
   * defined.
   */
  template < GUM_Numeric GUM_SCALAR >
  class BayesNetFragment: public IBayesNet< GUM_SCALAR >, public gum::DiGraphListener {
    /// The referred BayesNet
    const IBayesNet< GUM_SCALAR >& _bn_;

    /// Mapping between the variable's id and their CPT specific to this
    /// Fragment.
    NodeProperty< const Tensor< GUM_SCALAR >* > _localCPTs_;

    public:
    /// @name Constructors / Destructors
    /// @{
    BayesNetFragment()                                               = delete;
    BayesNetFragment(const BayesNetFragment< GUM_SCALAR >& fragment) = delete;
    BayesNetFragment(BayesNetFragment< GUM_SCALAR >&& fragment)      = delete;

    explicit BayesNetFragment(const IBayesNet< GUM_SCALAR >& bn);

    ~BayesNetFragment() override;
    /// @}

    /// @name signals
    /// @{

    /// the action to take when a new node is inserted into the graph
    /** @param src the object that sent the signal
     * @param id the id of the new node inserted into the graph */
    void whenNodeAdded(const void* src, NodeId id) final;

    /// the action to take when a node has just been removed from the graph
    /** @param src the object that sent the signal
     * @param id the id of the node has just been removed from the graph */
    void whenNodeDeleted(const void* src, NodeId id) final;

    /// the action to take when a new arc is inserted into the graph
    /** @param src the object that sent the signal
     * @param from the id of tail of the new arc inserted into the graph
     * @param to the id of head of the new arc inserted into the graph */
    void whenArcAdded(const void* src, NodeId from, NodeId to) final;

    /// the action to take when an arc has just been removed from the graph
    /** @param src the object that sent the signal
     * @param from the id of tail of the arc removed from the graph
     * @param to the id of head of the arc removed from the graph */
    void whenArcDeleted(const void* src, NodeId from, NodeId to) final;
    /// @}

    /// @name IBayesNet interface
    /// @{

    /**
     * Returns the CPT of a variable.
     *
     * @throw NotFound If no variable's id matches varId.
     */
    const Tensor< GUM_SCALAR >& cpt(NodeId varId) const final;

    const Tensor< GUM_SCALAR >& cpt(std::string_view name) const;

    /**
     * Returns a constant reference to the VariableNodeMap of this BN
     */
    const VariableNodeMap& variableNodeMap() const final;

    /**
     * Returns a constant reference over a variabe given it's node id.
     *
     * @throw NotFound If no variable's id matches varId.
     */
    const DiscreteVariable& variable(NodeId id) const final;

    virtual const DiscreteVariable& variable(std::string_view name) const final;

    /**
     * Return id node from discrete var pointer.
     *
     * @throw NotFound If no variable match var.
     */
    NodeId nodeId(const DiscreteVariable& var) const final;

    /**
     * Getter by name
     *
     * @throw NotFound if no such name exists in the graph.
     */
    NodeId idFromName(std::string_view name) const final;

    /**
     * Getter by name
     *
     * @throw NotFound if no such name exists in the graph.
     */
    const DiscreteVariable& variableFromName(std::string_view name) const final;

    /**
     * creates a dot representing the whole referred BN hilighting the fragment.
     * @return Returns a dot representation of this fragment
     */
    std::string toDot() const final;

    /// @}

    /// @name API for Fragment
    /// @{

    /**
     * check if a certain NodeId exists in the fragment
     */
    bool isInstalledNode(NodeId id) const;

    bool isInstalledNode(std::string_view name) const;

    /**
     * install a node referenced by its nodeId
     *
     * @throw NotFound if the node does not exist in the referred BN
     * @warning nothing happens if the node is already installed
     */
    void installNode(NodeId id);

    void installNode(std::string_view name);

    /**
     * install a node and all its ascendants
     *
     * @throw NotFound if the node does not exist in the referred BN
     * @warning nothing happens if the node is already installed
     */
    void installAscendants(NodeId id);

    void installAscendants(std::string_view name);

    /**
     * uninstall a node referenced by its nodeId
     *
     * @warning nothing happens if the node is not installed
     */
    void uninstallNode(NodeId id);

    void uninstallNode(std::string_view name);

    /**
     * install a local marginal BY COPY for a node into the fragment.
     * This function will remove all the arcs from the parents to the node.
     * @param id the nodeId
     * @param pot the tensor
     * @throw NotFound if the id is not in the fragment
     * @throw OperationNotAllowed if the tensor is not compliant with the
     *variable
     *(or is not a marginal)
     **/
    void installMarginal(NodeId id, const Tensor< GUM_SCALAR >& pot);

    void installMarginal(std::string_view name, const Tensor< GUM_SCALAR >& pot);

    /**
     * install a local cpt BY COPYfor a node into the fragment.
     * This function will change the arcs from the parents to the node in order
     *to be
     * consistent with the new local tensor.
     * @param id the nodeId
     * @param pot the tensor to be copied
     *
     * @throw NotFound if the id is not in the fragment
     * @throw OperationNotAllowed if the tensor is not compliant with the
     *variable or if  a variable in the CPT is not a parent in the referred bn.
     **/
    void installCPT(NodeId id, const Tensor< GUM_SCALAR >& pot);

    void installCPT(std::string_view name, const Tensor< GUM_SCALAR >& pot);

    /**
     * uninstall a local CPT.
     *
     * @warning  Nothing happens if no local CPT for this nodeId or if the node
     *is
     *not installed.
     */
    void uninstallCPT(NodeId id);

    void uninstallCPT(std::string_view name);

    /**
     * returns true if the nodeId's (local or not) cpt is consistent with its
     * parents
     * in the fragment
     * @throw NotFound if the id is not in the fragment
     */
    bool checkConsistency(NodeId id) const;

    bool checkConsistency(std::string_view name) const;

    /**
     * returns true if all nodes in the fragment are consistent
     *
     * @throws gum::OperatioNotAllowed if the fragment is not consistent.
     */
    bool checkConsistency() const;

    /// @}


    /** create a brand new BayesNet from a fragment.
     *
     * @return the new BayesNet<GUM_SCALAR>
     */
    gum::BayesNet< GUM_SCALAR > toBN() const;

    using IBayesNet< GUM_SCALAR >::nodes;
    using IBayesNet< GUM_SCALAR >::dag;

    protected:
    // remove an arc
    void uninstallArc_(NodeId from, NodeId to);

    // add an arc
    void installArc_(NodeId from, NodeId to);

    // install a CPT BY COPY, create or delete arcs. Checks are made in public
    // methods In particular, it is assumed that all the variables in the pot are
    // in the fragment
    void installCPT_(NodeId id, const Tensor< GUM_SCALAR >& pot);

    /**
     * uninstall a local CPT. Does nothing if no local CPT for this nodeId
     * No check. No change in the topology. Checks are made in public methods.
     */
    void uninstallCPT_(NodeId id);
  };


#ifndef GUM_NO_EXTERN_TEMPLATE_CLASS
  extern template class BayesNetFragment< double >;
#endif

}   // namespace gum

#include <agrum/BN/BayesNetFragment_tpl.h>

#endif   // GUM_BAYES_NET_FRAGMENT_H
