/****************************************************************************
 *   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 gum::MultiDimImplementation.
 *
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) & Christophe GONZALES(_at_AMU)
 */
#ifndef GUM_MULTI_DIM_IMPLEMENTATION_H
#define GUM_MULTI_DIM_IMPLEMENTATION_H

#include <vector>

#include <agrum/agrum.h>

#include <agrum/base/multidim/implementations/multiDimContainer.h>

namespace gum {

  // ===========================================================================
  // ===                    GUM_MULTI_DIM_IMPLEMENTATION                     ===
  // ===========================================================================
  /**
   * @class MultiDimImplementation
   * @headerfile multiDimImplementation.h
   *<agrum/base/multidim/multiDimImplementation.h>
   * @ingroup multidim_group
   *
   * @brief Abstract base class for all multi dimensionnal implementations
   *
   * The gum::MultiDimImplementation is an abstract class for all
   * multidimensional implementation of container of GUM_ELEMENT. Its purpose is
   * to implement base algorithms with no regard to how the storage is done
   * (tree, matrix...)
   *
   * It deals also with variables and implements pure virtual methods concerned
   * with dimensions.
   *
   * See operator<<(std::ostream&, const MultiDimImplementation<GUM_ELEMENT>&)
   * to print a gum::MultiDimImplementation.
   *
   * @tparam GUM_ELEMENT The type of the scalar stored in this multidimensional
   * matrix.
   */
  template < typename GUM_ELEMENT >
  class MultiDimImplementation: public MultiDimContainer< GUM_ELEMENT > {
    public:
    // =========================================================================
    /// @name Constructors / Destructors
    // =========================================================================
    /// @{

    /**
     * @brief Default constructor.
     */
    MultiDimImplementation();

    /**
     * @brief Copy constructor.
     * @param from The MultiDimImplementation to copy.
     */
    MultiDimImplementation(const MultiDimImplementation< GUM_ELEMENT >& from);

    /**
     * @brief Class destructor.
     */
    ~MultiDimImplementation() override;

    /// @}
    // =========================================================================
    /// @name Accessors / Modifiers
    // =========================================================================
    /// @{

    /**
     * @brief Returns the real name of the multiDim implementation
     *
     * In aGrUM, all the types of multi-dimensional arrays/functionals have a
     * name that describes what they are in reality. For instance, a table
     * stored in extension is a "MultiDimArray", one that stores only non zero
     * elements is a "MultiDimSparseArray", and so on. These names are unique
     * for each type of implementation and is used by the system to determine
     * which is the best functions to use, say, when we wish to use operators
     * such as operator+ on two MultiDimImplementations.
     *
     * @return Returns the real name of the multiDim implementation
     */
    virtual const std::string& name() const = 0;

    /**
     * @brief Returns the base class name of this MultiDimImplementation.
     *
     * This method is used for chosing a proposer operator when no specialized
     * operator have been defined.
     */
    const std::string& basename() const;

    /**
     * @brief Returns the real number of parameters used for this table.
     *
     * This function is used by the MultiDimImplementation::compressionRate()
     * method.
     *
     * @see MultiDimImplementation::compressionRate()
     *
     * @return Returns the real number of parameters used for this table.
     */
    virtual Size realSize() const = 0;

    /**
     * @brief The compression ratio of the table (depending on the type of
     * implementation).
     *
     * This method uses domainSize() and realSize() to compute the ratio,
     * both methods are virtual and should be surcharge if a subclass has a
     * special policies about memory management.
     *
     * @warning This compression ratio is not exactly the memory compression
     * ratio. It is computed in terms of number of parameters.
     *
     * @return Returns the compression ration of the table.
     */
    float compressionRate() const;

    /**
     * @brief Returns a pointer to the contiguous data buffer, or nullptr if
     * the implementation does not store data contiguously.
     *
     * Subclasses backed by a contiguous array (e.g. MultiDimArray) override
     * this to expose direct memory access, enabling zero-copy interop with
     * external libraries such as NumPy.
     *
     * @return Pointer to the first element of the contiguous buffer, or
     * nullptr.
     */
    virtual const GUM_ELEMENT* data() const noexcept;

    /**
     * @brief Returns a mutable pointer to the contiguous data buffer, or
     * nullptr if the implementation does not store data contiguously.
     *
     * @return Pointer to the first element of the contiguous buffer, or
     * nullptr.
     */
    virtual GUM_ELEMENT* data() noexcept;

    /// @}
    // ========================================================================
    /// @name MultiDimInterface implementation
    // ========================================================================
    /// @{
    Idx nbrDim() const override;

    Size domainSize() const override;

    void add(const DiscreteVariable& v) override;

    void erase(const DiscreteVariable& v) override;

    const Sequence< const DiscreteVariable* >& variablesSequence() const override;

    const DiscreteVariable& variable(Idx i) const override;
    const DiscreteVariable& variable(std::string_view name) const override;

    Idx pos(const DiscreteVariable& v) const override;

    bool contains(const DiscreteVariable& v) const override;

    bool empty() const override;

    /// @}
    // =========================================================================
    /// @name MultiDimAdressable implementation
    // =========================================================================
    /// @{

    bool registerSlave(Instantiation& slave) override;

    bool unregisterSlave(Instantiation& slave) override;

    /// @}
    // =========================================================================
    /// @name MultiDimContainer implementation
    // =========================================================================
    /// @{

    using MultiDimContainer< GUM_ELEMENT >::get;

    [[nodiscard]] MultiDimContainer< GUM_ELEMENT >* newFactory() const override = 0;

    void beginMultipleChanges() override;

    void endMultipleChanges() override;

    void endMultipleChanges(const GUM_ELEMENT&) override;

    /// @}

    protected:
    // =========================================================================
    /// @name Fast large modifications in structures
    // =========================================================================
    /// @{

    /**
     * @brief Synchronize content after MultipleChanges.
     */
    virtual void commitMultipleChanges_();

    /**
     * @brief Synchronize content after MultipleChanges.
     * @param value Default value for uninitialized values.
     */
    virtual void commitMultipleChanges_(const GUM_ELEMENT& value);

    /**
     * @brief Get the actual change method of this MultiDimImplementation.
     * @return Returns true if in multiple changes.
     */
    bool isInMultipleChangeMethod_() const;

    /**
     * @brief Get the actual state of *this.
     * @return Returns true if a commit is needed.
     */
    bool isCommitNeeded_() const;

    /**
     * @brief Returns a constant reference over the list of slaved
     * instantiations.
     * @return Returns a constant reference over the list of slaved
     * instantiations.
     */
    const List< Instantiation* >& slaves_() const;

    /**
     * @brief Replace variable x by y.
     *
     * Technically this should be call by any subclass overloading this method
     * to proceed with the changes in this class containers.
     *
     * @param x The first variable to swap.
     * @param y The second variable to swap.
     */
    void replace_(const DiscreteVariable* x, const DiscreteVariable* y) override;

    /**
     * @brief Inverts variables at position p1 and p2
     *
     * Call this from subclass when you want to invert position of two
     * variables in the MultiDimImplementation.
     *
     * @param p1 The first position.
     * @param p2 The second position.
     */
    virtual void invert_(Idx p1, Idx p2);

    /// @}

    /**
     * @brief Returns the implementation for this object (may be *this).
     */
    const MultiDimImplementation< GUM_ELEMENT >* content() const final;

    /**
     * @brief Returns the implementation for this object (may be *this).
     */
    MultiDimImplementation< GUM_ELEMENT >* content() final;

    private:
    /// List of discrete variables (dimensions).
    Sequence< const DiscreteVariable* > _vars_;

    /// List of instantiations of the tuples (sequences) of variables.
    List< Instantiation* > _slaveInstantiations_;

    /// Used to represent in which change method this MultiDimImplementation is.
    enum class _InternalChangeMethod_ : char { DIRECT_CHANGE, MULTIPLE_CHANGE };

    /// Used to represent in which change state this MultiDimImplementation is.
    enum class _InternalChangeState_ : char { NO_CHANGE, NOT_COMMITTED_CHANGE };

    /// The current change method.
    _InternalChangeMethod_ _internalChangeMethod_;

    /// The current change state.
    _InternalChangeState_ _internalChangeState_;

    /// This MultiDimImplementation domain size.
    Size _domainSize_;

    /**
     * @brief Change the  _internalChangeState_ to NOT_COMMITTED_CHANGE.
     */
    void _setNotCommitedChange_();
  };

  /**
   * @brief For friendly displaying the content of the array.
   * @ingroup multidim_group
   */
  template < typename GUM_ELEMENT >
  std::ostream& operator<<(std::ostream&, const MultiDimImplementation< GUM_ELEMENT >&);

} /* namespace gum */

#include <agrum/base/multidim/implementations/multiDimImplementation_tpl.h>

#endif /* GUM_MULTI_DIM_IMPLEMENTATION_H */
