#' @rdname RegimesAssign
#' @title Function for matching a field of anomalies with 
#' a set of maps used as a reference (e.g. clusters obtained from the WeatherRegime function).
#'
#' @author Verónica Torralba - BSC, \email{veronica.torralba@bsc.es}
#'
#' @description This function performs the matching between a field of anomalies and a set
#' of maps which will be used as a reference. The anomalies will be assigned to the reference map 
#' for which the minimum Eucledian distance (method=’distance’) or highest spatial correlation 
#' (method=‘ACC’) is obtained. 
#'
#'@references Torralba, V. (2019) Seasonal climate prediction for the wind energy sector: methods and tools 
#' for the development of a climate service. Thesis. Available online: \url{https://eprints.ucm.es/56841/}
#'
#'@param data an array containing anomalies with named dimensions: dataset, member, sdate, ftime, lat and lon.
#'@param ref.maps Array with 3-dimensions (lon,lat, k) containing the maps  k=1,..,n maps that will be used as a reference for the matching. 
#'@param lat Vector of latitudes.
#'@param method Whether the matching will be performed in terms of minimum distance (default=’distance’) or 
#' the maximum spatial correlation (method=’ACC’) between the maps.
#'@return A list with elements \code{$composite} (3-d array (lon, lat, k) containing the composites k=1,..,K for case (*1)
# or only k=1 for any specific cluster, i.e., case (*2)),
#'         \code{pvalue} (3-d array (lon,lat, k) containing the pvalue of the composites obtained through a t-test 
#'         that accounts for the serial dependence of the data with the same structure as Composite.),
#'         \code{cluster} (array with the same dimensions that data except latitude and longitude indicating the regime.maps to which each point is allocated.) ,
#'         \code{frequency} (A vector of integers (from k=1,...k n reference maps) indicating the percentage of assignations corresponding to each map.),
#'  
#'@import s2dverification
#'@import multiApply         
#'@examples 
#'@export

RegimesAssign <- function(data, ref.maps, lat, method = 'distance') {

  if (is.null(names(dim(ref.maps))) | is.null(names(dim(data)))) {
    stop(
      'The arrays should include dimensions names map.ref[lat,lon,cluster]
      and map.targ [lat,lon]'
    )
  }
  
  if (length(lat) != dim(ref.maps)['lat']) {
    stop('latitudes do not match with the maps')
  }

  if (any(names(dim(ref.maps))=='member')){
    names(dim(ref.maps)['member'])='member_obs'
  }
  
  if (any(names(dim(ref.maps))=='dataset')){
    names(dim(ref.maps)['dataset'])='dataset_obs'
  }
  print(dim(ref.maps))
  assign <-
    Apply( data = list(map.targ = data,map.ref=ref.maps),
      target_dims = list(c('lat','lon'),
                         c('lat','lon','cluster')),
      fun = '.anom2regime',
      lat = lat,
      method = method
    )[[1]]

  anom_array <-
    array(data, dim = c(prod(dim(data)[-c(poslat_ano, poslon_ano)]), nlat, nlon))
  # to be replaced with MergeDims
 
  recon <- Composite(var = aperm(anom_array, c(3, 2, 1)), occ = assign)
  freqs <- rep(NA, nclust)
  for (n in 1:nclust) {
    freqs[n] <- (length(which(assign == n)) / length(assign)) * 100
  }
  output <-
    list(
      composite = recon$composite,
      pvalue = recon$pvalue,
      cluster = assign,
      frequency = freqs
    )
  return(output)
}




.anom2regime <- function(map.ref, map.targ, method = 'distance', lat) {
  posdim <- which(names(dim(map.ref)) == 'cluster')
  poslat <- which(names(dim(map.ref)) == 'lat')
  poslon <- which(names(dim(map.ref)) == 'lon')
  
  nclust <- dim(map.ref)['cluster']
  
  if (all(dim(map.ref)[-posdim] != dim(map.targ))) {
    stop('The map.targ should have the same dimensions [lat,lon] that
         the map.reference ')
  }
  
  if (is.null(names(dim(map.ref))) | is.null(names(dim(map.targ)))) {
    stop(
      'The arrays should include dimensions names map.ref[nclust,lat,lon]
      and map.targ [lat,lon]'
    )
  }
  
  if (length(lat) != dim(map.ref)['lat']) {
    stop('latitudes do not match with the maps')
  }
  
  # This dimensions are reorganized
  map.ref <- aperm(map.ref, c(posdim, poslat, poslon))
  map.targ <-
    aperm(map.targ, c(which(names(dim(
      map.targ
    )) == 'lat'), which(names(dim(
      map.targ
    )) == 'lon')))
  
  # weights are defined
  latWeights <- InsertDim(sqrt(cos(lat * pi / 180)), 2, dim(map.ref)['lon'])
  names(dim(latWeights))<-c('lat','lon')
  
  if (method == 'ACC') {
    corr <- rep(NA, nclust)
    for (i in 1:nclust) {
      corr[i] <-
        ACC(InsertDim(InsertDim(
          InsertDim(map.ref[i, , ] * latWeights, 1, 1), 2, 1
        ), 3, 1),
        InsertDim(InsertDim(
          InsertDim(map.targ * latWeights, 1, 1), 2, 1
        ), 3, 1))$ACC[2]
    }
    assign <- which(corr == max(corr))
  }
  
  if (method == 'distance') {
    rms <- rep(NA, nclust)
    for (i in 1:nclust) {
      rms[i] <- .rmsdiff(map.ref[i, , ] * latWeights, map.targ * latWeights)
    }
    assign <- which(rms == min(rms))
  }
  
  return(assign)
}
.rmsdiff <- function(x, y) {
  dims <- dim(x)
  ndims <- length(dims)
  if (ndims != 2 | ndims != length(dim(y))) {
    stop('x and y should be maps')
  }
  map_diff <- NA * x
  for (i in 1:dims[1]) {
    for (j in 1:dims[2]) {
      map_diff[i, j] <- (x[i, j] - y[i, j]) ^ 2
    }
  }
  rmsdiff <- sqrt(mean(map_diff))
  return(rmsdiff)
}
