#' @title Mesh of an algebraic surface
#' @description Computes a mesh of an algebraic surface (isosurface defined 
#'   by a polynomial).
#'
#' @param polynomial the polynomial defining the isosurface; this can be either 
#'   a \code{\link[spray]{spray}} object or a list with two fields: 
#'   \code{exponents}, an integer matrix with three columns, and \code{coeffs}, 
#'   a numeric vector
#' @param isolevel a number, the value of the polynomial defining the 
#'   isosurface: the isosurface is defined by \code{P(x,y,z)=isolevel}, 
#'   where \code{P} is the polynomial 
#' @param sphereCenter,sphereRadius center and radius of a sphere bounding the 
#'   isosurface; the value of the polynomial at the center of 
#'   this sphere must be less than the isolevel 
#' @param angleBound lower bound in degrees for the angles of the faces of 
#'   the mesh
#' @param radiusBound upper bound for the radii of surface Delaunay balls; 
#'   a surface Delaunay ball is a ball circumscribing a face and centered 
#'  at the surface
#' @param distanceBound upper bound for the distance between the 
#'   circumcenter of a face and the center of the surface Delaunay 
#'   ball of this face
#' @param errorBound a relative error bound used in the computations
#'
#' @return A \code{cgalMesh} object. The mesh has normals computed 
#'   with the gradient of the polynomial.
#'   
#' @note If either \code{angleBound} is too high, \code{radiusBound} is 
#'   too small, or \code{distanceBound} is too small, the computation could 
#'   never finishes. Therefore, it is recommended to start with a small 
#'   \code{angleBound} and large \code{radiusBound} and \code{distanceBound}, 
#'   and then to adjust these parameters if the resulting mesh is not nice.  
#'   
#' @export
#'
#' @examples
#' library(cgalMeshes)
#' library(rgl)
#' # the Barth decic
#' phi <- (1 + sqrt(5)) / 2
#' f <- function(x, y, z) {
#'   (18+30*phi) * x**4 * y**4 +
#'     (-36 - 30*phi + 44*phi**2 - 10*phi**3) * x**2 +
#'     (-18 - 24*phi + 10*phi**2) * x**6 +
#'     (3 + 5*phi) * x**8 +
#'     (36 + 60*phi) * x**2 * y**2 * z**4 +
#'     (12 + 20*phi) * x**2 * y**6 +
#'     phi +
#'     (-16 + 8*phi**4 - 8*phi**8 + 16*phi**12) * x**2 * y**4 * z**4 +
#'     (8 * phi**8) * y**2 * z**8 +
#'     (-18 - 24*phi + 10*phi**2) * z**6 +
#'     (-8*phi**4 - 16*phi**8) * x**6 * z**4 +
#'     (16*phi**4 + 8*phi**8) * x**6 * y**4 +
#'     (-8*phi**4) * y**8 * z**2 +
#'     (-18 - 24*phi + 10*phi**2) * y**6 +
#'     (12 + 20*phi) * x**2 * z**6 +
#'     (36 + 60*phi) * x**4 * y**2 * z**2 +
#'     (36 + 60*phi) * x**2 * y**4 * z**2 +
#'     (8 + 16*phi**4 -16*phi**8 - 8*phi**12) * x**2 * y**2 * z**6 +
#'     (-54 - 72*phi + 30*phi**2) * y**4 * z**2 +
#'     (-8*phi**4) * x**8 * y**2 +
#'     (16*phi**4 + 8*phi**8) * y**6 * z**4 +
#'     (12 + 20*phi) * y**2 * z**6 +
#'     (3 + 5*phi) * z**8 +
#'     (-8*phi**4) * x**2 * z**8 +
#'     (39 + 41*phi - 37*phi**2 + 5*phi**3) * z**4 +
#'     (-54 - 72*phi + 30*phi**2) * x**2 * y**4 +
#'     (8 + 16*phi**4 -16*phi**8 - 8*phi**12) * x**6 * y**2 * z**2 +
#'     (-54 - 72*phi + 30*phi**2) * x**2 * z**4 +
#'     (12 + 20*phi) * x**6 * z**2 +
#'     (-16 + 8*phi**4 - 8*phi**8 + 16*phi**12) * x**4 * y**2 * z**4 +
#'     (16*phi**4 + 8*phi**8) * x**4 * z**6 +
#'     (39 + 41*phi - 37*phi**2 + 5*phi**3) * y**4 +
#'     (-36 - 30*phi + 44*phi**2 - 10*phi**3) * z**2 +
#'     (8*phi**8) * x**2 * y**8 +
#'     (12 + 20*phi) * y**6 * z**2 +
#'     (8*phi**8) * x**8 * z**2 +
#'     (-36 - 30*phi + 44*phi**2 - 10*phi**3) * y**2 +
#'     (12 + 20*phi) * x**6 * y**2 +
#'     (-8*phi**4 - 16*phi**8) * y**4 * z**6 +
#'     (-16 + 8*phi**4 - 8*phi**8 + 16*phi**12) * x**4 * y**4 * z**2 +
#'     (78 + 82*phi - 74*phi**2 + 10*phi**3) * x**2 * z**2 +
#'     (18+30*phi) * x**4 * z**4 +
#'     (-8*phi**4 - 16*phi**8) * x**4 * y**6 +
#'     (-54 - 72*phi + 30*phi**2) * x**4 * y**2 +
#'     (-54 - 72*phi + 30*phi**2) * x**4 * z**2 +
#'     (-54 - 72*phi + 30*phi**2) * y**2 * z**4 +
#'     (78 + 82*phi - 74*phi**2 + 10*phi**3) * x**2 * y**2 +
#'     (-108 - 144*phi + 60*phi**2) * x**2 * y**2 * z**2 +
#'     (18+30*phi) * y**4 * z**4 +
#'     (3 + 5*phi) * y**8 +
#'     (78 + 82*phi - 74*phi**2 + 10*phi**3) * y**2 * z**2 +
#'     (8 + 16*phi**4 -16*phi**8 - 8*phi**12) * x**2 * y**6 * z**2 +
#'     (39 + 41*phi - 37*phi**2 + 5*phi**3) * x**4
#' }
#' # define f as a polynomial
#' library(spray)
#' x <- lone(1, 3)
#' y <- lone(2, 3)
#' z <- lone(3, 3)
#' P <- f(x, y, z)
#' # compute the mesh of the algebraic surface
#' # we have to negate P in order that the value of P at the 
#' # center of the bounding sphere is less than the isovalue
#' \donttest{mesh <- algebraicMesh(
#'   polynomial = -P, isolevel = 0,
#'   sphereCenter = c(0, 0, 0),
#'   sphereRadius = sqrt((5+sqrt(5))/2),
#'   angleBound    = 20, 
#'   radiusBound   = 0.015, 
#'   distanceBound = 0.015
#' )
#' # plot
#' rmesh <- mesh$getMesh()
#' open3d(windowRect = c(50, 50, 562, 562))
#' view3d(20, 20, zoom= 0.75)
#' bg3d(rgb(54, 57, 64, maxColorValue = 255))
#' shade3d(rmesh, color = "orangered")}
algebraicMesh <- function(
  polynomial, isolevel, 
  sphereCenter, sphereRadius,
  angleBound, radiusBound, distanceBound, 
  errorBound = 1e-3
) {
  stopifnot(isNumber(isolevel))
  stopifnot(isVector3(sphereCenter))
  stopifnot(isPositiveNumber(sphereRadius))
  stopifnot(isPositiveNumber(angleBound))
  stopifnot(isPositiveNumber(radiusBound))
  stopifnot(isPositiveNumber(distanceBound))
  stopifnot(isPositiveNumber(errorBound))
  if(inherits(polynomial, "spray")) {
    polynomial[["exponents"]] <- polynomial[["index"]]
    polynomial[["coeffs"]]    <- polynomial[["value"]]
  }
  if(is.list(polynomial)) {
    exponents <- polynomial[["exponents"]]
    stopifnot(is.matrix(exponents))
    stopifnot(ncol(exponents) == 3L)
    storage.mode(exponents) <- "integer"
    if(anyNA(exponents)) {
      stop("Found missing values in the exponents of the polynomial.")
    }
    if(any(exponents < 0L)) {
      stop("The exponents of the polynomial must be positive.")
    }
    coeffs <- polynomial[["coeffs"]]
    storage.mode(coeffs) <- "double"
    if(anyNA(coeffs)) {
      stop("Found missing values in the coefficients of the polynomial.")
    }
    stopifnot(nrow(exponents) == length(coeffs))
  } else {
    stop("Invalid `polynomial` argument.")
  }
  xptr <- AlgebraicMesh(
    exponents, coeffs, isolevel, 
    sphereCenter, sphereRadius, 
    angleBound, radiusBound, distanceBound,
    errorBound
  )
  cgalMesh$new(clean = xptr)
}

#' @title Intersection of algebraic surfaces
#' @description Computes a mesh of the intersection of some algebraic surfaces 
#'   (isosurfaces defined by a polynomial).
#'
#' @param polynomials the polynomials defining the isosurfaces, a list; 
#'   each element of this list can be either a \code{\link[spray]{spray}} 
#'   object or a list with two fields: \code{exponents}, an integer matrix with 
#'   three columns, and \code{coeffs}, a numeric vector; each isosurface is 
#'   then defined by \code{P(x,y,z)=0}, where \code{P} is a polynomial of this 
#'   list 
#' @param sphereCenter,sphereRadius center and radius of a sphere bounding the 
#'   isosurface; the values of the polynomials at the center of this sphere 
#'   must be less than zero
#' @param angleBound lower bound in degrees for the angles of the faces of 
#'   the mesh
#' @param radiusBound upper bound for the radii of surface Delaunay balls; 
#'   a surface Delaunay ball is a ball circumscribing a face and centered 
#'  at the surface
#' @param distanceBound upper bound for the distance between the 
#'   circumcenter of a face and the center of the surface Delaunay 
#'   ball of this face
#' @param errorBound a relative error bound used in the computations
#'
#' @return A \code{cgalMesh} object.
#'   
#' @note If either \code{angleBound} is too high, \code{radiusBound} is 
#'   too small, or \code{distanceBound} is too small, the computation could 
#'   never finishes. Therefore, it is recommended to start with a small 
#'   \code{angleBound} and large \code{radiusBound} and \code{distanceBound}, 
#'   and then to adjust these parameters if the resulting mesh is not nice.  
#'   
#' @export
#' @examples
#' \donttest{library(cgalMeshes)
#' library(rgl)
#' library(spray)
#' x <- lone(1, 3); y <- lone(2, 3); z <- lone(3, 3)
#' P1 <-  x^2 + y^2 - z^2 - 1
#' P2 <-  x^2 - y^2 + z^2 - 1
#' P3 <- -x^2 + y^2 + z^2 - 1
#' # compute the mesh of the intersection (trihyperboloid)
#' mesh <- algebraicMeshesIntersection(
#'   polynomials = list(P1, P2, P3), 
#'   sphereCenter = c(0, 0, 0), sphereRadius = 1.8,
#'   angleBound    = 10, 
#'   radiusBound   = 0.008, 
#'   distanceBound = 0.005,
#'   errorBound = 5e-6
#' )
#' # plot
#' rmesh <- mesh$getMesh()
#' open3d(windowRect = c(50, 50, 562, 562))
#' view3d(20, 20)
#' bg3d(rgb(54, 57, 64, maxColorValue = 255))
#' shade3d(rmesh, color = "orangered")}
algebraicMeshesIntersection <- function(
    polynomials, 
    sphereCenter, sphereRadius,
    angleBound, radiusBound, distanceBound, 
    errorBound = 1e-3
) {
  stopifnot(is.list(polynomials), length(polynomials) >= 2L)
  stopifnot(isVector3(sphereCenter))
  stopifnot(isPositiveNumber(sphereRadius))
  stopifnot(isPositiveNumber(angleBound))
  stopifnot(isPositiveNumber(radiusBound))
  stopifnot(isPositiveNumber(distanceBound))
  stopifnot(isPositiveNumber(errorBound))
  Polynomials <- vector("list", length = length(polynomials))
  for(i in seq_along(polynomials)) {
    polynomial <- polynomials[[i]]
    if(inherits(polynomial, "spray")) {
      polynomial[["exponents"]] <- polynomial[["index"]]
      polynomial[["coeffs"]]    <- polynomial[["value"]]
    }
    if(is.list(polynomial)) {
      exponents <- polynomial[["exponents"]]
      stopifnot(is.matrix(exponents))
      stopifnot(ncol(exponents) == 3L)
      storage.mode(exponents) <- "integer"
      if(anyNA(exponents)) {
        stop("Found missing values in the exponents of a polynomial.")
      }
      if(any(exponents < 0L)) {
        stop("The exponents of a polynomial must be positive.")
      }
      coeffs <- polynomial[["coeffs"]]
      storage.mode(coeffs) <- "double"
      if(anyNA(coeffs)) {
        stop("Found missing values in the coefficients of a polynomial.")
      }
      stopifnot(nrow(exponents) == length(coeffs))
    } else {
      stop("Invalid polynomial found.")
    }
    Polynomials[[i]] <- list(
      "exponents" = exponents, 
      "coeffs"    = coeffs
    )
  }
  xptr <- AlgebraicMeshesIntersection(
    Polynomials, 
    sphereCenter, sphereRadius, 
    angleBound, radiusBound, distanceBound,
    errorBound
  )
  cgalMesh$new(clean = xptr)
}


#' @title Union of algebraic surfaces
#' @description Computes a mesh of the union of some algebraic surfaces 
#'   (isosurfaces defined by a polynomial).
#'
#' @param polynomials the polynomials defining the isosurfaces, a list; 
#'   each element of this list can be either a \code{\link[spray]{spray}} 
#'   object or a list with two fields: \code{exponents}, an integer matrix with 
#'   three columns, and \code{coeffs}, a numeric vector; each isosurface is 
#'   then defined by \code{P(x,y,z)=0}, where \code{P} is a polynomial of this 
#'   list 
#' @param sphereCenter,sphereRadius center and radius of a sphere bounding the 
#'   isosurface; at least one of the values of the polynomials at the center 
#'   of this sphere must be less than zero
#' @param angleBound lower bound in degrees for the angles of the faces of 
#'   the mesh
#' @param radiusBound upper bound for the radii of surface Delaunay balls; 
#'   a surface Delaunay ball is a ball circumscribing a face and centered 
#'  at the surface
#' @param distanceBound upper bound for the distance between the 
#'   circumcenter of a face and the center of the surface Delaunay 
#'   ball of this face
#' @param errorBound a relative error bound used in the computations
#'
#' @return A \code{cgalMesh} object.
#'   
#' @note If either \code{angleBound} is too high, \code{radiusBound} is 
#'   too small, or \code{distanceBound} is too small, the computation could 
#'   never finishes. Therefore, it is recommended to start with a small 
#'   \code{angleBound} and large \code{radiusBound} and \code{distanceBound}, 
#'   and then to adjust these parameters if the resulting mesh is not nice.  
#'   
#' @export
#' @examples
#' \donttest{library(cgalMeshes)
#' library(rgl)
#' library(spray)
#' x <- lone(1, 3); y <- lone(2, 3); z <- lone(3, 3)
#' # sphere
#' P1 <- x^2 + y^2 + z^2 - 1
#' # torus
#' R <- 1; r <- 0.4
#' P2 <- (x^2 + y^2 + z^2 + R^2 - r^2)^2 - 4*R^2*(x^2 + y^2)
#' # compute the mesh of the union
#' mesh <- algebraicMeshesUnion(
#'   polynomials = list(P1, P2), 
#'   sphereCenter = c(0, 0, 0), sphereRadius = 1.5,
#'   angleBound    = 30, 
#'   radiusBound   = 0.04, 
#'   distanceBound = 0.02,
#'   errorBound = 1e-5
#' )
#' mesh$computeNormals()
#' # plot
#' rmesh <- mesh$getMesh()
#' open3d(windowRect = c(50, 50, 562, 562))
#' view3d(20, 20, zoom = 0.7)
#' bg3d(rgb(54, 57, 64, maxColorValue = 255))
#' shade3d(rmesh, color = "orangered")
#' wire3d(rmesh, color = "black")}
algebraicMeshesUnion <- function(
    polynomials, 
    sphereCenter, sphereRadius,
    angleBound, radiusBound, distanceBound, 
    errorBound = 1e-3
) {
  stopifnot(is.list(polynomials), length(polynomials) >= 2L)
  stopifnot(isVector3(sphereCenter))
  stopifnot(isPositiveNumber(sphereRadius))
  stopifnot(isPositiveNumber(angleBound))
  stopifnot(isPositiveNumber(radiusBound))
  stopifnot(isPositiveNumber(distanceBound))
  stopifnot(isPositiveNumber(errorBound))
  Polynomials <- vector("list", length = length(polynomials))
  for(i in seq_along(polynomials)) {
    polynomial <- polynomials[[i]]
    if(inherits(polynomial, "spray")) {
      polynomial[["exponents"]] <- polynomial[["index"]]
      polynomial[["coeffs"]]    <- polynomial[["value"]]
    }
    if(is.list(polynomial)) {
      exponents <- polynomial[["exponents"]]
      stopifnot(is.matrix(exponents))
      stopifnot(ncol(exponents) == 3L)
      storage.mode(exponents) <- "integer"
      if(anyNA(exponents)) {
        stop("Found missing values in the exponents of a polynomial.")
      }
      if(any(exponents < 0L)) {
        stop("The exponents of a polynomial must be positive.")
      }
      coeffs <- polynomial[["coeffs"]]
      storage.mode(coeffs) <- "double"
      if(anyNA(coeffs)) {
        stop("Found missing values in the coefficients of a polynomial.")
      }
      stopifnot(nrow(exponents) == length(coeffs))
    } else {
      stop("Invalid polynomial found.")
    }
    Polynomials[[i]] <- list(
      "exponents" = exponents, 
      "coeffs"    = coeffs
    )
  }
  xptr <- AlgebraicMeshesUnion(
    Polynomials, 
    sphereCenter, sphereRadius, 
    angleBound, radiusBound, distanceBound,
    errorBound
  )
  cgalMesh$new(clean = xptr)
}
