library(doMC)
library(abind)
library(plyr)

#  Function to determine whether the AtomicFun is for 
# Verification, Transformation or Calibration, needs to
# be updated to include all relevant s2dv, Specs and easyV funs.
.getStep <- function(x) {
  TransformFuns <- c("Ano")
  VeriFuns <- c("Corr", ".Corr", "BrierScore", "RMS",
                "RatioSDRMS", "RatioRMS", "EnsBrier", "Brier",
                "EnsCorr", "EnsRmse")
  CalibrateFuns <- c("Calibrate")
  if (x %in% c(TransformFuns)) {
    Step <- "Transform"
  } else if (x %in% c(CalibrateFuns)) {
    Step <- "Calibrate"
  } 
  else if (x %in% c(VeriFuns)) {
    Step <- "Verification"
  } else {
    Step <- "Unknown"
  }
  invisible(Step)
}

#Create a function to guess the long name based on the Variable,
#Step and Command metadata.
#.guessLongname <- function(x) {
#  if 
#}

AtomicToArray <- function(exp, obs, AtomicFun = NULL, members_dim = NULL, 
                          time_dim = NULL, chunk_dim = 4, meanexp = FALSE, meanobs = FALSE, 
                          parallel = FALSE, ncores = NULL, metadata = FALSE, ...) {
  if (chunk_dim == time_dim || chunk_dim == members_dim || members_dim == time_dim) {
    stop("'chunk_dim' cannot be the same as time_dim or members_dim")
  }
  
  AtomicFunction <- get(AtomicFun)
  Combine <-abind(exp, obs, along = members_dim)
  lengthA <- c(dim(exp)[members_dim])
  lengthB <- c(dim(obs)[members_dim])
  dims <- 1 : length(dim(Combine))
  chunk <- dim(Combine)[chunk_dim]
  .WrapperFun <- function(Combine, AtomicFunction, chunk_dim, lengthA, lengthB, 
                          meanexp, meanobs, ...) {
    t(sapply(1: chunk, FUN = function(i) {
      A <- Combine[1 : lengthA, , i]
      B <- Combine[(lengthA + 1) : (lengthA + lengthB), , i]
      if (meanexp == TRUE) {
        A <- rowMeans(t(A))
      }
      if (meanobs == TRUE && length(dim(B)) > 1) {
        B <- rowMeans(B)
      } 
      if (!is.vector(A) && is.vector(B)) {
        if (dim(A)[1] != length(B)) {
          A <- t(A)
        }
      }
      out <- unlist(AtomicFunction(A, B, ...))
      attr(out, "elementnames") <- names(out)
      out
      }))
  }
  if (parallel == TRUE) {
    if (is.null(ncores)) {
    } else {
      ncores <- min(detectCores() - 1, ncores)
    }
    registerDoMC(ncores)
    WrapperFun <- aaply(Combine, .margins = dims[c(-members_dim, -time_dim, -chunk_dim)], .fun = .WrapperFun,
                        AtomicFunction, chunk_dim, lengthA, lengthB, meanexp, meanobs, ..., .drop = FALSE, .parallel = TRUE) 
  }
  if (parallel == FALSE) {
    WrapperFun <- aaply(Combine, .margins = dims[c(-members_dim, -time_dim, -chunk_dim)], .fun = .WrapperFun, 
                        AtomicFun, chunk_dim, lengthA, lengthB, meanexp, meanobs, ..., .drop = FALSE, .parallel = FALSE) 
  }
  if (class(exp) ==  "multiCDM") {
    class(WrapperFun) <- "multiCDM"
    #attr(WrapperFun, "dimensionNames") <- names(dim(exp[-time_dim]))
    attr(WrapperFun, "Command") = list(generatedbyCommand = AtomicFun, fromPackage = gsub("package:", "",find(AtomicFun))[2],
                                     packageVersion = packageVersion(gsub("package:", "",find(AtomicFun))[2]),
                                     withForecast = attr(exp, "Dataset")$Dataset, 
                                     withReference = attr(obs, "Dataset")$Dataset)
    attr(WrapperFun, "Step") <- list(class = .getStep(AtomicFun), type = AtomicFun, attribute = NULL)
    
                                                          
    if (.getStep(AtomicFun) == "Verification") {
      attr(WrapperFun, "Dataset")$TemporalPeriod = NULL
      attr(WrapperFun, "hasNext")  = "PlotTimeSeries" 
      attr(WrapperFun, "dimensionNames") = c(names(dim(exp)[c(-time_dim, -members_dim)]), "test")
      
    }
  }
  invisible(WrapperFun)
}