#' Perform Multivariate Spectral Principal Components Analysis on Spatial Functional Data.
#'
#' This function performs multivariate PCA on functional spatial data on a grid of
#' dimension r.
#' @param X a list containing the functional spatial data. Either an fd object or an array.
#' @param freq.res the resolution for the computation of the spectral density.
#' @param SM_Npc the number of multivariat spatial functional principal components to be computed.
#' @param L a list containing the maximum lag for the filters for each variable. An integer or vector of integers.
#' @param q a list containing tuning parameter for the estimation of the the spectral density
#'   operator for each variable. An integer or vector of integers.
#' @param na.ignore whether to ignore missing data points in the computation of
#'   the scores.
#' @param return.F a boolean indicating whether to return the spectral density.
#' @param only.filters a boolean indicating whether to compute only the filters,
#'   leaving out the scores.
#' @return A list with components \item{Value}{eigenvalue for each multivariate PC}
#'   \item{tuning.params}{a list of the used tuning parameters \code{freq.res},
#'   \code{q} and \code{Lmax}.} \item{filters}{the multivariate SPC filters.}
#'   \item{scores}{the multivariate SPC scores.} \item{vm}{the theoretical fractions of
#'   variance explained by each multivariate PC.} \item{ SML_Mean }{the mean of X.}
#'   \item{ SM_FSD }{list of univariate spatial functional principal component analysis.}
#' @details This function can be used to compute spectral PCA. By setting
#'   \code{q = 0}, it can also be used to perform static MFPCA.
#'
#'   Setting a higher value for \code{freq.res} increases the runtime   significantly, but yields a more
#'   accurate result because of reduced integration errors.
#' @keywords mfsd
#' @export
#' @examples
#' \donttest{
#' data("X1999")
#' data("X2000")
#' data("qlist")
#' data("Llist")
#' X = list(X1999, X2000)
#' q = list(qlist[,17], qlist[,18])
#' L = list(L1 = Llist[17], L2 = Llist[18])
#' mfsd(X = X, L = L, q = q, SM_Npc = 4)
#' }
mfsd = function (X, freq.res = 100, SM_Npc = 3, L = 3, q = NULL,
                 na.ignore = TRUE, return.F = FALSE, only.filters = FALSE)
{
  if (!inherits(X, "list")) {
    stop("'X' is not a list.")
  }
  if (!inherits(L, "list")) {
    stop("'L' is not a list.")
  }

  if (length(X)<2) {
    stop("in 'X', the number of variables must be greater or equal than 2.")
  }

  if (!is.numeric(SM_Npc)) {
    stop("Parameter SM_Npc must be integer")
  }


  y <- length(X)
  J = SM_Npc
  var = 1
  SML_Scores = vector("list", y)
  oldClass(SML_Scores) <- ("SML_Scores")

  SML_Filter = vector("list", y)
  oldClass(SML_Filter) <- ("SML_Filter")

  SM_FSD = vector("list", y)
  oldClass(SM_FSD) <- ("SM_FSD")

  SM_vec = vector("list", y)
  oldClass(SM_vec) <- ("SM_vec")

  SML_Mean = vector("list", y)
  oldClass(SML_Mean) <- ("SML_Mean")


  min = X[[1]][["basis"]][["nbasis"]]

  for(i in 1:y){
    if(X[[i]][["basis"]][["nbasis"]] < min){
      min = X[[i]][["basis"]][["nbasis"]]
    } }

  if( SM_Npc > min){
    stop("The parameter SM_Npc must be less than or equal to the number of basis functions")
  }


  if(length(q) > 1){
    for(i in 1:y){
      tryCatch({
        spca = fsd.spca(X[[i]], freq.res = freq.res, q = q[[i]], L = L[[i]], Npc = X[[i]][["basis"]][["nbasis"]], na.ignore = TRUE, return.F = TRUE, only.filters = FALSE)
        SML_Scores[[i]] <- spca$scores
        SML_Filter[[i]] <- spca$filters
        SML_Mean[[i]] <- spca$X.mean


        SM_FSD[[i]] <- list(F = spca$F, tuning.params = spca$tuning.params, filters = spca$filters, scores = spca$scores, var = spca$var, X.mean = spca$X.mean)
      }, error = function(e) {
        message("An error occurred in mfsd while processing the element ", i, ": ", conditionMessage(e), "\n")
      })
    }


  } else{
    for(i in 1:y){
      tryCatch({
        Xspec = fsd.spca(X[[i]], freq.res = freq.res, q = 0, Npc = X[[i]][["basis"]][["nbasis"]], na.ignore = TRUE, return.F = TRUE, only.filters = FALSE)
        SML_Scores[[i]] <- Xspec$scores
        SML_Filter[[i]] <- Xspec$filters
        SML_Mean[[i]] <- Xspec$X.mean


        SM_FSD[[i]] <- list(F = Xspec$F, tuning.params = Xspec$tuning.params, filters = Xspec$filters, scores = Xspec$scores, var = Xspec$var, X.mean = Xspec$X.mean)
      }, error = function(e) {
        message("An error occurred in mfsd while processing the element ", i, ": ", conditionMessage(e), "\n")
      })

    } }


  for(i in 1:y){
    tryCatch({
      SML_Scores[[i]] <- matrix(SM_FSD[[i]][["scores"]], ncol = dim(SM_FSD[[i]][["scores"]])[1], byrow = TRUE)
    }, error = function(e) {
      message("An error occurred in mfsd while processing the element ", i, ": ", conditionMessage(e), "\n")
    }) }

  EEM <- do.call("cbind", SML_Scores)


  zhat <- (1/(dim(EEM)[1]-1))*(t(EEM)%*%EEM)

  evz = eigen(zhat)
  cm = evz$vectors
  vm = evz$values

  val=vm[seq_len(J)]

  vm = 100*(cumsum(vm)/sum(vm))


  vec=cm[,seq_len(J)]


  for(i in 1:y){
    tryCatch({
      SM_vec[[i]] <- vec[var:(J+var-1),]
      var = var + dim(SM_FSD[[i]][["scores"]])[1]
    }, error = function(e) {
      message("An error occurred in mfsd while processing the element ", i, ": ", conditionMessage(e), "\n")
    })}

  var_score= EEM%*%vec
  var_score_m <- array(t(var_score), dim = c(dim(var_score)[2], dim(X[[1]][["coefs"]])[2], dim(X[[1]][["coefs"]])[3]))

  SML_Scores <- mfsd_Scores(EEM, SM_vec, SM_FSD, X, J)


  SML_Filter <- mfsd_Filter(SM_FSD, SM_vec, J)

  if(length(q)<2 && q==0){
    M_object <- list(Value = val, filter = SML_Filter, SM_Scores = SML_Scores,SML_Mean = SML_Mean, SM_FSD = SM_FSD, vm = vm)
    oldClass(M_object) <- ("M_object")

    return(M_object)
  }

  SM_object <- list(Value = val, filter = SML_Filter, SM_Scores = SML_Scores, SML_Mean = SML_Mean, SM_FSD = SM_FSD, vm = vm)
  oldClass(SM_object) <- ("SM_object")
  return(SM_object)
}
