/****************************************************************************
 *   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 Headers of the FMDPFactory class.
 *
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) and Jean-Christophe MAGNAN and Christophe
 * GONZALES(_at_AMU)
 */

#ifndef GUM_FMDP_FACTORY_H
#define GUM_FMDP_FACTORY_H
//======================================================================
#include <string>
#include <vector>

#include <string_view>
//======================================================================

#include <agrum/base/variables/labelizedVariable.h>
#include <agrum/FMDP/IfmdpFactory.h>

namespace gum {

  /**
   * @class FMDPFactory
   * @headerfile fmdpFactory.h <agrum/FMDP/fmdpFactory.h>
   * @brief A factory class to ease Factored Markov Decision Process
   * construction.
   * @ingroup fmdp_group
   *
   * A FMDPFactory will never create a Factored Markov Decision Process and
   * works on only one
   * Factored Markov Decision Process.
   *
   * The only exception of this behaviour is when you create a copy of the
   * factory,
   * it will create a copy of its FMDP. This is useful when you want to create
   * two FMDP sharing a common base.
   * However be very careful because the copy will not delete its FMDP.
   *
   * Each method will raise an OperationNotAllowed if you call it when the
   * factory
   * is not in a valid state for that call. The error message is "Illegal
   * state.".
   *
   */

  template < typename GUM_ELEMENT >
  class FMDPFactory: public AbstractFMDPFactory {
    public:
    // ==========================================================================
    /// @name Constructor & destructor.
    // ==========================================================================
    /// @{

    /**
     * Use this constructor if you want to use an already created factored
     * markov decision process.
     * @param fmdp A pointer over the @ref FMDP filled by this factory.
     */
    FMDPFactory(FMDP< GUM_ELEMENT >* fmdp);

    /**
     * @brief Destructor.
     *
     * To prevent strange behaviour you should always destroy a FMDPFactory
     * when it's state equals NONE.
     *
     * @throw FatalError Raised if the state of the factory prevents it to die
     *                   peacefully.
     */
    ~FMDPFactory() override;

    /// @}
    // ==========================================================================
    /// @name Getter and setters.
    // ==========================================================================
    /// @{

    /**
     * Returns the Factored Markov Decision Process created by this factory.
     * @throw OperationNotAllowed Raise if the state of the factory is different
     *                            than NONE.
     */
    FMDP< GUM_ELEMENT >* fmdp() const;

    /// Returns the current state of the factory.
    FMDPfactory_state state() const override;

    /// Returns a constant reference on a variable given it's name.
    /// @throw NotFound Raised if no variable matches the name.
    const DiscreteVariable* variable(std::string_view name) const override;

    /// @}
    // ==========================================================================
    /// @name Variable declaration methods (NONE -> VARIABLE)
    // ==========================================================================
    /// @{

    /// Tells the factory that we're in a variable declaration.
    void startVariableDeclaration() override;

    /// Tells the factory the current variable's name.
    /// @throw DuplicateElement Raised if a variable with the same name already
    ///                         exist.
    void variableName(std::string_view name) override;

    /// Tells the factory the current variable's description.
    void variableDescription(std::string_view desc) override;

    /// Adds a modality to the current variable.
    void addModality(std::string_view name) override;

    /// Tells the factory that we're out of a variable declaration.
    /// @throw UndefinedElement Raised if the variable isn't defined (or not
    ///                         enough defined).
    void endVariableDeclaration() override;

    /// @}
    // ==========================================================================
    /// @name Action declaration methods (NONE -> ACTION)
    // ==========================================================================
    /// @{

    /// Tells the factory that we're in an action declaration.
    void startActionDeclaration() override;

    /// Tells the factory to add an action to the current fmdp.
    void addAction(std::string_view action) override;

    /// Tells the factory that we're out of an action declaration.
    void endActionDeclaration() override;

    /// @}
    // ==========================================================================
    /// @name Transition declaration methods (NONE -> TRANSITION <- ACTION)
    // ==========================================================================
    /// @{

    /// Tells the factory that we're in a transition declaration.
    void startTransitionDeclaration() override;

    /// Tells the factory to add a transition table to the current fmdp.
    void addTransition(std::string_view var, const MultiDimAdressable* transition) override;

    /// Tells the factory to add a transition table to the current fmdp.
    /// This transition table will be extracted from incorporated
    /// multiDimFunctionGraph.
    void addTransition(std::string_view var) override;

    /// Tells the factory that we're out of a transition declaration.
    void endTransitionDeclaration() override;

    /// @}
    // ==========================================================================
    /// @name Cost declaration methods (NONE -> COST <- ACTION)
    // ==========================================================================
    /// @{

    /// Tells the factory that we're in a cost declaration.
    void startCostDeclaration() override;

    /// Tells the factory to add a cost table to the current fmdp.
    void addCost(const MultiDimAdressable* cost) override;

    /// Tells the factory to add a cost table to the current fmdp.
    /// This cost table will be extracted from incorporated
    /// multiDimFunctionGraph.
    void addCost() override;

    /// Tells the factory that we're out of a cost declaration.
    void endCostDeclaration() override;

    /// @}
    // ==========================================================================
    /// @name Reward declaration methods (NONE -> REWARD <- ACTION)
    // ==========================================================================
    /// @{

    /// Tells the factory that we're in a reward declaration.
    void startRewardDeclaration() override;

    /// Tells the factory that we're in a reward declaration mode where the
    /// global reward diagram is an operation between
    /// simplier decision diagram..
    void setOperationModeOn(std::string_view operationType) override;

    /// Tells the factory to add a reward table to the current fmdp.
    void addReward(const MultiDimAdressable* reward) override;

    /// Tells the factory to add a reward table to the current fmdp.
    /// This reward table will be extracted from incorporated
    /// multiDimFunctionGraph.
    void addReward() override;

    /// Tells the factory that we're out of a cost declaration.
    void endRewardDeclaration() override;

    /// @}
    // ==========================================================================
    /// @name Discount declaration methods (NONE -> DISCOUNT)
    // ==========================================================================
    /// @{

    /// Tells the factory that we're in a cost declaration.
    void startDiscountDeclaration() override;

    /// Tells the factory to add a cost table to the current fmdp.
    void addDiscount(float discount) override;

    /// Tells the factory that we're out of a cost declaration.
    void endDiscountDeclaration() override;

    /// @}
    // ==========================================================================
    /// @name FunctionGraph Creation specific methods
    /// @brief methods used by internal FunctionGraphfactory to build up
    /// a multidimFunctionGraph
    // ==========================================================================
    /// @{

    /// Insert in diagram a non terminal node
    NodeId addInternalNode(std::string_view name_of_var) override;

    /// Insert in diagram a terminal node
    NodeId addTerminalNode(float value) override;

    ///
    void addArc(NodeId from, NodeId to, Idx modality) override;

    /// add an arc in diagram
    void setRoot(NodeId rootId) override;

    /// @}

    private:
    // ==========================================================================
    /// @name Current constructed FMDP's parts.
    // ==========================================================================
    /// @{

    /// Depending on the context this flag is used for some VERY important
    /// reasons.
    bool _foo_flag_;

    /// Depending on the context this flag is used for some VERY important
    /// reasons.
    bool _bar_flag_;

    /// Just to keep track of strings between two start/end calls.
    std::vector< std::string > _stringBag_;

    /// Just to keep track of multidim between two start/end calls.
    std::vector< const MultiDimImplementation< GUM_ELEMENT >* > _ddBag_;

    /// Used in VARIABLE mode
    /// Checks if in  _stringBag_ there is no other modality with the same name.
    void _checkModalityInBag_(std::string_view mod);

    /// Reset the different parts used to constructed the FMDP.
    void _resetParts_();

    /// Insert every variables in the function graph
    void _initializeFunctionGraph_();

    /// Insert every variables in the function graph
    void _finalizeFunctionGraph_();

    /// @}

    /// State stack.
    std::vector< FMDPfactory_state > _states_;

    /// The constructed FMDP
    FMDP< GUM_ELEMENT >* _fmdp_;

    /// The FunctionGraph we're building at a given time
    MultiDimFunctionGraph< GUM_ELEMENT >* _FunctionGraph_;

    /// Mapping between a declared variable's name and itself.
    HashTable< std::string, const DiscreteVariable* > _varNameMap_;

    /// Action Id counter
    Idx _actionIdcpt_;

    /// Copy operator is illegal, use only copy constructor.
    FMDPFactory< GUM_ELEMENT >& operator=(const FMDPFactory< GUM_ELEMENT >& source);

    /// Raise an OperationNotAllowed with the message "Illegal state."
    void _illegalStateError_(std::string_view s);
  };


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

} /* namespace gum */

#include <agrum/FMDP/fmdpFactory_tpl.h>

#endif   // GUM_FMDP_FACTORY_H
