#' @title Replace addmargins with treatment for data.frame
#' 
#' @param x either a matrix, an array or a data.frame
#' @param name vector, if x is a data.frame, gives the poistion of what should
#' be considered as names (and so not pass to addmargins).
#' @param output character, gives the type of output, either as.is or a
#' data.frame.
#' @param ... arguments that will be used for addmargins
#'
#' @return A table, array or data.frame with margins added.
#'
#' @export
#'
#' @encoding UTF-8
#' 
#' @examples
#'
#' add_margin(head(iris), 5)
#' add_margin(head(iris), 5, "data.frame")
#' add_margin(head(iris), 5, FUN = list("total" = sum))
#' 
#' x = data.frame(
#'     gender = sample(c("H", "F"), 20, replace = TRUE),
#'     region = sample(c("east", "west", "south", "north"), 20, replace = TRUE),
#'     wealth = round(runif(20) * 10000),
#'     size = round(runif(20) * 50 + 150)
#' )
#' add_margin(head(x), 1:2, margin = 1, FUN = list(mean = mean))
 
add_margin = function(
    x,
    name,
    output = c("normal", "data.frame"),
    ...
) {
    output = match.arg(output)

    if (is.data.frame(x)) {
        if (missing(name)) name = 1
        if (is.character(name)) name = grep(name, names(x))
        x_name = x[name]
        if (length(name) > 0) x = x[-name]
        x = structure(
            as.matrix(x),
            dimnames = list(
                apply(as.matrix(x_name), 1, paste, collapse = "_"),
                colnames(x)
            )
        )
    }

    if (!is.array(x) && ! is.table(x)) {
        stop("x should be something addmargins will expect at this stage!")
    }

    result = stats::addmargins(x, ...)

    if (output == "data.frame") {
        result = cbind(name = rownames(result), as.data.frame(result))
    }

    return(result)
}