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

#include <utility>

#include "SFCGAL/LineString.h"
#include "SFCGAL/Point.h"
#include "SFCGAL/Polygon.h"
#include "SFCGAL/PolyhedralSurface.h"
#include "SFCGAL/detail/transform/AffineTransform3.h"
#include "SFCGAL/primitive3d/Box.h"

namespace SFCGAL {

Box::Box(const Kernel::FT &x_extent, const Kernel::FT &y_extent,
         const Kernel::FT &z_extent)
{
  m_parameters["x_extent"] = x_extent;
  m_parameters["y_extent"] = y_extent;
  m_parameters["z_extent"] = z_extent;

  Box::validateParameters(m_parameters);
}

auto
Box::primitiveType() const -> std::string
{
  return "Box";
}

auto
Box::primitiveTypeId() const -> PrimitiveType
{
  return PrimitiveType::TYPE_BOX;
}

void
Box::setXExtent(const Kernel::FT &x_extent)
{
  validateAndSetParameter("x_extent", x_extent);
}

void
Box::setYExtent(const Kernel::FT &y_extent)
{
  validateAndSetParameter("y_extent", y_extent);
}

void
Box::setZExtent(const Kernel::FT &z_extent)
{
  validateAndSetParameter("z_extent", z_extent);
}

void
Box::validateParameters(
    std::unordered_map<std::string, PrimitiveParameter> const &tempParameters)
    const
{
  const double xExtent =
      CGAL::to_double(std::get<Kernel::FT>(tempParameters.at("x_extent")));
  const double yExtent =
      CGAL::to_double(std::get<Kernel::FT>(tempParameters.at("y_extent")));
  const double zExtent =
      CGAL::to_double(std::get<Kernel::FT>(tempParameters.at("z_extent")));

  if (xExtent < 0. || yExtent < 0. || zExtent < 0) {
    BOOST_THROW_EXCEPTION(Exception("Box parameters cannot be negative."));
  }
}

auto
Box::xExtent() const -> const Kernel::FT &
{
  return std::get<Kernel::FT>(m_parameters.at("x_extent"));
}

auto
Box::yExtent() const -> const Kernel::FT &
{
  return std::get<Kernel::FT>(m_parameters.at("y_extent"));
}

auto
Box::zExtent() const -> const Kernel::FT &
{
  return std::get<Kernel::FT>(m_parameters.at("z_extent"));
}

auto
Box::generatePolyhedralSurface() const -> PolyhedralSurface
{
  if (m_polyhedral_surface) {
    return *m_polyhedral_surface;
  }

  m_polyhedral_surface = PolyhedralSurface();

  // vertices
  const Kernel::FT   halfXExtent = xExtent() / 2.;
  const Kernel::FT   halfYExtent = yExtent() / 2.;
  const Kernel::FT   halfZExtent = zExtent() / 2.;
  std::vector<Point> points{{-halfXExtent, -halfYExtent, -halfZExtent},
                            {halfXExtent, -halfYExtent, -halfZExtent},
                            {-halfXExtent, halfYExtent, -halfZExtent},
                            {halfXExtent, halfYExtent, -halfZExtent},
                            {-halfXExtent, -halfYExtent, halfZExtent},
                            {halfXExtent, -halfYExtent, halfZExtent},
                            {-halfXExtent, halfYExtent, halfZExtent},
                            {halfXExtent, halfYExtent, halfZExtent}};

  // faces
  m_polyhedral_surface->addPatch(
      LineString({points[0], points[2], points[3], points[1], points[0]}));
  m_polyhedral_surface->addPatch(
      LineString({points[4], points[5], points[7], points[6], points[4]}));
  m_polyhedral_surface->addPatch(
      LineString({points[0], points[1], points[5], points[4], points[0]}));
  m_polyhedral_surface->addPatch(
      LineString({points[2], points[6], points[7], points[3], points[2]}));
  m_polyhedral_surface->addPatch(
      LineString({points[1], points[3], points[7], points[5], points[1]}));
  m_polyhedral_surface->addPatch(
      LineString({points[0], points[4], points[6], points[2], points[0]}));

  // handle affine transformation
  if (m_transform != Kernel::Aff_transformation_3(CGAL::IDENTITY)) {
    SFCGAL::transform::AffineTransform3 visitor(m_transform);
    m_polyhedral_surface->accept(visitor);
  }

  return *m_polyhedral_surface;
}

auto
Box::area3D(bool /*withDiscretization*/) const -> double
{
  const double lx = CGAL::to_double(xExtent());
  const double ly = CGAL::to_double(yExtent());
  const double lz = CGAL::to_double(zExtent());

  if (m_transform == Kernel::Aff_transformation_3(CGAL::IDENTITY)) {
    return 2.0 * (lx * ly + ly * lz + lx * lz);
  }

  const double sx = scaleFactor(0);
  const double sy = scaleFactor(1);
  const double sz = scaleFactor(2);

  return 2.0 * (lx * ly * sx * sy + ly * lz * sy * sz + lx * lz * sx * sz);
}

auto
Box::baseVolume(bool /*withDiscretization*/) const -> double
{
  return CGAL::to_double(xExtent() * yExtent() * zExtent());
}

} // namespace SFCGAL
