#'Project anomalies onto modes of variability
#'
#'Project anomalies onto modes of variability to get the temporal evolution of 
#'the EOF mode selected. It returns principal components (PCs) by area-weighted
#'projection onto EOF pattern (from \code{EOF()}). The calculation removes NA 
#'and returns NA if the whole spatial pattern is NA.
#'
#'@param ano A numerical array of anomalies with named dimensions. The 
#'  dimensions must have at least 'time_dim' and 'space_dim'.
#'@param eof A list contains at least 'EOFs' and 'wght', which are both arrays.
#'  'EOFs' has dimensions same as 'ano' except 'EOFs' has 'mode' and 'ano' has
#'   time_dim. 'wght' has dimensions space_dim. It can be generated by EOF().
#'@param time_dim A character string indicating the name of the time dimension
#' of 'ano'. The default value is 'sdate'. 
#'@param space_dim A vector of two character strings. The first is the dimension
#'  name of latitude of 'ano' and the second is the dimension name of longitude
#'  of 'ano'. The default value is c('lat', 'lon').
#'@param mode An integer of the variability mode number in the EOF to be 
#'  projected on. The default value is 1.
#'@param ncores An integer indicating the number of cores to use for parallel 
#'  computation. The default value is NULL.
#'
#'@return A numerical array of the principal components in the verification 
#'  format. The dimensions are the same as 'ano' except 'space_dim'.
#'
#'@seealso EOF, NAO, PlotBoxWhisker
#'@examples
#'\dontshow{
#'startDates <- c('19851101', '19901101', '19951101', '20001101', '20051101')
#'sampleData <- s2dv:::.LoadSampleData('tos', c('experiment'),
#'                                     c('observation'), startDates,
#'                                     leadtimemin = 1,
#'                                     leadtimemax = 4,
#'                                     output = 'lonlat',
#'                                     latmin = 27, latmax = 48,
#'                                     lonmin = -12, lonmax = 40)
#'}
#'ano <- Ano_CrossValid(sampleData$mod, sampleData$obs)
#'eof_exp <- EOF(ano$exp, sampleData$lat, sampleData$lon) 
#'eof_obs <- EOF(ano$obs, sampleData$lat, sampleData$lon) 
#'mode1_exp <- ProjectField(ano$exp, eof_exp)
#'mode1_obs <- ProjectField(ano$obs, eof_obs)
#'
#'\dontrun{
#'  # Plot the forecast and the observation of the first mode for the last year 
#'  # of forecast
#'  sdate_dim_length <- dim(mode1_obs)['sdate']
#'  plot(mode1_obs[sdate_dim_length, 1, 1, ], type = "l", ylim = c(-1, 1), 
#'       lwd = 2)
#'  for (i in 1:dim(mode1_exp)['member']) {
#'    par(new = TRUE)
#'    plot(mode1_exp[sdate_dim_length, 1, i, ], type = "l", col = rainbow(10)[i], 
#'         ylim = c(-15000, 15000))
#'  }
#'}
#'
#'@export
ProjectField <- function(ano, eof, time_dim = 'sdate', 
                         space_dim = c('lat', 'lon'), mode = 1, ncores = NULL) {

  # Check inputs 
  ## ano
  if (is.null(ano)) {
    stop("Parameter 'ano' cannot be NULL.")
  }
  if (!is.numeric(ano)) {
    stop("Parameter 'ano' must be a numeric array.")
  }
  if(any(is.null(names(dim(ano))))| any(nchar(names(dim(ano))) == 0)) {
    stop("Parameter 'ano' must have dimension names.")
  }
  ## eof (1)
  if (is.null(eof)) {
    stop("Parameter 'eof' cannot be NULL.")
  }
  if (!is.list(eof)) {
    stop("Parameter 'eof' must be a list generated by EOF().")
  }
  if (!all(c('EOFs', 'wght') %in% names(eof))) {
    stop("Parameter 'eof' must contain 'EOFs' and 'wght' generated by EOF().")
  }
  if (!is.numeric(eof$EOFs) || !is.array(eof$EOFs)) {
    stop("The component 'EOFs' of parameter 'eof' must be a numeric array.")
  }
  if (!is.numeric(eof$wght) || !is.array(eof$wght)) {
    stop("The component 'wght' of parameter 'eof' must be a numeric array.")
  }
  ## time_dim
  if (!is.character(time_dim) | length(time_dim) > 1) {
    stop("Parameter 'time_dim' must be a character string.")
  }
  if (!time_dim %in% names(dim(ano))) {
    stop("Parameter 'time_dim' is not found in 'ano' dimension.")
  }
  ## space_dim
  if (!is.character(space_dim) | length(space_dim) != 2) {
    stop("Parameter 'space_dim' must be a character vector of 2.")
  }
  if (any(!space_dim %in% names(dim(ano)))) {
    stop("Parameter 'space_dim' is not found in 'ano' dimension.")
  }
  ## eof (2)
  if (!all(space_dim %in% names(dim(eof$EOFs))) | 
      !'mode' %in% names(dim(eof$EOFs))) {
    stop(paste0("The component 'EOFs' of parameter 'eof' must be an array ",
                "with dimensions named as parameter 'space_dim' and 'mode'."))
  }
  # eof$EOFs should have the same dimensions as 'ano' except that ano doesn't have 'mode' and EOFs doesn't have time_dim
  common_dim_ano <- dim(ano)[-which(names(dim(ano)) == time_dim)]
  common_dim_eofs <- dim(eof$EOFs)[-which(names(dim(eof$EOFs)) == 'mode')]
  raise_error <- FALSE
  if (length(common_dim_ano) != length(common_dim_eofs)) {
    raise_error <- TRUE
  } else if (!all(names(common_dim_ano) %in% names(common_dim_eofs)) |
             !all(names(common_dim_eofs) %in% names(common_dim_ano))) {
    raise_error <- TRUE
  } else {
    order <- match(names(common_dim_ano), names(common_dim_eofs))
    if (any(common_dim_eofs[order] != common_dim_ano)) {
      raise_error <- TRUE
    }
  }
  if (raise_error) {
    stop(paste0("The component 'EOFs' of parameter 'eof' must have the ",
                "same dimensions as 'ano' except that 'ano' does not have ",
                "'mode' and 'EOFs' does not have time_dim."))
  }
  if (length(dim(eof$wght)) != 2 | !all(names(dim(eof$wght)) %in% space_dim)) {
    stop(paste0("The component 'wght' of parameter 'eof' must be an array ",
                "with dimensions named as parameter 'space_dim'."))
  }
  ## mode
  if (!is.numeric(mode) | mode %% 1 != 0 | mode < 0 | length(mode) > 1) {
    stop("Parameter 'mode' must be a positive integer.")
  }
  if (mode > dim(eof$EOFs)['mode']) {
    stop(paste0("Parameter 'mode' is greater than the number of available ",
                "modes in EOF."))
  }
  ## ncores
  if (!is.null(ncores)) {
    if (!is.numeric(ncores) | ncores %% 1 != 0 | ncores <= 0 |
      length(ncores) > 1) {
      stop("Parameter 'ncores' must be a positive integer.")
    }
  }
 
#-------------------------------------------------------

  # Keep the chosen mode
  eof_mode <- Subset(eof$EOFs, 'mode', mode, drop = 'selected')

  res <- Apply(list(ano, eof_mode, eof$wght),
               target_dims = list(c(space_dim, time_dim),
                                  c(space_dim),
                                  c(space_dim)),
               output_dims = time_dim,
               fun = .ProjectField,
               ncores = ncores)$output1

  return(res)
}


.ProjectField <- function(ano, eof_mode, wght) {
  # ano: [lat, lon, sdate]
  # eof_mode: [lat, lon]
  # wght: [lat, lon]
  dim_time <- dim(ano)[3]

  # Initialization of pc.ver.
  pc.ver <- array(NA, dim = dim_time)  #[sdate]
  
  # Weigths
  e.1 <- eof_mode * wght

  ano <- ano * InsertDim(wght, 3, dim_time)
  na <- apply(ano, 3, mean, na.rm = TRUE)  # if [lat, lon] all NA, it's NA
  tmp <- ano * InsertDim(e.1, 3, dim_time)  # [lat, lon, sdate]
  pc.ver <- apply(tmp, 3, sum, na.rm = TRUE) 
  pc.ver[which(is.na(na))] <- NA

  return(pc.ver)
}

