/****************************************************************************
 *   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 for implementation of factored markov decision process.
 *
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) and Jean-Christophe MAGNAN and Christophe
 * GONZALES(_at_AMU)
 */
#ifndef GUM_FACTORED_MARKOV_DECISION_PROCESS_H
#define GUM_FACTORED_MARKOV_DECISION_PROCESS_H
//======================================================================
#include <string>
#include <vector>

#include <string_view>
//======================================================================
//======================================================================
#include <agrum/base/multidim/implementations/multiDimFunctionGraph.h>

//======================================================================

namespace gum {
  /**
   * @class FMDP
   * @brief This class is used to implement factored decision process.
   * @ingroup fmdp_group
   *
   * This class supports a mechanism which allows user to give, for each
   * variable ivolved in the process, a default transition probability table
   * and to describe for specific actions a different table.
   *
   */

  template < typename GUM_ELEMENT >
  class FMDP {
    template < typename GUM_ELEMENT_O >
    using VarTransitionTable
        = HashTable< const DiscreteVariable*, const MultiDimImplementation< GUM_ELEMENT_O >* >;

    public:
    // ===========================================================================
    /// @name Constructors, Destructors.
    // ===========================================================================
    /// @{

    /**
     * Default constructor.
     */
    FMDP(bool onDestructionDeleteVar = false);

    /**
     * Default destructor.
     */
    ~FMDP();

    /// @}

    // ===========================================================================
    /// @name Variable Handling Methods.
    // ===========================================================================
    /// @{

    /**
     * Adds a variable to FMDP description
     * @throw DuplicateElement if a similar variable already exists
     */
    void addVariable(const DiscreteVariable* var);

    /**
     * Returns an iterator reference to he beginning of the list of variables
     */
    SequenceIteratorSafe< const DiscreteVariable* > beginVariables() const;

    /**
     * Returns an iterator reference to the end of the list of variables
     */
    SequenceIteratorSafe< const DiscreteVariable* > endVariables() const;

    /**
     * Returns the primed variable associate to the given main variable
     */
    const DiscreteVariable* main2prime(const DiscreteVariable* mainVar) const;

    /**
     * Returns the map binding main variables and prime variables
     */
    const Bijection< const DiscreteVariable*, const DiscreteVariable* >& mapMainPrime() const;

    /// @}

    // ===========================================================================
    /// @name Action Handling Methods.
    // ===========================================================================
    /// @{

    /**
     * Adds an action to FMDP description
     * @throw DuplicateElement if an action with same name already exists
     */
    void addAction(Idx actionId, std::string_view action);

    /**
     * Returns an iterator reference to he beginning of the list of actions
     */
    SequenceIteratorSafe< Idx > beginActions() const;

    /**
     * Returns an iterator reference to the end of the list of actions
     */
    SequenceIteratorSafe< Idx > endActions() const;

    /// @}

    // ===========================================================================
    /// @name Transition methods.
    // ===========================================================================
    /// @{

    /**
     * Adds a variable transition table to specified action
     * @throw NotFound if action or var does not exists
     * @throw DuplicateElement if variable already has a transition for this
     * action
     */
    void addTransitionForAction(Idx                                          actionId,
                                const DiscreteVariable*                      var,
                                const MultiDimImplementation< GUM_ELEMENT >* transition);

    /**
     * Adds a default variable transition
     * @throw NotFound if var does not exists
     * @throw DuplicateElement if variable already has a default transition
     */
    void addTransition(const DiscreteVariable*                      var,
                       const MultiDimImplementation< GUM_ELEMENT >* transition);

    /**
     * Returns transition associated to given in parameter variable and the
     * given action
     */
    const MultiDimImplementation< GUM_ELEMENT >* transition(Idx                     actionId,
                                                            const DiscreteVariable* v) const;

    /// @}

    // ===========================================================================
    /// @name Cost methods.
    // ===========================================================================
    /// @{

    /**
     * Adds a cost table to specified action
     * @throw NotFound if action does not exists
     */
    void addCostForAction(Idx actionId, const MultiDimImplementation< GUM_ELEMENT >* cost);

    /**
     * Adds a default variable cost
     * @throw DuplicateElement if a default cost exists already
     */
    void addCost(const MultiDimImplementation< GUM_ELEMENT >* cost);

    /**
     * Returns the reward table of mdp
     */
    const MultiDimImplementation< GUM_ELEMENT >* cost(Idx actionId = 0) const;

    /// @}

    // ===========================================================================
    /// @name Reward methods.
    // ===========================================================================
    /// @{

    /**
     * Adds a default variable reward
     * @throw DuplicateElement if a default reward exists already
     */
    void addRewardForAction(Idx actionId, const MultiDimImplementation< GUM_ELEMENT >* reward);

    /**
     * Adds a default variable reward
     * @throw DuplicateElement if a default reward exists already
     */
    void addReward(const MultiDimImplementation< GUM_ELEMENT >* reward);

    /**
     * Returns the reward table of mdp
     */
    const MultiDimImplementation< GUM_ELEMENT >* reward(Idx actionId = 0) const;

    /// @}

    // ===========================================================================
    /// @name FMPD miscelleanous methods.
    // ===========================================================================
    /// @{

    /**
     * Returns name of action given in parameter
     */
    const std::string& actionName(Idx actionId) const;

    /**
     * Returns action id
     */
    Idx actionId(std::string_view) const;

    /**
     * Displays the FMDP in a Dot format
     */
    std::string toString() const;

    /**
     * Returns the map binding main variables and prime variables
     */
    Size size() const;

    /**
     * Transfers ownership of variables to this FMDP (they will be deleted on destruction).
     * Call this when the FMDP has been populated via FMDPFactory.
     */
    void setDeleteVariablesOnDestruction(bool b);

    /**
     * Sets the discount factor.
     */
    void setDiscount(GUM_ELEMENT discount);

    /**
     * Returns the discount factor.
     */
    GUM_ELEMENT discount() const;

    /// @}

    private:
    /// Sequence de variables and its iterator
    Sequence< const DiscreteVariable* > _varSeq_;

    Sequence< Idx > _actionSeq_;

    /// Mapping from a main variable to its associated primed version
    Bijection< const DiscreteVariable*, const DiscreteVariable* > _main2primed_;

    /// Bijection mapping an action name to its id
    Bijection< Idx, const std::string* > _actionMap_;

    /// Table which give for each action a table containing variables transition
    /// cpt
    HashTable< Idx, VarTransitionTable< GUM_ELEMENT >* > _actionTransitionTable_;

    /// Table which give for each action cost table
    HashTable< Idx, const MultiDimImplementation< GUM_ELEMENT >* > _actionCostTable_;

    /// Table which give for each action reward table
    HashTable< Idx, const MultiDimImplementation< GUM_ELEMENT >* > _actionRewardTable_;

    /// Boolean indicates whether or not main variables should be deleted on
    /// destruction of this instance
    /// Usually the case when fmdp has been initialized with the factory
    bool _onDestructionDeleteVars_;

    GUM_ELEMENT _discount_{GUM_ELEMENT(1)};
  };


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


} /* namespace gum */

#include <agrum/FMDP/fmdp_tpl.h>

#endif   // GUM_FACTORED_MARKOV_DECISION_PROCESS_H
