////////////////////////////////////////////////////////////////////////////////
//
//  File: GeomFactors.h
//
//  For more information, please see: http://www.nektar.info/
//
//  The MIT License
//
//  Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
//  Department of Aeronautics, Imperial College London (UK), and Scientific
//  Computing and Imaging Institute, University of Utah (USA).
//
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included
//  in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
//  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  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.
//
//  Description: Geometric Factors base class
//
////////////////////////////////////////////////////////////////////////////////

#ifndef NEKTAR_SPATIALDOMAINS_GEOMFACTORS_H
#define NEKTAR_SPATIALDOMAINS_GEOMFACTORS_H

#include <unordered_set>

#include <LibUtilities/BasicUtils/HashUtils.hpp>
#include <LibUtilities/Foundations/Basis.h>
#include <LibUtilities/Memory/ObjectPool.hpp>
#include <SpatialDomains/SpatialDomains.hpp>
#include <SpatialDomains/SpatialDomainsDeclspec.h>
#include <StdRegions/StdExpansion.h>
#include <StdRegions/StdRegions.hpp>

namespace Nektar::SpatialDomains
{
// Forward declarations
class GeomFactors;

/// Equivalence test for GeomFactors objects
SPATIAL_DOMAINS_EXPORT bool operator==(const GeomFactors &lhs,
                                       const GeomFactors &rhs);

/// A vector of GeomFactor pointers.
typedef std::vector<GeomFactors *> GeomFactorsVector;
/// An unordered set of GeomFactor pointers.
typedef std::unordered_set<GeomFactors *> GeomFactorsSet;
/// Storage type for derivative of mapping.
typedef Array<OneD, Array<OneD, Array<OneD, NekDouble>>> DerivStorage;

/// Calculation and storage of geometric factors associated with the
/// mapping from StdRegions reference elements to a given LocalRegions
/// physical element in the mesh.
class GeomFactors
{
public:
    /// Constructor for GeomFactors class.
    SPATIAL_DOMAINS_EXPORT GeomFactors(
        const GeomType gtype, const int coordim,
        const StdRegions::StdExpansionSharedPtr &xmap,
        const std::vector<Array<OneD, NekDouble>> &coords,
        const LibUtilities::PointsKeyVector &keyTgt);

    /// Copy constructor.
    SPATIAL_DOMAINS_EXPORT GeomFactors(const GeomFactors &S);

    /// Tests if two GeomFactors classes are equal.
    SPATIAL_DOMAINS_EXPORT friend bool operator==(const GeomFactors &lhs,
                                                  const GeomFactors &rhs);

    /// Destructor.
    SPATIAL_DOMAINS_EXPORT ~GeomFactors() = default;

    /// Return the derivative of the mapping with respect to the
    /// reference coordinates,
    /// \f$\frac{\partial \chi_i}{\partial \xi_j}\f$.
    inline DerivStorage GetDeriv(const LibUtilities::PointsKeyVector &keyTgt);

    /// Return the Jacobian of the mapping.
    inline const Array<OneD, const NekDouble> GetJac();
    /// Compute the Jacobian of the mapping.
    SPATIAL_DOMAINS_EXPORT Array<OneD, NekDouble> ComputeJac(
        const LibUtilities::PointsKeyVector &keyTgt) const;

    /// Return the Laplacian coefficients \f$g_{ij}\f$.
    inline const Array<TwoD, const NekDouble> GetGmat(
        const LibUtilities::PointsKeyVector &keyTgt);

    /// Return the derivative of the reference coordinates with respect
    /// to the mapping, \f$\frac{\partial \xi_i}{\partial \chi_j}\f$.
    inline const Array<TwoD, const NekDouble> GetDerivFactors();
    /// Compute the derivative of the reference coordinates with respect
    /// to the mapping, \f$\frac{\partial \xi_i}{\partial \chi_j}\f$.
    SPATIAL_DOMAINS_EXPORT Array<TwoD, NekDouble> ComputeDerivFactors(
        const LibUtilities::PointsKeyVector &keyTgt) const;

    /// Returns moving frames
    inline void GetMovingFrames(const LibUtilities::PointsKeyVector &keyTgt,
                                const SpatialDomains::GeomMMF MMFdir,
                                const Array<OneD, const NekDouble> &CircCentre,
                                Array<OneD, Array<OneD, NekDouble>> &outarray);

    /// Returns whether the geometry is regular or deformed.
    inline GeomType GetGtype();

    /// Determine if element is valid and not self-intersecting.
    inline bool IsValid() const;

    /// Return the number of dimensions of the coordinate system.
    inline int GetCoordim() const;

    /// Computes a hash of this GeomFactors element.
    inline size_t GetHash();

protected:
    /// Type of geometry (e.g. eRegular, eDeformed, eMovingRegular).
    GeomType m_type;
    /// Dimension of expansion.
    int m_expDim;
    /// Dimension of coordinate system.
    int m_coordDim;
    /// Validity of element (Jacobian positive)
    bool m_valid;
    /// Default points at which to compute jacobian and derivative factors
    LibUtilities::PointsKeyVector m_keyTgt;

    /// Principle tangent direction for MMF.
    enum GeomMMF m_MMFDir;

    /// Stores information about the expansion.
    StdRegions::StdExpansionSharedPtr m_xmap;
    /// Stores coordinates of the geometry.
    std::vector<Array<OneD, NekDouble>> m_coords;
    /// Jacobian vector
    Array<OneD, NekDouble> m_jac;
    /// DerivFactors vector
    Array<TwoD, NekDouble> m_derivFactor;
    /// Indicates whether stored jacobian has been populated
    bool m_jacComputed = false;
    /// Indicates whether stored derivative factors have been populated
    bool m_derivFacComputed = false;
    /// Return the Xmap;
    inline StdRegions::StdExpansionSharedPtr &GetXmap();

private:
    /// Tests if the element is valid and not self-intersecting.
    void CheckIfValid();

    SPATIAL_DOMAINS_EXPORT DerivStorage
    ComputeDeriv(const LibUtilities::PointsKeyVector &keyTgt) const;

    /// Computes the Laplacian coefficients \f$g_{ij}\f$.
    SPATIAL_DOMAINS_EXPORT Array<TwoD, NekDouble> ComputeGmat(
        const LibUtilities::PointsKeyVector &keyTgt) const;

    SPATIAL_DOMAINS_EXPORT void ComputeMovingFrames(
        const LibUtilities::PointsKeyVector &keyTgt,
        const SpatialDomains::GeomMMF MMFdir,
        const Array<OneD, const NekDouble> &CircCentre,
        Array<OneD, Array<OneD, NekDouble>> &movingframes);

    /// Perform interpolation of data between two point
    /// distributions.
    void Interp(const LibUtilities::PointsKeyVector &src_points,
                const Array<OneD, const NekDouble> &src,
                const LibUtilities::PointsKeyVector &tgt_points,
                Array<OneD, NekDouble> &tgt) const;

    /// Compute the transpose of the cofactors matrix
    void Adjoint(const Array<TwoD, const NekDouble> &src,
                 Array<TwoD, NekDouble> &tgt) const;

    void ComputePrincipleDirection(
        const LibUtilities::PointsKeyVector &keyTgt,
        const SpatialDomains::GeomMMF MMFdir,
        const Array<OneD, const NekDouble> &CircCentre,
        Array<OneD, Array<OneD, NekDouble>> &output);

    void VectorNormalise(Array<OneD, Array<OneD, NekDouble>> &array);

    void VectorCrossProd(const Array<OneD, const Array<OneD, NekDouble>> &v1,
                         const Array<OneD, const Array<OneD, NekDouble>> &v2,
                         Array<OneD, Array<OneD, NekDouble>> &v3);
};

/// A hash functor for geometric factors. Utilises
/// GeomFactors::GetHash.
struct GeomFactorsHash
{
    std::size_t operator()(GeomFactors *const &p) const
    {
        return p->GetHash();
    }
};

/**
 * @param   tpoints     Target point distributions.
 * @returns             Derivative evaluated at target point
 *                      distributions.
 * @see                 GeomFactors::ComputeDeriv
 */
inline DerivStorage GeomFactors::GetDeriv(
    const LibUtilities::PointsKeyVector &tpoints)
{
    return ComputeDeriv(tpoints);
}

/**
 * Returns Jacobian.
 *
 * @returns             Jacobian evaluated at target point
 *                      distributions.
 * @see                 GeomFactors::ComputeJac
 */
inline const Array<OneD, const NekDouble> GeomFactors::GetJac()
{
    if (!m_jacComputed)
    {
        m_jac         = ComputeJac(m_keyTgt);
        m_jacComputed = true;
    }
    return m_jac;
}

/**
 * @param   keyTgt      Target point distributions.
 * @returns             Inverse metric tensor evaluated at target point
 *                      distributions.
 * @see                 GeomFactors::ComputeGmat
 */
inline const Array<TwoD, const NekDouble> GeomFactors::GetGmat(
    const LibUtilities::PointsKeyVector &keyTgt)
{
    return ComputeGmat(keyTgt);
}

/**
 * Returns derivative factors.
 *
 * @returns             Derivative factors evaluated at target point
 *                      distributions.
 * @see                 GeomFactors::ComputeDerivFactors
 */
inline const Array<TwoD, const NekDouble> GeomFactors::GetDerivFactors()
{
    if (!m_derivFacComputed)
    {
        m_derivFactor      = ComputeDerivFactors(m_keyTgt);
        m_derivFacComputed = true;
    }
    return m_derivFactor;
}

inline void GeomFactors::GetMovingFrames(
    const LibUtilities::PointsKeyVector &keyTgt,
    const SpatialDomains::GeomMMF MMFdir,
    const Array<OneD, const NekDouble> &CircCentre,
    Array<OneD, Array<OneD, NekDouble>> &outarray)
{
    ComputeMovingFrames(keyTgt, MMFdir, CircCentre, outarray);
}

/**
 * A geometric shape is considered regular if it has constant geometric
 * information, and deformed if this information changes throughout the
 * shape.
 * @returns             The type of geometry.
 * @see GeomType
 */
inline GeomType GeomFactors::GetGtype()
{
    return m_type;
}

/**
 * This is greater than or equal to the expansion dimension.
 * @returns             The dimension of the coordinate system.
 */
inline int GeomFactors::GetCoordim() const
{
    return m_coordDim;
}

/**
 * The validity test is performed by testing if the Jacobian is
 * negative at any point in the shape.
 * @returns             True if the element is not self-intersecting.
 */
inline bool GeomFactors::IsValid() const
{
    return m_valid;
}

/**
 * The hash is computed from the geometry type, expansion dimension,
 * coordinate dimension and Jacobian.
 * @returns             Hash of this GeomFactors object.
 */
inline size_t GeomFactors::GetHash()
{
    LibUtilities::PointsKeyVector ptsKeys  = m_xmap->GetPointsKeys();
    const Array<OneD, const NekDouble> jac = ComputeJac(ptsKeys);

    size_t hash = 0;
    hash_combine(hash, (int)m_type, m_expDim, m_coordDim);
    if (m_type == eDeformed)
    {
        hash_range(hash, jac.begin(), jac.end());
    }
    else
    {
        hash_combine(hash, jac[0]);
    }
    return hash;
}

StdRegions::StdExpansionSharedPtr &GeomFactors::GetXmap(void)
{
    return m_xmap;
}

} // namespace Nektar::SpatialDomains

#endif
