#' @export
icu_tensor_normal <- function(X, F, max.iter = 100L) {
    if (is.null(dim(X)) || is.null(dim(F))) {
        stop("Dim. of `X` and `F` nust be non-null")
    }
    if (length(dim(X)) != length(dim(F)) || length(dim(X)) < 2L) {
        stop("`X` and `F` must have the same number of dimensions")
    }
    storage.mode(X) <- storage.mode(F) <- "double"

    est <- .Call("C_icu_tensor_normal", X, F, as.integer(max.iter))

    r <- length(dim(X)) - 1L
    list(
        eta = structure(est[[1]], dim = head(dim(X), -1)),
        alphas = est[seq.int(2, r + 1)],
        omegas = est[seq.int(r + 2, 2 * r + 1)]
    )
}


icu_tensor_normal_ref <- function(X, F, max.iter) {
    dimX <- head(dim(X), -1)
    dimF <- head(dim(F), -1)
    ord <- length(dimX)
    sample.size <- tail(dim(X), 1)
    # sizeX <- prod(dimX)
    # sizeF <- prod(dimF)


    # center X
    meanX <- rowMeans(X, dims = ord)
    X <- X - c(meanX)

    # initial alphas
    alphas <- Map(function(k) {
        XX <- tcrossprod(mat(X, k)) / prod(dimX[-k], sample.size)
        FF <- tcrossprod(mat(F, k)) / prod(dimF[-k], sample.size)

        svdXX <- La.svd(XX, dimF[k], 0)
        svdFF <- La.svd(FF, 0, dimX[k])

        dd <- head(svdXX$d, min(dimX[k], dimF[k])) * head(svdFF$d, min(dimX[k], dimF[k]))
        svdXX$u %*% diag(sqrt(dd)) %*% svdFF$vt
    }, 1:ord)

    # initial Omegas are set to the identity
    Omegas <- Map(diag, dimX)
    Deltas <- Map(solve, Omegas)

    # iterative cyclic updating loop
    for (iter in seq_len(max.iter)) {

        # update alphas one at a time
        for (j in seq_len(ord)) {
            Fa <- mlm(F, alphas[-j], (1:ord)[-j])
            FDa <- mlm(Fa, Deltas[-j], (1:ord)[-j])

            alphas[[j]] <- t(solve(
                mcrossprod(FDa, Fa, j), mcrossprod(Fa, X, j) %*% Omegas[[j]]
            ))
        }

        # update Omegas one at a time
        for (k in seq_len(ord)) {
            


            # update chenged Delta_j = Omega_j^-1
            Deltas[[j]] <- solve(Omegas[[j]])
        }

    }


    list(eta_bar = mlm(meanX, Omegas), alphas, Omegas)
}

# ################################################################################
# # setup dimensions
# n <- 1e3
# p <- c(3, 5, 11)
# q <- c(2, 7, 13)

# # create "true" GLM parameters
# eta1 <- array(rnorm(prod(p)), dim = p)
# alphas <- Map(matrix, Map(rnorm, p * q), p)
# Omegas <- Map(function(p_j) {
#     solve(0.5^abs(outer(seq_len(p_j), seq_len(p_j), `-`)))
# }, p)
# true.params <- list(eta1, alphas, Omegas)

# # compute tensor normal parameters from GLM parameters
# Deltas <- Map(solve, Omegas)
# mu <- mlm(eta1, Deltas)

# # sample some test data
# sample.axis <- length(p) + 1L
# Fy <- array(rnorm(n * prod(q)), dim = c(q, n))
# X <- mlm(Fy, Map(`%*%`, Deltas, alphas)) + rtensornorm(n, mu, Deltas, sample.axis)

# ################################################################################

# kronperm <- function(A) {
#     # force A to have even number of dimensions
#     dim(A) <- c(dim(A), rep(1L, length(dim(A)) %% 2L))
#     # compute axis permutation
#     perm <- as.vector(t(matrix(seq_along(dim(A)), ncol = 2)[, 2:1]))
#     # permute elements of A
#     K <- aperm(A, perm, resize = FALSE)
#     # collapse/set dimensions
#     dim(K) <- head(dim(A), length(dim(A)) / 2) * tail(dim(A), length(dim(A)) / 2)
#     K
# }

# p <- c(3, 5, 11)
# q <- c(2, 7, 13)

# A <- array(rnorm(prod(p)), dim = p)
# B <- array(rnorm(prod(q)), dim = q)
# Cs <- Map(function(d1, d2) matrix(rnorm(d1 * d2), d1), sample.int(5, length(p), TRUE), p)
# Ds <- Map(function(d1, d2) matrix(rnorm(d1 * d2), d1), sample.int(5, length(q), TRUE), q)

# all.equal(
#     kronperm(mlm(outer(A, B), c(Cs, Ds))),
#     kronecker(mlm(A, Cs), mlm(B, Ds))
# )
# all.equal(
#     mlm(outer(A, B), c(Cs, Ds)),
#     outer(mlm(A, Cs), mlm(B, Ds))
# )
