
SpatialDoF <- function(data, lon_dim = 'longitude', lat_dim = 'latitude',
                      time_dim = 'syear', standardize = TRUE,
                      num_simulations = 1000, significance_level = 0.05,
                      confidence_level = 0.05, N = 1000, ncores = NULL){
  library(CSTools)
  
  ## Checks

  ## Merge lon and lat into point
  data <- CST_MergeDims(data, merge_dims = c(lat_dim, lon_dim), rename = "points")

  ## Parallel running
  res <- Apply(data = data$data, 
               target_dims = c(time_dim, 'points'),
               fun = .SpatialDoF,
               standardize = standardize,
               num_simulations = num_simulations,
               significance_level = significance_level,
               confidence_level = confidence_level,
               N = N, ncores = ncores)$output1

  return(res)
}

.SpatialDoF <- function(data, spatial_dims = 'points',
                       time_dim = 'syear', standardize = TRUE,
                       num_simulations = 1000, significance_level = 0.05,
                       confidence_level = 0.05, N = 1000, ncores = NULL) {
  # Check if data is 'time', 'points' matrix
  # ...
  # standardize?
  if (standardize) {
    data <- Apply(list(data), target_dims = time_dim,
                  fun = function(x) {
                    (x - mean(x))/sd(x)}, ncores = ncores)$output1
  }
  # Create Figure 3
  success <- NULL
  if (N < 1000) { N <- 1000}
  if (N == 1000) {
    N_test <- c(10, seq(20,50, 10), seq(100,500,100), 1000)
  } else {
    N_test <- c(10, seq(20,50, 10), seq(100,500,100), 1000, N)
  }
  # N_test <- 1:1000 
  for (N in N_test) {
    store <- NULL
    for (n in 0:N) {
      m <- pbinom(n - 1, size = N, prob = significance_level,
                  lower.tail = FALSE)
      store <- c(store, m)
    }
    # Interpolation to PB = 0.05
    # Which are the m values closed to the PB
    positions <- which(abs(store-confidence_level) %in%
                         sort(abs(store-confidence_level),
                              partial=1:2)[1:2]) - 1
    emes <- store[positions + 1]
    
    passed <- (confidence_level - lm(emes ~ positions)$coefficients[1])/
      lm(emes ~ positions)$coefficients[2]
    success <- c(success, passed/N*100)
  }
  ##############################################3
  # POWER LAW is the model that best fits
  sample <- data.frame(N = N_test, success = success)
  
  # Power Law Model
  power_model <- nls(success ~ a * N^(-b) + c, data = sample,
                     start = list(a = 25, b = 0.5, c = 5))
  success <- predict(power_model, newdata = data.frame(N = 1:N))
  
  
  # Monte Carlo
  num_points <- dim(data)['points']
  significant_counts <- Apply(
    data = list(array(1:num_simulations, c(simul = num_simulations)),
                data),
    fun = function(x,y, signif) {
      simulate_significant_correlations(x,  data_matrix = y, signif)},
    target_dims = list(NULL, c(time_dim, spatial_dims)), 
    signif = significance_level,
    ncores = ncores)$output1
  
  significant_ratios <- significant_counts/num_points
  sorted_ratios <- sort(significant_ratios, decreasing = FALSE)
  cumulative_probabilities <- ecdf(sorted_ratios)
  m0_ratio <- sorted_ratios[cumulative_probabilities(sorted_ratios) >= 
                              (1 - confidence_level)][1]
  solution <- which(sort(abs((success - m0_ratio*100)))[1] == 
                      abs(success - m0_ratio*100))
  return(solution)
}
# Function to perform one Monte Carlo simulation
simulate_significant_correlations <- function(sim, data_matrix,
                                              signif = 0.05) {
  # data_matrix [time, points]
  T <- dim(data_matrix)[1]
  # Generate a random time series Rt with same length as original data (T)
  Rt <- rnorm(T)
  # Calculate significance of correlation for each spatial point
  significant_count <- sum(apply(data_matrix, 2, function(x) {
    # Perform the correlation test
    test_result <- cor.test(Rt, x)
    # Check if the p-value is less than the significance level
    test_result$p.value < signif
  }))
  
  return(significant_count)
}

