/***************************************************************************
                        qgssfcgalengine.h
  -------------------------------------------------------------------
    begin                : May 2025
    copyright            : (C) 2025 by Oslandia
    email                : benoit dot de dot mezzo at oslandia dot com
    email                : jean dot felder at oslandia dot com
    email                : loic dot bartoletti at oslandia dot com
 ***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef WITH_SFCGAL
#ifndef QGSSFCGALENGINE_H
#define QGSSFCGALENGINE_H


#include <SFCGAL/capi/sfcgal_c.h>
#include <source_location>

#include "qgis_core.h"
#include "qgsexception.h"
#include "qgsgeometry.h"
#include "qgslogger.h"
#include "qgspoint.h"
#include "qgsvector3d.h"

#include <QMatrix4x4>

#define SIP_NO_FILE

class QgsGeometry;
class QgsSfcgalGeometry;

/// compute SFCGAL integer version from major, minor and patch number
/// This is not available before SFCGAL version 2.3.
#ifndef SFCGAL_MAKE_VERSION
#define SFCGAL_MAKE_VERSION( major, minor, patch ) ( ( major ) * 10000 + ( minor ) * 100 + ( patch ) )
#endif
/// compute current SFCGAL integer version
/// This is not available before SFCGAL version 2.3.
#ifndef SFCGAL_VERSION_NUM
#define SFCGAL_VERSION_NUM SFCGAL_MAKE_VERSION( SFCGAL_VERSION_MAJOR_INT, SFCGAL_VERSION_MINOR_INT, SFCGAL_VERSION_PATCH_INT )
#endif

/// check if \a ptr is not null else add stacktrace entry and return the \a defaultObj
#define CHECK_NOT_NULL( ptr, defaultObj )                                              \
  if ( !( ptr ) )                                                                      \
  {                                                                                    \
    sfcgal::errorHandler()->addText( QString( "Null pointer for '%1'" ).arg( #ptr ) ); \
    return ( defaultObj );                                                             \
  }

/// check if no error has been caught else add stacktrace entry and return the \a defaultObj
#define CHECK_SUCCESS( errorMsg, defaultObj )                       \
  if ( !sfcgal::errorHandler()->hasSucceedOrStack( ( errorMsg ) ) ) \
  {                                                                 \
    return ( defaultObj );                                          \
  }

/// check if no error has been caught else add stacktrace entry, log the stacktrace and return the \a defaultObj
#define CHECK_SUCCESS_LOG( errorMsg, defaultObj )                   \
  if ( !sfcgal::errorHandler()->hasSucceedOrStack( ( errorMsg ) ) ) \
  {                                                                 \
    QgsDebugError( sfcgal::errorHandler()->getFullText() );         \
    return ( defaultObj );                                          \
  }

/// check if no error has been caught else add stacktrace entry, log the stacktrace and throw an exception
#define THROW_ON_ERROR( errorMsg )                                     \
  if ( !sfcgal::errorHandler()->hasSucceedOrStack( ( errorMsg ) ) )    \
  {                                                                    \
    QgsDebugError( sfcgal::errorHandler()->getFullText() );            \
    throw QgsSfcgalException( sfcgal::errorHandler()->getFullText() ); \
  }


/**
 * Contains SFCGAL related utilities and functions.
 * \note not available in Python bindings.
 * \since QGIS 4.0
 */
namespace sfcgal
{
  // ==== SFCGAL geometry
  //! Shortcut to SFCGAL geometry
  using geometry = sfcgal_geometry_t;

  //! Class used as SFCGAL geometry deleter.
  struct GeometryDeleter
  {
      //! Destroys the SFCGAL geometry \a geom, using the static QGIS SFCGAL context.
      void CORE_EXPORT operator()( geometry *geom ) const;
  };

  //! Unique SFCGAL geometry pointer.
  using unique_geom = std::unique_ptr< geometry, GeometryDeleter >;
  //! Shared SFCGAL geometry pointer.
  using shared_geom = std::shared_ptr< geometry >; // NO DELETER ==> added with function make_shared_geom!!!!!

  //! Creates a shared geometry pointer with sfcgal::GeometryDeleter
  shared_geom make_shared_geom( geometry *geom );
} // namespace sfcgal

namespace sfcgal
{
  // ==== SFCGAL primitive
#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
  //! Shortcut to SFCGAL primitive
  using primitive = sfcgal_primitive_t;
  //! Shortcut to SFCGAL primitive type
  using primitiveType = sfcgal_primitive_type_t;
#else
  //! Shortcut to SFCGAL primitive
  using primitive = int;
  //! Shortcut to SFCGAL primitive type
  using primitiveType = int;
#endif

  //! Class used as SFCGAL primitive deleter.
  struct PrimitiveDeleter
  {
      //! Destroys the SFCGAL primitive \a prim, using the static QGIS SFCGAL context.
      void CORE_EXPORT operator()( primitive *prim ) const;
  };

  //! Unique SFCGAL primitive pointer.
  using unique_prim = std::unique_ptr< primitive, PrimitiveDeleter >;
  //! Shared SFCGAL primitive pointer.
  using shared_prim = std::shared_ptr< primitive >; // NO DELETER ==> added with function make_shared_prim!!!!!

  //! Creates a shared primitive pointer with sfcgal::PrimitiveDeleter
  shared_prim make_shared_prim( primitive *prim );

  //! Hold primitive parameter description
  struct PrimitiveParameterDesc
  {
      std::string name;
      std::string type;
      std::variant<int, double, QgsPoint, QgsVector3D> value;
  };

  //! Used by json lib to convert to json
  void to_json( json &j, const PrimitiveParameterDesc &p );

  //! Used by json lib to convert from json
  void from_json( const json &j, PrimitiveParameterDesc &p );
} // namespace sfcgal

namespace sfcgal
{
  // ==== SFCGAL errors
  //! Callback uses by SFCGAL lib to push error.
  int errorCallback( const char *, ... );

  //! Callback uses by SFCGAL lib to push warning.
  int warningCallback( const char *, ... );

  /**
   * Helper class to handle SFCGAL engine errors.
   *
   * Messages are held in a stacktrace in order to improve context understanding.
   * \ingroup core
   * \since QGIS 4.0
   */
  class CORE_EXPORT ErrorHandler
  {
    public:
      //! Default constructor.
      ErrorHandler();

      /**
      * Returns true if no failure has been caught or returns false and adds a new stacktrace entry.
      *
      * If a failure has already been caught and \a errorMsg is not null then:
      *
      * - a stacktrace entry is added with caller location
      * - \a errorMsg will be updated with failure messages
      */
      bool hasSucceedOrStack( QString *errorMsg = nullptr, const std::source_location &location = std::source_location::current() );

      /**
       * Clears failure messages and also clear \a errorMsg content if not null.
       */
      void clearText( QString *errorMsg = nullptr );

      //! Returns true if no failure has been caught.
      bool isTextEmpty() const;

      //! Returns the first caught failure message.
      QString getMainText() const;

      //! Returns all failure messages as a stack trace.
      QString getFullText() const;

      //! Adds \a msg to the failure message list.
      void addText( const QString &msg, const std::source_location &location = std::source_location::current() );

    private:
      QStringList errorMessages;
  };

  //! Returns current error handler.
  CORE_EXPORT ErrorHandler *errorHandler();

  //! Shortcut for SFCGAL function definition.
  using func_geomgeom_to_geom = sfcgal_geometry_t *( * ) ( const sfcgal_geometry_t *, const sfcgal_geometry_t * );
  //! Shortcut for SFCGAL function definition.
  using func_geom_to_geom = sfcgal_geometry_t *( * ) ( const sfcgal_geometry_t * );
} // namespace sfcgal

/**
 * \ingroup core
 * \brief Does vector analysis using the SFCGAL library and handles import, export, exception handling
 * \note not available in Python bindings
 * \since QGIS 4.0
 */
class CORE_EXPORT QgsSfcgalEngine
{
  public:
    /**
     * Creates a QgsAbstractGeometry from an internal SFCGAL geometry (from SFCGAL library).
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static std::unique_ptr< QgsAbstractGeometry > toAbstractGeometry( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Creates a SFGAL geometry from a shared SFCGAL geometry (from SFCGAL library).
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static std::unique_ptr< QgsSfcgalGeometry > toSfcgalGeometry( sfcgal::shared_geom &geom, QString *errorMsg = nullptr );

    /**
     * Creates internal SFCGAL geometry (from SFCGAL library) from a QGIS QgsAbstractGeometry.
     *
     * \param geom geometry to convert to SFCGAL representation
     * \param errorMsg pointer to QString to receive the error message if any
     */
    static sfcgal::shared_geom fromAbstractGeometry( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr );

    /**
     * Clones \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom cloneGeometry( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Returns the type of a given geometry as a OGC string in CamelCase
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static QString geometryType( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Creates geometry from WKB data.
     *
     * \param wkbPtr reference onto WKB data.
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom fromWkb( const QgsConstWkbPtr &wkbPtr, QString *errorMsg = nullptr );

    /**
     * Creates geometry from WKT string.
     *
     * \param wkt reference onto WKT string.
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom fromWkt( const QString &wkt, QString *errorMsg = nullptr );

    /**
     * Computes WKB data from \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static QByteArray toWkb( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Computes WKT string from \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     * \param numDecimals Floating point precision for WKT coordinates. Setting to -1 yields rational number WKT (not decimal) f.e. "Point(1/3, 1/6, 1/4)". Note that this will produce WKT which is not compatible with other QGIS methods or external libraries.
     */
    static QString toWkt( const sfcgal::geometry *geom, int numDecimals = -1, QString *errorMsg = nullptr );

    /**
     * Returns the QGIS WKB type from \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg pointer to QString to receive the error message if any
     */
    static Qgis::WkbType wkbType( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Returns the \a geom dimension.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg pointer to QString to receive the error message if any
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static int dimension( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Returns the \a geom part count.
     *
     * - POINT, TRIANGLE, LINESTRING: vertex number
     * - POLYGON, SOLID, POLYHEDRALSURFACE, TRIANGULATEDSURFACE: ring or patch or shell number
     * - MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, MULTISOLID, GEOMETRYCOLLECTION: number of geom in collection
     *
     * \param geom geometry to perform the operation
     * \param errorMsg pointer to QString to receive the error message if any
     */
    static int partCount( const sfcgal::geometry *geom, QString *errorMsg );

    /**
     * Adds a z-dimension to the geometry, initialized to a preset value (existing Z values remains unchanged).
     *
     * \return true if success
     * \param geom geometry to perform the operation
     * \param zValue z value to use
     * \param errorMsg pointer to QString to receive the error message if any
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static bool addZValue( sfcgal::geometry *geom, double zValue = 0, QString *errorMsg = nullptr );

    /**
     * Adds a m-dimension to the geometry, initialized to a preset value (existing M values remains unchanged).
     *
     * \return true if success
     * \param geom geometry to perform the operation
     * \param mValue m value to use
     * \param errorMsg pointer to QString to receive the error message if any
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static bool addMValue( sfcgal::geometry *geom, double mValue = 0, QString *errorMsg = nullptr );

    /**
     * Drops the z coordinate of the geometry
     *
     * \return true if success
     * \param geom geometry to perform the operation
     * \param errorMsg pointer to QString to receive the error message if any
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static bool dropZValue( sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Drops the m coordinate of the geometry
     *
     * \return true if success
     * \param geom geometry to perform the operation
     * \param errorMsg pointer to QString to receive the error message if any
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static bool dropMValue( sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Swaps the x and y coordinates of the geometry
     *
     * \param geom geometry to perform the operation
     * \param errorMsg pointer to QString to receive the error message if any
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static void swapXy( sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Checks if \a geomA and \a geomB are equal.
     *
     * \param geomA first geometry to perform the operation
     * \param geomB second geometry to perform the operation
     * \param tolerance max distance allowed between each point
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static bool isEqual( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double tolerance = -1.0, QString *errorMsg = nullptr );

    /**
     * Checks if \a geom is empty.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static bool isEmpty( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Checks if \a geom is valid.
     *
     * If the geometry is invalid, \a errorMsg will be filled with the reported geometry error.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     * \param errorLoc if specified, it will be set to the geometry of the error location.
     */
    static bool isValid( const sfcgal::geometry *geom, QString *errorMsg = nullptr, QgsGeometry *errorLoc = nullptr );

    /**
     * Checks if \a geom is simple.
     *
     * As OGC specifications. Here are PostGIS (https://postgis.net/docs/using_postgis_dbmanagement.html#OGC_Validity) extract:
     * A POINT is inherently simple as a 0-dimensional geometry object.
     * MULTIPOINTs are simple if no two coordinates (POINTs) are equal (have identical coordinate values).
     * A LINESTRING is simple if it does not pass through the same point twice, except for the endpoints. If the endpoints of a simple LineString are identical it is called closed and referred to as a Linear Ring.
     * A MULTILINESTRING is simple only if all of its elements are simple and the only intersection between any two elements occurs at points that are on the boundaries of both elements.
     * POLYGONs are formed from linear rings, so valid polygonal geometry is always simple.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static bool isSimple( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Calculate the boundary of \a geom
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static sfcgal::shared_geom boundary( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Calculate the centroid of \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static QgsPoint centroid( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Translate the \a geom by vector \a translation.
     *
     * \param geom geometry to perform the operation
     * \param translation translation vector (2D or 3D)
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static sfcgal::shared_geom translate( const sfcgal::geometry *geom, const QgsVector3D &translation, QString *errorMsg = nullptr );

    /**
     * Scale the \a geom by vector \a scaleFactor.
     *
     * \param geom geometry to perform the operation
     * \param scaleFactor scale factor vector (2D or 3D)
     * \param center optional parameter. If specified, scaling will be performed relative to this center
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom scale( const sfcgal::geometry *geom, const QgsVector3D &scaleFactor, const QgsPoint &center = QgsPoint(), QString *errorMsg = nullptr );

    /**
     * 2D Rotation of geometry \a geom around point \a center by angle \a angle
     *
     * \param geom geometry to perform the operation
     * \param angle rotation angle in radians
     * \param center rotation center
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom rotate2D( const sfcgal::geometry *geom, double angle, const QgsPoint &center, QString *errorMsg = nullptr );

    /**
     * 3D Rotation of geometry \a geom around axis \a axisVector by angle \a angle
     *
     * \param geom geometry to perform the operation
     * \param angle rotation angle in radians
     * \param axisVector rotation axis
     * \param center optional parameter. If specified, rotation will be applied around axis and center point
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom rotate3D( const sfcgal::geometry *geom, double angle, const QgsVector3D &axisVector, const QgsPoint &center = QgsPoint(), QString *errorMsg = nullptr );

    /**
     * Computes shortest distance between \a geomA and \a geomB.
     *
     * \param geomA first geometry to perform the operation
     * \param geomB second geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static double distance( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg = nullptr );

    /**
     * Checks if \a geomA is within \a maxdistance distance from \a geomB
     *
     * \param geomA first geometry to perform the operation
     * \param geomB second geometry to perform the operation
     * \param maxdistance
     * \param errorMsg Error message returned by SFGCAL
     */
    static bool distanceWithin( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double maxdistance, QString *errorMsg );

    /**
     * Computes the area of \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static double area( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Computes the max length of \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static double length( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Checks if \a geomA and \a geomB intersect each other
     * A 3D intersection test is performed if at least one geometry is 3D; otherwise, a 2D intersection test is performed.
     *
     * \param geomA first geometry to perform the operation
     * \param geomB second geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static bool intersects( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg = nullptr );

    /**
     * Calculates the intersection of \a geomA and \a geomB
     *
     * \param geomA first geometry to perform the operation
     * \param geomB second geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom intersection( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg = nullptr );

    /**
     * Calculates the difference of \a geomA minus \a geomB
     *
     * \param geomA first geometry to perform the operation
     * \param geomB second geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom difference( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg = nullptr );

    /**
     * Calculate the combination of all geometry in \a geomList.
     *
     * \param geomList list of geometries to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom combine( const QVector< sfcgal::shared_geom > &geomList, QString *errorMsg = nullptr );

    /**
     * Calculate a triangulation of \a geom using constraint 2D Delaunay Triangulation (keep Z if defined).
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom triangulate( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Cover test on 2D or 3D geometries
     * Checks if \a geomA covers \a geomB.
     * A 3D covers test is conducted when at least one geometry is 3D; otherwise, a 2D covers test is carried out.
     *
     * \param geomA first geometry to perform the operation
     * \param geomB second geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static bool covers( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg = nullptr );

    /**
     * Calculate the convex hull of \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom convexHull( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Calculate the envelope (bounding box) of \a geom.
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static sfcgal::shared_geom envelope( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

    /**
     * Calculate a buffer for the \a geom where all points are at \a distance from the original geometry.
     * A negative distance shrinks the geometry rather than expanding it.
     *
     * \param geom geometry to perform the operation
     * \param distance distance to move each point of the geometry
     * \param segments the number of segments to use for approximating curved
     * \param joinStyle the type of buffer to compute. Only round is supported.
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom offsetCurve( const sfcgal::geometry *geom, double distance, int segments, Qgis::JoinStyle joinStyle, QString *errorMsg = nullptr );

    /**
     * Calculate a 3D buffer for the \a geom where all points are at \a distance from the original geometry.
     * A negative distance shrinks the geometry rather than expanding it.
     * It is limited to Point and LineString.
     *
     * \param geom geometry to perform the operation
     * \param radius the buffer radius
     * \param segments the number of segments to use for approximating curved
     * \param joinStyle3D the type of buffer to compute
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom buffer3D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle3D joinStyle3D, QString *errorMsg = nullptr );

    /**
     * Calculate a 2D buffer for the \a geom where all points are at \a distance from the original geometry.
     * A negative distance shrinks the geometry rather than expanding it.
     *
     * \param geom geometry to perform the operation
     * \param radius the buffer radius
     * \param segments the number of segments to use for approximating curved
     * \param joinStyle the type of buffer to compute. Only round is supported.
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom buffer2D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle joinStyle, QString *errorMsg = nullptr );

    /**
     * Extrude the \a geom by vector \a extrusion.
     *
     * \param geom geometry to perform the operation
     * \param extrusion translation vector (2D or 3D)
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom extrude( const sfcgal::geometry *geom, const QgsVector3D &extrusion, QString *errorMsg = nullptr );

    /**
     * Calculate the simplified version of \a geom.
     *
     * \param geom geometry to perform the operation
     * \param tolerance The distance (in geometry unit) threshold
     * \param preserveTopology Whether to preserve topology during simplification
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static sfcgal::shared_geom simplify( const sfcgal::geometry *geom, double tolerance, bool preserveTopology, QString *errorMsg = nullptr );

    /**
     * Calculate a 2D approximate medial axis of \a geom based on its straight skeleton.
     * The approximate medial axis is a simplified representation of a shape’s central skeleton
     * It \a geom is 3D, the approximate medial axis will be calculated from its 2D projection
     * The output is a 2D multilinestring
     *
     * \param geom geometry to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom approximateMedialAxis( const sfcgal::geometry *geom, QString *errorMsg = nullptr );

#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )

    /**
     * Apply 3D matrix transform \a mat to geometry \a geom
     *
     * \param geom geometry to perform the operation
     * \param mat 4x4 transformation matrix
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom transform( const sfcgal::geometry *geom, const QMatrix4x4 &mat, QString *errorMsg = nullptr );

    /**
     * Creates a SFGAL geometry from a shared SFCGAL primitive (from SFCGAL library).
     *
     * \param prim primitive to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static std::unique_ptr< QgsSfcgalGeometry > toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg = nullptr );

    /**
     * Create a cube primitive
     * \param size the cube size
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_prim createCube( double size, QString *errorMsg = nullptr );

    /**
     * Clones \a prim.
     *
     * \param prim primitive to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_prim primitiveClone( const sfcgal::primitive *prim, QString *errorMsg = nullptr );

    /**
     * Convert \a prim to Polyhedral geometry
     *
     * \param prim primitive to perform the operation
     * \param mat a transformation matrix
     * \param errorMsg Error message returned by SFGCAL
     */
    static sfcgal::shared_geom primitiveAsPolyhedral( const sfcgal::primitive *prim, const QMatrix4x4 &mat = QMatrix4x4(), QString *errorMsg = nullptr );

    /**
     * Checks if \a primA and \a primB are equal.
     *
     * \param primA first primitive to perform the operation
     * \param primB second primitive to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     *
     * \throws QgsNotSupportedException on QGIS builds based on SFCGAL 2.0 or earlier.
     */
    static bool primitiveIsEqual( const sfcgal::primitive *primA, const sfcgal::primitive *primB, double tolerance = -1.0, QString *errorMsg = nullptr );

    /**
     * Computes the area of \a prim.
     *
     * \param prim primitive to perform the operation
     * \param withDiscretization If true, the area is computed
     * using the real discretization with radial segments. If false, the area is
     * computed for a perfect primitive. Defaults to false.
     * \param errorMsg Error message returned by SFGCAL
     */
    static double primitiveArea( const sfcgal::primitive *prim, bool withDiscretization = false, QString *errorMsg = nullptr );

    /**
     * Computes the volume of \a prim.
     *
     * \param prim primitive to perform the operation
     * \param withDiscretization If true, the volume is computed
     * using the real discretization with radial segments. If false, the volume is
     * computed for a perfect primitive. Defaults to false.
     * \param errorMsg Error message returned by SFGCAL
     */
    static double primitiveVolume( const sfcgal::primitive *prim, bool withDiscretization = false, QString *errorMsg = nullptr );

    /**
     * Returns the list of available parameter description for this primitive.
     *
     * Only the name and type fields will be filled.
     *
     * \param prim primitive to perform the operation
     * \param errorMsg Error message returned by SFGCAL
     */
    static QVector<sfcgal::PrimitiveParameterDesc> primitiveParameters( const sfcgal::primitive *prim, QString *errorMsg = nullptr );

    /**
     * Returns the parameter value according to its \a name
     *
     * \param prim primitive to perform the operation
     * \param name parameter name
     * \param errorMsg Error message returned by SFGCAL
     */
    static QVariant primitiveParameter( const sfcgal::primitive *prim, const QString &name, QString *errorMsg = nullptr );

    /**
     * Updates parameter value
     *
     * \param prim primitive to perform the operation
     * \param name parameter name
     * \param value new parameter value
     * \param errorMsg Error message returned by SFGCAL
     */
    static void primitiveSetParameter( sfcgal::primitive *prim, const QString &name, const QVariant &value, QString *errorMsg = nullptr );

#endif
};

/// @cond PRIVATE


class SFCGALException : public std::runtime_error
{
  public:
    explicit SFCGALException( const QString &message )
      : std::runtime_error( message.toUtf8().constData() )
    {}
};

/// @endcond

#endif // QGSSFCGALENGINE_H
#endif
