#' Random sampling from a tensor (multi-array) normal distribution
#'
#' @examples
#' n <- 1000
#' Sigma.1 <- 0.5^abs(outer(1:5, 1:5, "-"))
#' Sigma.2 <- diag(1:4)
#' X <- rtensornorm(n, 0, list(Sigma.1, Sigma.2))
#'
#' @export
rtensornorm <- function(n, mean, cov, sample.axis = 1L) {
    # get sample shape (dimensions of an observation)
    shape <- unlist(Map(nrow, cov))

    # result tensor dimensions
    dims <- c(shape, n)

    # validate mean dimensions
    if (!missing(mean)) {
        stopifnot(is.numeric(mean))
        stopifnot(is.array(mean) || length(mean) == 1)
        stopifnot(length(shape) == length(cov))
        stopifnot(all(shape == dim(mean)))
    }

    # sample i.i.d. normal entries
    X <- array(rnorm(prod(dims)), dim = dims)

    # transform from standard normal to tensor normal with given covariances
    for (i in seq_along(cov)) {
        X <- ttm(X, matpow(cov[[i]], 1 / 2), i)
    }

    # add mean (using recycling, observations on last mode)
    if (!missing(mean)) {
        X <- X + c(mean)
    }

    # permute axis for indexing observations on sample mode (permute first axis
    # with sample mode axis)
    if (sample.axis != length(dims)) {
        axis <- seq_len(length(dims) - 1)
        start <- seq_len(sample.axis - 1)
        end <- seq_len(length(dims) - sample.axis) + sample.axis - 1
        X <- aperm(X, c(axis[start], length(dims), axis[end]))
    }

    X
}
