// Copyright (c) 2015 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#ifndef _BRepGProp_Gauss_HeaderFile
#define _BRepGProp_Gauss_HeaderFile

#include <NCollection_Handle.hxx>
#include <NCollection_Array1.hxx>

template <typename T>
class math_VectorBase;
using math_Vector = math_VectorBase<double>;

//! Class performs computing of the global inertia properties
//! of geometric object in 3D space by adaptive and non-adaptive
//! 2D Gauss integration algorithms.
class BRepGProp_Gauss
{
  //! Auxiliary structure for storing of inertial moments.
  struct Inertia
  {
    //! Mass of the current system (without density).
    //! May correspond to: length, area, volume.
    Standard_Real Mass;

    //! Static moments of inertia.
    Standard_Real Ix;
    Standard_Real Iy;
    Standard_Real Iz;

    //! Quadratic moments of inertia.
    Standard_Real Ixx;
    Standard_Real Iyy;
    Standard_Real Izz;
    Standard_Real Ixy;
    Standard_Real Ixz;
    Standard_Real Iyz;

    //! Default constructor.
    Inertia();

    //! Zeroes all values.
    void Reset();
  };

  typedef NCollection_Handle<NCollection_Array1<Inertia>> InertiaArray;
  typedef Standard_Real (*BRepGProp_GaussFunc)(const Standard_Real, const Standard_Real);

public: //! @name public API
  //! Describes types of geometric objects.
  //! - Vinert is 3D closed region of space delimited with:
  //! -- Surface;
  //! -- Point and Surface;
  //! -- Plane and Surface.
  //! - Sinert is face in 3D space.
  typedef enum
  {
    Vinert = 0,
    Sinert
  } BRepGProp_GaussType;

  //! Constructor
  Standard_EXPORT explicit BRepGProp_Gauss(const BRepGProp_GaussType theType);

  //! Computes the global properties of a solid region of 3D space which can be
  //! delimited by the surface and point or surface and plane. Surface can be closed.
  //! The method is quick and its precision is enough for many cases of analytical surfaces.
  //! Non-adaptive 2D Gauss integration with predefined numbers of Gauss points
  //! is used. Numbers of points depend on types of surfaces and curves.
  //! Error of the computation is not calculated.
  //! @param theSurface - bounding surface of the region;
  //! @param theLocation - location of the point or the plane;
  //! @param theCoeff - plane coefficients;
  //! @param theIsByPoint - flag of restricition (point/plane);
  //! @param[out] theOutMass - mass (volume) of region;
  //! @param[out] theOutGravityCenter - garvity center of region;
  //! @param[out] theOutInertia - matrix of inertia;
  Standard_EXPORT void Compute(const BRepGProp_Face&  theSurface,
                               const gp_Pnt&          theLocation,
                               const Standard_Real    theCoeff[],
                               const Standard_Boolean theIsByPoint,
                               Standard_Real&         theOutMass,
                               gp_Pnt&                theOutGravityCenter,
                               gp_Mat&                theOutInertia);

  //! Computes the global properties of a surface. Surface can be closed.
  //! The method is quick and its precision is enough for many cases of analytical surfaces.
  //! Non-adaptive 2D Gauss integration with predefined numbers of Gauss points
  //! is used. Numbers of points depend on types of surfaces and curves.
  //! Error of the computation is not calculated.
  //! @param theSurface - bounding surface of the region;
  //! @param theLocation - surface location;
  //! @param[out] theOutMass - mass (volume) of region;
  //! @param[out] theOutGravityCenter - garvity center of region;
  //! @param[out] theOutInertia - matrix of inertia;
  Standard_EXPORT void Compute(const BRepGProp_Face& theSurface,
                               const gp_Pnt&         theLocation,
                               Standard_Real&        theOutMass,
                               gp_Pnt&               theOutGravityCenter,
                               gp_Mat&               theOutInertia);

  //! Computes the global properties of a region of 3D space which can be
  //! delimited by the surface and point or surface and plane. Surface can be closed.
  //! The method is quick and its precision is enough for many cases of analytical surfaces.
  //! Non-adaptive 2D Gauss integration with predefined numbers of Gauss points is used.
  //! Numbers of points depend on types of surfaces and curves.
  //! Error of the computation is not calculated.
  //! @param theSurface - bounding surface of the region;
  //! @param theDomain - surface boundings;
  //! @param theLocation - location of the point or the plane;
  //! @param theCoeff - plane coefficients;
  //! @param theIsByPoint - flag of restricition (point/plane);
  //! @param[out] theOutMass - mass (volume) of region;
  //! @param[out] theOutGravityCenter - garvity center of region;
  //! @param[out] theOutInertia - matrix of inertia;
  Standard_EXPORT void Compute(BRepGProp_Face&        theSurface,
                               BRepGProp_Domain&      theDomain,
                               const gp_Pnt&          theLocation,
                               const Standard_Real    theCoeff[],
                               const Standard_Boolean theIsByPoint,
                               Standard_Real&         theOutMass,
                               gp_Pnt&                theOutGravityCenter,
                               gp_Mat&                theOutInertia);

  //! Computes the global properties of a surface. Surface can be closed.
  //! The method is quick and its precision is enough for many cases of analytical surfaces.
  //! Non-adaptive 2D Gauss integration with predefined numbers of Gauss points
  //! is used. Numbers of points depend on types of surfaces and curves.
  //! Error of the computation is not calculated.
  //! @param theSurface - bounding surface of the region;
  //! @param theDomain - surface boundings;
  //! @param theLocation - surface location;
  //! @param[out] theOutMass - mass (volume) of region;
  //! @param[out] theOutGravityCenter - garvity center of region;
  //! @param[out] theOutInertia - matrix of inertia;
  Standard_EXPORT void Compute(BRepGProp_Face&   theSurface,
                               BRepGProp_Domain& theDomain,
                               const gp_Pnt&     theLocation,
                               Standard_Real&    theOutMass,
                               gp_Pnt&           theOutGravityCenter,
                               gp_Mat&           theOutInertia);

  //! Computes the global properties of the region of 3D space which can be
  //! delimited by the surface and point or surface and plane.
  //! Adaptive 2D Gauss integration is used.
  //! If Epsilon more than 0.001 then algorithm performs non-adaptive integration.
  //! @param theSurface - bounding surface of the region;
  //! @param theDomain - surface boundings;
  //! @param theLocation - location of the point or the plane;
  //! @param theEps - maximal relative error of computed mass (volume) for face;
  //! @param theCoeff - plane coefficients;
  //! @param theIsByPoint - flag of restricition (point/plane);
  //! @param[out] theOutMass - mass (volume) of region;
  //! @param[out] theOutGravityCenter - garvity center of region;
  //! @param[out] theOutInertia - matrix of inertia;
  //! @return value of error which is calculated as
  //! Abs((M(i+1)-M(i))/M(i+1)), M(i+1) and M(i) are values
  //! for two successive steps of adaptive integration.
  Standard_EXPORT Standard_Real Compute(BRepGProp_Face&        theSurface,
                                        BRepGProp_Domain&      theDomain,
                                        const gp_Pnt&          theLocation,
                                        const Standard_Real    theEps,
                                        const Standard_Real    theCoeff[],
                                        const Standard_Boolean theByPoint,
                                        Standard_Real&         theOutMass,
                                        gp_Pnt&                theOutGravityCenter,
                                        gp_Mat&                theOutInertia);

  //! Computes the global properties of the face. Adaptive 2D Gauss integration is used.
  //! If Epsilon more than 0.001 then algorithm performs non-adaptive integration.
  //! @param theSurface - bounding surface of the region;
  //! @param theDomain - surface boundings;
  //! @param theLocation - surface location;
  //! @param theEps - maximal relative error of computed mass (square) for face;
  //! @param[out] theOutMass - mass (volume) of region;
  //! @param[out] theOutGravityCenter - garvity center of region;
  //! @param[out] theOutInertia - matrix of inertia;
  //! @return value of error which is calculated as
  //! Abs((M(i+1)-M(i))/M(i+1)), M(i+1) and M(i) are values
  //! for two successive steps of adaptive integration.
  Standard_EXPORT Standard_Real Compute(BRepGProp_Face&     theSurface,
                                        BRepGProp_Domain&   theDomain,
                                        const gp_Pnt&       theLocation,
                                        const Standard_Real theEps,
                                        Standard_Real&      theOutMass,
                                        gp_Pnt&             theOutGravityCenter,
                                        gp_Mat&             theOutInertia);

private: //! @name private methods
  BRepGProp_Gauss(BRepGProp_Gauss const&);
  BRepGProp_Gauss& operator=(BRepGProp_Gauss const&);

  void computeVInertiaOfElementaryPart(const gp_Pnt&             thePoint,
                                       const gp_Vec&             theNormal,
                                       const gp_Pnt&             theLocation,
                                       const Standard_Real       theWeight,
                                       const Standard_Real       theCoeff[],
                                       const Standard_Boolean    theIsByPoint,
                                       BRepGProp_Gauss::Inertia& theOutInertia);

  void computeSInertiaOfElementaryPart(const gp_Pnt&             thePoint,
                                       const gp_Vec&             theNormal,
                                       const gp_Pnt&             theLocation,
                                       const Standard_Real       theWeight,
                                       BRepGProp_Gauss::Inertia& theOutInertia);

  void checkBounds(const Standard_Real theU1,
                   const Standard_Real theU2,
                   const Standard_Real theV1,
                   const Standard_Real theV2);

  void addAndRestoreInertia(const BRepGProp_Gauss::Inertia& theInInertia,
                            BRepGProp_Gauss::Inertia&       theOutInertia);

  void multAndRestoreInertia(const Standard_Real theValue, BRepGProp_Gauss::Inertia& theInertia);

  void convert(const BRepGProp_Gauss::Inertia& theInertia,
               gp_Pnt&                         theOutGravityCenter,
               gp_Mat&                         theOutMatrixOfInertia,
               Standard_Real&                  theOutMass);

  void convert(const BRepGProp_Gauss::Inertia& theInertia,
               const Standard_Real             theCoeff[],
               const Standard_Boolean          theIsByPoint,
               gp_Pnt&                         theOutGravityCenter,
               gp_Mat&                         theOutMatrixOfInertia,
               Standard_Real&                  theOutMass);

  static Standard_Integer MaxSubs(const Standard_Integer theN,
                                  const Standard_Integer theCoeff = 32);

  static void Init(NCollection_Handle<math_Vector>& theOutVec,
                   const Standard_Real              theValue,
                   const Standard_Integer           theFirst = 0,
                   const Standard_Integer           theLast  = 0);

  static void InitMass(const Standard_Real    theValue,
                       const Standard_Integer theFirst,
                       const Standard_Integer theLast,
                       InertiaArray&          theArray);

  static Standard_Integer FillIntervalBounds(const Standard_Real              theA,
                                             const Standard_Real              theB,
                                             const TColStd_Array1OfReal&      theKnots,
                                             const Standard_Integer           theNumSubs,
                                             InertiaArray&                    theInerts,
                                             NCollection_Handle<math_Vector>& theParam1,
                                             NCollection_Handle<math_Vector>& theParam2,
                                             NCollection_Handle<math_Vector>& theError,
                                             NCollection_Handle<math_Vector>& theCommonError);

private:                      //! @name private fields
  BRepGProp_GaussType myType; //!< Type of geometric object
  BRepGProp_GaussFunc add;    //!< Pointer on the add function
  BRepGProp_GaussFunc mult;   //!< Pointer on the mult function
};

#endif