// Copyright (c) 2025-2026, SFCGAL team.
// SPDX-License-Identifier: LGPL-2.0-or-later

#ifndef SFCGAL_PRIMITIVE_H_
#define SFCGAL_PRIMITIVE_H_

#include <string>
#include <unordered_map>
#include <variant>

#include "SFCGAL/Kernel.h"
#include "SFCGAL/PolyhedralSurface.h"
#include "SFCGAL/export.h"

namespace SFCGAL {

/**
 * @brief Enumeration of available primitive types.
 */
enum class PrimitiveType : std::int8_t {
  TYPE_BOX = 0,
  TYPE_CONE,
  TYPE_CUBE,
  TYPE_CYLINDER,
  TYPE_SPHERE,
  TYPE_TORUS,
};

/**
 * @brief Variant type representing the possible values of a primitive
 * parameter.
 *
 * A primitive parameter can be one of the following types:
 * - SFCGAL::Kernel::FT : a scalar floating-point value
 * - unsigned int : an integer value
 */
using PrimitiveParameter = std::variant<Kernel::FT, unsigned int>;

/**
 * @brief Abstract base class for primitive object (Cube, Cylinder, Cone, etc.)
 *
 * This class provides generic parameter functions to set, get, list member
 * fields. All parameters needed by inherited classes must be declared in the
 * constructor:
 *
 * \code{.cpp}
 * SubClass::SubClass(const Kernel::FT &radius, const Kernel::FT &height,
 * unsigned int num)
 * {
 * m_parameters["radius"] = radius;
 * m_parameters["height"] = height;
 * m_parameters["num"] = num;
 *
 * SubClass::validateParameters(m_parameters);
 * }
 * \endcode
 */
class SFCGAL_API Primitive {

public:
  /**
   * @brief Default constructor.
   */
  Primitive() = default;

  /**
   * @brief Destructor.
   */
  virtual ~Primitive() = default;

  /**
   * @brief Get a deep copy of the primitive
   *
   * @return a deep copy of the primitive as a unique_ptr
   */
  [[nodiscard]] auto
  clone() const -> std::unique_ptr<Primitive>
  {
    return std::unique_ptr<Primitive>(this->cloneImpl());
  }

  /**
   * @brief returns the primitive type
   * @warning use CamelCase (Cylinder, not CYLINDER)
   * @return the primitive type as string
   */
  [[nodiscard]] virtual auto
  primitiveType() const -> std::string = 0;

  /**
   * @return a code corresponding to the type
   */
  [[nodiscard]] virtual auto
  primitiveTypeId() const -> PrimitiveType = 0;

  /**
   * @brief Generates a surface mesh representation of the primitive
   * @return A PolyhedralSurface object representing the torus
   */
  virtual auto
  generatePolyhedralSurface() const -> PolyhedralSurface = 0;

  /**
   * @brief Sets the value of a primitive parameter.
   * Assigns a new value to the parameter identified by @p name.
   * @param name The name of the parameter to set.
   * @param parameter The new parameter value as a ::PrimitiveParameter.
   * @pre The parameter identified by @p name must exist and accept the provided
   * variant type.
   * @throws SFCGAL::Exception if the parameter does not exist or if the
   * provided variant type is not compatible with the parameter.
   */
  void
  setParameter(const std::string &name, const PrimitiveParameter &parameter);

  /**
   * @brief Retrieves the value of a primitive parameter based ont its @p name.
   * @param name The name of the parameter to retrieve.
   * @return The parameter value as a ::PrimitiveParameter variant.
   * @pre The parameter identified by @p name must exist.
   * @throws SFCGAL::Exception if the parameter does not exist or if the
   * provided variant type is not compatible with the parameter.
   */
  [[nodiscard]] auto
  parameter(const std::string &name) const -> PrimitiveParameter;

  /**
   * @brief Retrieves the list of parameters
   * @return the key/value parameter map
   */
  [[nodiscard]] auto
  parameters() const -> std::unordered_map<std::string, PrimitiveParameter>;

  /**
   * @brief Sets the primitive affine transformation
   * @param transform The new affine transformation
   */
  void
  setTransformation(const Kernel::Aff_transformation_3 &transform);

  /**
   * @brief Retrieves the primitive affine transformation
   * @return the primitive affine transformation
   */
  [[nodiscard]] auto
  transformation() const -> Kernel::Aff_transformation_3;

  /**
   * Translates the primitive by the given vector.
   *
   * @param vector The translation vector.
   */
  void
  translate(const Kernel::Vector_3 &vector);

  /**
   * Rotates the primitive around a specified axis and center point
   * by a given angle
   *
   * @param angle Rotation angle in radians
   * @param axis The rotation axis.
   * @param center The rotation center
   */
  void
  rotate(const Kernel::FT &angle, const Kernel::Vector_3 &axis,
         const Kernel::Point_3 &center = Kernel::Point_3(0, 0, 0));

  /**
   * Scales the primitive by the given factor.
   *
   * @param factors The scale factors.
   * @param center The scale center.
   */
  void
  scale(const Kernel::Vector_3 &factors,
        const Kernel::Point_3  &center = Kernel::Point_3(0, 0, 0));

  /**
   * @brief Returns the primitive volume
   * @param withDiscretization Computes area with discretization (true) or as
   * perfect primitive (false). Defaults to false.
   * @return The cone volume
   */
  [[nodiscard]] auto
  volume(bool withDiscretization = false) const -> double;

  /**
   * @brief Returns the sphere volume without scaling
   * @param withDiscretization Computes volume with discretization (true) or as
   * perfect primitive (false). Defaults to false.
   * @return The sphere volume
   */
  auto virtual baseVolume(bool withDiscretization = false) const -> double = 0;

  /**
   * @brief Returns the primitive area
   * @param withDiscretization Computes volume with discretization (true) or as
   * perfect primitive (false). Defaults to false.
   * @return The primitive volume
   */
  [[nodiscard]] virtual auto
  area3D(bool withDiscretization = false) const -> double = 0;

  /**
   * Equality operator
   * @param other other geometry to compare to
   * @param epsilon allowed tolerance
   * @return true when this is almost equal
   */
  [[nodiscard]] auto
  almostEqual(const Primitive &other, double epsilon) const -> bool;

  /**
   * Returns string representation of this object. Can be overriden by
   * sub-classes.
   *
   * \return string representation of this object
   */
  [[nodiscard]] virtual auto
  toString() const -> std::string;

  /**
   * overloaded operator<<
   * \param out stream to fill
   * \param prim primitive to print via Primitive::toString()
   * \return filled stream
   */
  friend auto
  operator<<(std::ostream &out, const Primitive &prim) -> std::ostream &
  {
    // Call virtual function toString() to get the string to be printed
    out << prim.toString();
    return out;
  }

protected:
  /**
   * @brief Invalidates the cached geometries
   */
  virtual void
  invalidateCache();

  /**
   * @brief Verifies that all parameters are valid. For instance, it raises an
   * error if a radius is negative.
   * @param tempParameters a temp map of parameter with new values
   * @throws SFCGAL::Exception if one of the parameters if not valid
   * provided variant type is not compatible with the parameter.
   */
  virtual void
  validateParameters(std::unordered_map<std::string, PrimitiveParameter> const
                         &tempParameters) const = 0;

  /**
   * @brief Checks that the new value is valid and sets it.
   * This also invalidates the cache.
   * Unlike @ref setParameter, this does not check that name exists and that it
   * accepts the provided variant type. This assumes that name exists and the
   * variant type is compatible with the parameter
   * @param name parameter name to check
   * @param parameter value
   * @throws SFCGAL::Exception if the parameter is not valid
   */
  virtual void
  validateAndSetParameter(const std::string        &name,
                          const PrimitiveParameter &parameter);

  /**
   *@brief Returns the scale factor for a given column of the affine transform
   *matrix.
   *
   * @param columnIdx Column index of the transform matrix (0 = X, 1 = Y, 2 =
   *Z).
   * @return The scale factor along the corresponding axis.
   */
  auto
  scaleFactor(int columnIdx) const -> double;

  /**
   * Holds parameter value according to their name
   */
  std::unordered_map<std::string, PrimitiveParameter> m_parameters;

  /**
   * Cached PolyhedralSurface to avoid regeneration each time we call
   * generatePolyhedralSurface()
   */
  mutable std::optional<PolyhedralSurface> m_polyhedral_surface;

  /**
   * Holds primitive affine transformation
   */
  Kernel::Aff_transformation_3 m_transform = CGAL::IDENTITY;

private:
  [[nodiscard]] virtual auto
  cloneImpl() const -> Primitive * = 0;
};

/**
 * Equality operator
 * @todo only compare coordinate points
 * @pre the two geometries must be valid
 * @param prim1 first primitive to compare
 * @param prim2 second primitive to compare
 * @return true when the primitives are the same
 */
SFCGAL_API auto
operator==(const Primitive &prim1, const Primitive &prim2) -> bool;

/// @{
/// @privatesection
#ifndef DOXYGEN_SHOULD_SKIP_THIS

/**
 * @brief Base class that implements covariant cloning with CRTP pattern
 */
template <typename Derived, typename Base>
class SFCGAL_API PrimitiveImpl : public Base {
  PrimitiveImpl() = default;

public:
  [[nodiscard]] auto
  clone() const -> std::unique_ptr<Derived>
  {
    return std::unique_ptr<Derived>(static_cast<Derived *>(this->cloneImpl()));
  }

private:
  [[nodiscard]] auto
  cloneImpl() const -> PrimitiveImpl * override
  {
    return new Derived(*static_cast<const Derived *>(this));
  }
  friend Derived;
};

template <typename Derived, typename Base>
class SFCGAL_API PrimitiveImpl<AbstractMethod<Derived>, Base> : public Base {
public:
  virtual ~PrimitiveImpl() = default;

private:
  virtual auto
  cloneImpl() const -> PrimitiveImpl * = 0;
};

#endif // ifndef DOXYGEN_SHOULD_SKIP_THIS
/// @} end of private section

} // namespace SFCGAL

#endif // SFCGAL_PRIMITIVE_H_
