/****************************************************************************
 *   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 Header of the MultiDimArray class.
 *
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) & Christophe GONZALES(_at_AMU)
 */
#ifndef GUM_MULTI_DIM_ARRAY_H
#define GUM_MULTI_DIM_ARRAY_H

#include <vector>

#include <agrum/agrum.h>

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

namespace gum {

  template < typename GUM_ELEMENT >
  class MultiDimBijArray;

  // ===========================================================================
  // ===                          GUM_MULTI_DIM_ARRAY                        ===
  // ===========================================================================
  /**
   * @class MultiDimArray
   * @headerfile multiDimArray.h <agrum/base/multidim/multiDimArray.h>
   * @ingroup multidim_group
   *
   * @brief Multidimensional matrix stored as an array in memory.
   * @tparam GUM_ELEMENT The type of the scalars stroed in the multidimensional
   * table.
   */
  template < typename GUM_ELEMENT >
  class MultiDimArray final: public MultiDimWithOffset< GUM_ELEMENT > {
    public:
    template < typename GUM_ELEMENTBIS >
    friend class MultiDimBijArray;
    // =========================================================================
    /// @name Constructors, destructors and copy
    // =========================================================================
    /// @{

    /**
     * @brief Default constructor.
     *
     * Creates an empty null dimensional matrix
     */
    MultiDimArray();

    /**
     * @brief Copy constructor.
     *
     * The newly created matrix contains the same variables and the same values
     * as src, but no instantiation is associated to it.
     *
     * @param src The MultiDimArray to copy.
     */
    MultiDimArray(const MultiDimArray< GUM_ELEMENT >& src);

    /**
     * @todo : operator= for MultiDimArray
     *
     * @brief Copy operator.
     * @param src The MultiDimArray to copy.
     * @return Returns this MultiDimArray.
    MultiDimArray<GUM_ELEMENT>&
    operator=( const MultiDimArray<GUM_ELEMENT>& src );
     */

    /**
     * @brief Class destructor.
     *
     * Note that, when the multidimensional array is removed src memory, its
     * variables are not removed as well.
     */
    ~MultiDimArray() override;

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

    /// }
    // =========================================================================
    /// @name Inherited methods
    // =========================================================================
    /// @{

    /**
     * @brief Copy from a other MultiDimContainer.
     */
    void copyFrom(const MultiDimContainer< GUM_ELEMENT >& src) const override;

    /**
     * @brief Apply a function on every element of the container
     * @param f the function to apply
     */
    void apply(std::function< GUM_ELEMENT(GUM_ELEMENT) > f) const override;

    /**
     * @brief compute lfold for this container
     * @param f the function to apply
     * @param base the initial value
     */
    GUM_ELEMENT reduce(std::function< GUM_ELEMENT(GUM_ELEMENT, GUM_ELEMENT) > f,
                       GUM_ELEMENT                                            base) const override;

    /**
     * @brief Adds a variable.
     */
    void add(const DiscreteVariable& v) override;

    /**
     * @brief Removes a variable.
     */
    void erase(const DiscreteVariable& v) override;

    /**
     * @brief Returns the real size of this MultiDimArray.
     */
    Size realSize() const override;

    /**
     * @brief Fills the MultiDimArray with the given value.
     */
    void fill(const GUM_ELEMENT& d) const override;

    /**
     * @brief Returns the MultiDimArray name.
     */
    const std::string& name() const override;

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

    /**
     * @brief Returns the element stored in the multidimArray at a given
     * offset.
     *
     * The offset is computed as follows: let S={X1,...,Xk} be the sequence of
     * variables in the multidim, and let x1,...,xk be their values
     * respectively.  then the corresponding offset is x1 + |X1| * x2 + [X1| *
     * |X2| * x3 + ...  This thus corresponds to the internal MultiDimArray's
     * offset we would get by using function get with instantiation
     * {x1,...,xk}.
     *
     * @warning for efficiency reasons, this function does not check whether
     * the element referenced by the offset actually belongs to the
     * MultiDimArray.  If you want this additional feature, use instead
     * MultiDimArray::getByOffset(Idx).
     *
     * @param offset The offset of the returned value.
     * @brief Returns the element stored in the multidimArray at a given
     * offset.
     */
    const GUM_ELEMENT& unsafeGet(Idx offset) const;

    /**
     * @brief Returns the element stored in the multidimArray at a given
     * offset.
     *
     * The offset is computed as follows: let S={X1,...,Xk} be the sequence of
     * variables in the multidim, and let x1,...,xk be their values
     * respectively.  then the corresponding offset is x1 + |X1| * x2 + [X1| *
     * |X2| * x3 + ...  This thus corresponds to the internal MultiDimArray's
     * offset we would get by using function get with instantiation
     * {x1,...,xk}.
     *
     * @param offset The offset of the returned value.
     * @brief Returns the element stored in the multidimArray at a given
     * offset.
     *
     * @throws OutOfBounds Raised if the referenced element does not belong to
     * the MultiDimArray.
     */
    const GUM_ELEMENT& getByOffset(Idx offset) const;

    /**
     * @brief Modifies the element stored in the multidimArray at a given
     * offset.
     *
     * The offset is computed as follows: let S={X1,...,Xk} be the sequence of
     * variables in the multidim, and let x1,...,xk be their values
     * respectively.  then the corresponding offset is x1 + |X1| * x2 + [X1| *
     * |X2| * x3 + ...  This thus corresponds to the internal MultiDimArray's
     * offset we would get by using function get with instantiation
     * {x1,...,xk}.
     *
     * @warning for efficiency reasons, this function does not check whether
     * the element referenced by the offset actually belongs to the
     * MultiDimArray.  If you want this additional feature, use instead
     * function MultiDimArray::setByOffset(Idx, const GUM_ELEMENT&).
     *
     * @param offset The offset where to store the value.
     * @param val The value to store.
     */
    void unsafeSet(Idx offset, const GUM_ELEMENT& val);

    /**
     * @brief Modifies the element stored in the multidimArray at a given
     * offset.
     *
     * The offset is computed as follows: let S={X1,...,Xk} be the sequence of
     * variables in the multidim, and let x1,...,xk be their values
     * respectively.  then the corresponding offset is x1 + |X1| * x2 + [X1| *
     * |X2| * x3 + ...  This thus corresponds to the internal MultiDimArray's
     * offset we would get by using function get with instantiation
     * {x1,...,xk}.
     *
     * @param offset The offset where to store the value.
     * @param val The value to store.
     *
     * @throws OutOfBounds Raised if the referenced element does not belong to
     * the MultiDimArray.
     */
    void setByOffset(Idx offset, const GUM_ELEMENT& val);

    /**
     * @brief Returns a pointer to the contiguous data buffer.
     * @return Pointer to the first element of values_.
     */
    const GUM_ELEMENT* data() const noexcept override;

    /**
     * @brief Returns a mutable pointer to the contiguous data buffer.
     * @return Pointer to the first element of values_.
     */
    GUM_ELEMENT* data() noexcept override;

    /// @}

    protected:
    /// The true data : the values is mutable since we can change the value /
    /// in a const multiDimArray
    mutable std::vector< GUM_ELEMENT > values_;

    void commitMultipleChanges_() final;

    void commitMultipleChanges_(const GUM_ELEMENT&) final;

    GUM_ELEMENT& get_(const Instantiation& i) const final;

    void replace_(const DiscreteVariable* x, const DiscreteVariable* y) final;
  };


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


} /* namespace gum */

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

#endif /* GUM_MULTI_DIM_ARRAY_H */
