# S3 methods - Elementwise arithmetic
# Ops.HDF5Matrix dispatches +, -, *, / to the R6 methods


#' Elementwise arithmetic operators for HDF5Matrix objects
#'
#' @description
#' Standard R arithmetic operators applied element-wise to \code{HDF5Matrix}
#' objects stored on disk. Both operands must be \code{HDF5Matrix} objects
#' with identical dimensions.
#'
#' @param e1 An \code{HDF5Matrix} object (left-hand side)
#' @param e2 An \code{HDF5Matrix} object (right-hand side)
#'
#' @return A new \code{HDF5Matrix} containing the result, stored in the same
#'   HDF5 file as \code{e1} under a temporary dataset name.
#'
#' @details
#' Supported operators:
#' \describe{
#'   \item{\code{+}}{Element-wise addition}
#'   \item{\code{-}}{Element-wise subtraction}
#'   \item{\code{*}}{Element-wise multiplication (Hadamard product)}
#'   \item{\code{/}}{Element-wise division. Division by zero produces
#'     \code{NaN} or \code{Inf}, matching base R behaviour.}
#' }
#'
#' All operations use block-wise processing and optional OpenMP parallelisation,
#' controlled via \code{\link{hdf5matrix_options}}.
#'
#' **Broadcasting:** if one operand is a \eqn{1 \times n} or \eqn{n \times 1}
#' \code{HDF5Matrix} it is treated as a diagonal/vector operand and the
#' operation is broadcast row- or column-wise (via \code{\link{diag_op}})
#' rather than applied element-wise. Both operands must be \code{HDF5Matrix}
#' objects: scalar arithmetic such as \code{A + 5} is not supported.
#'
#' **Performance settings:**
#'
#' Global options set via \code{\link{hdf5matrix_options}} are applied.
#' For explicit control use the R6 methods directly:
#' \code{A$add(B, paral = TRUE, threads = 4)}.
#'
#' @examples
#' \donttest{
#' fn <- tempfile(fileext = ".h5")
#' A_hdf5 <- hdf5_create_matrix(fn, "data/A", data = matrix(1:12, 3, 4))
#' B_hdf5 <- hdf5_create_matrix(fn, "data/B", data = matrix(2, 3, 4))
#'
#' C <- A_hdf5 + B_hdf5
#' D <- A_hdf5 - B_hdf5
#' E <- A_hdf5 * B_hdf5
#' G <- A_hdf5 / B_hdf5
#'
#' all.equal(as.matrix(C), matrix(1:12, 3, 4) + 2)
#'
#' hdf5_close_all()
#' unlink(fn)
#' }
#'
#' @seealso
#' \code{\link{hdf5matrix_options}} for global performance settings,
#' \code{HDF5Matrix} for R6 methods with explicit parameters
#'
#' @export
Ops.HDF5Matrix <- function(e1, e2) {

    if (!inherits(e1, "HDF5Matrix") || !inherits(e2, "HDF5Matrix"))
        stop("Both operands must be HDF5Matrix objects")
    if (!e1$is_valid()) stop("e1 is closed or invalid")
    if (!e2$is_valid()) stop("e2 is closed or invalid")

    d1 <- dim(e1)
    d2 <- dim(e2)
    e1_is_diag <- (d1[1] == 1L || d1[2] == 1L)
    e2_is_diag <- (d2[1] == 1L || d2[2] == 1L)

    if (e1_is_diag || e2_is_diag) {
        mat  <- if (e1_is_diag) e2 else e1
        diag <- if (e1_is_diag) e1 else e2
        return(mat$diag_op(diag,
                           op          = .Generic,
                           threads     = .get_option("threads"),
                           compression = .get_option("compression",
                                                     default = NULL)))
    }

    switch(.Generic,
        "+" = e1$add(e2,
                     paral       = .get_option("paral"),
                     block_size  = .get_option("block_size"),
                     threads     = .get_option("threads"),
                     compression = .get_option("compression", default = NULL)),

        "-" = e1$subtract(e2,
                          paral       = .get_option("paral"),
                          block_size  = .get_option("block_size"),
                          threads     = .get_option("threads"),
                          compression = .get_option("compression", default = NULL)),

        "*" = e1$multiply_ew(e2,
                             paral       = .get_option("paral"),
                             block_size  = .get_option("block_size"),
                             threads     = .get_option("threads"),
                             compression = .get_option("compression", default = NULL)),

        "/" = e1$divide_ew(e2,
                           paral       = .get_option("paral"),
                           block_size  = .get_option("block_size"),
                           threads     = .get_option("threads"),
                           compression = .get_option("compression", default = NULL)),

        stop(paste0("operator '", .Generic,
                    "' is not supported for HDF5Matrix objects"))
    )
}
