### Downscaling Module
source('modules/Downscaling/tmp/Interpolation.R')
source('modules/Downscaling/tmp/Intbc.R')
source('modules/Downscaling/tmp/Intlr.R')
source('modules/Downscaling/tmp/Analogs.R')
source('modules/Downscaling/tmp/LogisticReg.R')
source('modules/Downscaling/tmp/Utils.R')

Crossval_downscaling <- function(recipe, data) {
  # Function that downscale the hindcast using the method stated in the
  # recipe. For the moment, forecast must be null.
  #
  # data: list of s2dv_cube objects containing the hcst, obs and fcst.
  # recipe: object obtained when passing the .yml recipe file to read_yaml()
  
  type <- tolower(recipe$Analysis$Workflow$Downscaling$type)
#  fcst_only <- recipe$Analysis$Workflow$Downscaling$fcst_only

#  if (is.null(data$fcst) && is.null(data$fcstL)) {
    # In this case, fcst_only is set to TRUE because the forecast call will
    # return the hindcast, so that the hindcast is not computed twice.
#    fcst_only <- TRUE
#  } else if (!isTRUE(fcst_only)) {
#    fcst_only <- FALSE
#  }

  if (type == "none") {
    DOWNSCAL_MSG <- "##### METHOD SET TO 'none': NO DOWNSCALING PERFORMED #####"
  } else {
    # Downscaling function params
    int_method     <- tolower(recipe$Analysis$Workflow$Downscaling$int_method)
    bc_method      <- tolower(recipe$Analysis$Workflow$Downscaling$bc_method)
    lr_method      <- tolower(recipe$Analysis$Workflow$Downscaling$lr_method)
    log_reg_method <- tolower(recipe$Analysis$Workflow$Downscaling$log_reg_method)
    target_grid    <- recipe$Analysis$Workflow$Downscaling$target_grid
    nanalogs       <- as.numeric(recipe$Analysis$Workflow$Downscaling$nanalogs)
    size           <- recipe$Analysis$Workflow$Downscaling$size   
    categories <- recipe$Analysis$Workflow$Probabilities$percentiles
    categories <- lapply(categories, function (x) { 
                         sapply(x, function(y) {
                                eval(parse(text = y))})})
    na.rm <- recipe$Analysis$remove_NAs 

    if (tolower(recipe$Analysis$Horizon) == "seasonal") {
      archive <- read_yaml("conf/archive_seasonal.yml")
    } else if (tolower(recipe$Analysis$Horizon) == "subseasonal") {
      archive <- read_yaml("conf/archive_subseasonal.yml")
    } else if (tolower(recipe$Analysis$Horizon) == "decadal") {
      archive <- read_yaml("conf/archive_decadal.yml")
    }
    system_info <- archive[[recipe$Run$filesystem]]$System[[recipe$Analysis$Datasets$System$name]]
    
    if (is.null(recipe$Analysis$ncores)) {
      ncores <- 1
    } else {
      ncores <- recipe$Analysis$ncores
    }
 
#    loocv_hcst <- TRUE # loocv for the hcst dwn, in case the fcst dwn is also requested.
#    if (!is.null(data$fcst) | (!is.null(data$fcstL) & type == "analogs")) {
#      loocv <- FALSE
#    } else  {
#      loocv <- TRUE	
#    }

    DOWNSCAL_TYPES <- c("none", "intbc", "intlr", "analogs", "logreg")
    BC_METHODS <- c("quantile_mapping", "bias", "evmos", "mse_min", "crps_min", "rpc-based", "qm")
    LR_METHODS <- c("basic", "large-scale", "9nn")
    LOG_REG_METHODS <- c("ens_mean", "ens_mean_sd", "sorted_members")

    if (!(type %in% DOWNSCAL_TYPES)) {
      stop("Downscaling type in the recipe is not available. Accepted types ",
           "are 'none', 'intbc', 'intlr', 'analogs', 'logreg'.")
    } 

    # Instead of interpolating inside the functions every time, interpolate once outside the loop.
    if (type %in% setdiff(DOWNSCAL_TYPES, c("none", "analogs")) & 
        (!length(lr_method) || lr_method != "9nn")) {
      region <- numeric()      
      region[1] <- data$obs$coords$longitude[1]
      region[2] <- data$obs$coords$longitude[length(data$obs$coords$longitude)]
      region[3] <- data$obs$coords$latitude[1]
      region[4] <- data$obs$coords$latitude[length(data$obs$coords$latitude)]

      data$hcst <- CST_Interpolation(exp = data$hcst,
                                     points = NULL,
                                     method_remap = int_method,
                                     target_grid = target_grid,
                                     lat_dim = "latitude",
                                     lon_dim = "longitude",
                                     region = region,
                                     method_point_interp = NULL)$exp

#     exp_cor_interpolated <- NULL
      if (!is.null(data$fcst)) {
        data$fcst <- CST_Interpolation(exp = data$fcst,
                                       points = NULL,
                                       method_remap = int_method,
                                       target_grid = target_grid,
                                       lat_dim = "latitude",
                                       lon_dim = "longitude",
                                       region = region,
                                       method_point_interp = NULL)$exp
      }

      # If after interpolating 'exp' data the coordinates do not match, 
      # the obs data is interpolated to
      # the same grid to force the matching
      if ((!.check_coords(lat1 = data$hcst$coords$latitude, lat2 = data$obs$coords$latitude,
                          lon1 = data$hcst$coords$longitude, lon2 = data$obs$coords$longitude))  
                          ) { # | !(point_obs)) 
        data$obs <- CST_Interpolation(exp = data$obs,
                                      points = NULL,
                                      method_remap = int_method,
                                      target_grid = target_grid,
                                      lat_dim = "latitude",
                                      lon_dim = "longitude",
                                      region = region,
                                      method_point_interp = NULL)$exp
      }
    } else if (type == "analogs") {
      exp_cor <- NULL
      if (!is.null(data$hcstL)) {
        obs_train <- data$obsL
        exp <- data$hcstL
        exp_lats <- data$hcstL$coords$latitude
        exp_lons <- data$hcstL$coords$longitude
        region <- numeric()
        region[1] <- data$hcstL$coords$longitude[1]
        region[2] <- data$hcstL$coords$longitude[length(data$hcstL$coords$longitude)]
        region[3] <- data$hcstL$coords$latitude[1]
        region[4] <- data$hcstL$coords$latitude[length(data$hcstL$coords$latitude)]
        if (!is.null(system_info)) {
          grid_exp <- system_info$reference_grid
        } else {
          grid_exp <- data$hcstL$attrs$source_files[which(!is.na(data$hcstL$attrs$source_files))[1]]
        }
        if (!is.null(data$fcstL)) {
          exp_cor <- data$fcstL
        } else {
          if (!is.null(data$fcst)) { 
            stop("data$hcstL and data$fcst are provided, but data$fcstL is not.", 
                 "Please provide large-scale forecast in data$fcstL.")
          }
        }
      } else {
        obs_train <- data$obs
        exp <- data$hcst
        exp_lats <- data$hcst$coords$latitude
        exp_lons <- data$hcst$coords$longitude
        region <- numeric()
        region[1] <- data$hcst$coords$longitude[1]
        region[2] <- data$hcst$coords$longitude[length(data$hcst$coords$longitude)]
        region[3] <- data$hcst$coords$latitude[1]
        region[4] <- data$hcst$coords$latitude[length(data$hcst$coords$latitude)]
        if (!is.null(system_info)) {
          grid_exp <- system_info$reference_grid
        } else {
          grid_exp <- data$hcst$attrs$source_files[which(!is.na(data$hcst$attrs$source_files))[1]]
        }
        if (!is.null(data$fcst)) {
          exp_cor <- data$fcst
        }
      }

      data$obsL <- CST_Interpolation(exp = obs_train,
                                     points = NULL,
                                     method_remap = "conservative",
                                     target_grid = grid_exp,
                                     lat_dim = "latitude",
                                     lon_dim = "longitude",
                                     region = region,
                                     method_point_interp = NULL)$exp

      if (!.check_coords(lat1 = data$obsL$coords$latitude,
                         lat2 = exp_lats,
                         lon1 = data$obsL$coords$longitude,
                         lon2 = exp_lons)) {
        exp_interpolated <- CST_Interpolation(exp = exp,
                                              points = NULL,
                                              method_remap = "conservative",
                                              target_grid = grid_exp,
                                              lat_dim = "latitude",
                                              lon_dim = "longitude",
                                              region = region,
                                              method_point_interp = NULL)$exp
        
        if (!is.null(exp_cor)) {
          exp_cor_interpolated <- CST_Interpolation(exp = exp_cor,
                                                    points = NULL,
                                                    method_remap = "conservative",
                                                    target_grid = grid_exp,
                                                    lat_dim = "latitude",
                                                    lon_dim = "longitude",
                                                    region = region,
                                                    method_point_interp = NULL)$exp
        }

      } else {
        exp_interpolated <- exp
        exp_cor_interpolated <- exp_cor
      }

      if (!is.null(data$hcstL)) {
        data$hcstL <- exp_interpolated
        data$fcstL <- exp_cor_interpolated
      } else {
        data$hcst <- exp_interpolated
        data$fcst <- exp_cor_interpolated
      }
    }

    # Starting of FCV
    y <- length(categories)
    cross <- 1:dim(data$hcst$data)[["syear"]]
    downscal_ev_list <- NULL
    #downscal_tr_list <- NULL
    lims_obs_tr_list <- NULL
    if (type != "logreg") {
      lims_hcst_tr_list <- NULL
    }

    for (t in 1:length(cross)) {
      # training
      obs_tr <- CST_Subset(data$obs, along = 'syear',
     		           indices = cross[-t])

      hcst_tr <- CST_Subset(data$hcst, along = 'syear', 
    		            indices = cross[-t])
      #evaluation
      obs_ev <- CST_Subset(data$obs, along = 'syear',
                           indices = cross[t])

      hcst_ev <- CST_Subset(data$hcst, along = 'syear',
                            indices = cross[t])
      
      if (type == "analogs") {
        # training
        obsL_tr <- CST_Subset(data$obsL, along = 'syear',
                              indices = cross[-t])
        #evaluation
        obsL_ev <- CST_Subset(data$obsL, along = 'syear',
                              indices = cross[t])

        if (!is.null(data$hcstL)) { 
          # training
          hcstL_tr <- CST_Subset(data$hcstL, along = 'syear',
                                 indices = cross[-t])

          #evaluation
          hcstL_ev <- CST_Subset(data$hcstL, along = 'syear',
                                 indices = cross[t])

        }
      } 

      if (!is.null(data$pred) & type == "intlr") {
        # training
        hcstP_tr <- CST_Subset(data$pred, along = 'syear',
                               indices = cross[-t])

        #evaluation
        hcst_ev <- CST_Subset(data$pred, along = 'syear',
                               indices = cross[t])

      }

      # ANOMALY CALCULATION (anomaly calculation is after interpolation)
      if (recipe$Analysis$Workflow$Anomalies$compute) {
        clim_obs_tr <- MeanDims(obs_tr$data, 'syear')
        clim_hcst_tr <- MeanDims(hcst_tr$data, c('syear', 'ensemble'))
        obs_tr$data <- s2dv::Ano(obs_tr$data, clim_obs_tr)
        hcst_tr$data <- s2dv::Ano(hcst_tr$data, clim_hcst_tr)
        hcst_ev$data <- s2dv::Ano(hcst_ev$data, clim_hcst_tr)
        obs_ev$data <- s2dv::Ano(obs_ev$data, clim_obs_tr)

        if (type == "analogs" & !is.null(data$hcstL)) {
          clim_obsL_tr <- MeanDims(obsL_tr$data, 'syear')
          clim_hcstL_tr <- MeanDims(hcstL_tr$data, c('syear', 'ensemble'))
          obsL_tr$data <- s2dv::Ano(obsL_tr$data, clim_obsL_tr)
          hcstL_tr$data <- s2dv::Ano(hcstL_tr$data, clim_hcstL_tr)
          hcstL_ev$data <- s2dv::Ano(hcstL_ev$data, clim_hcstL_tr)
          obsL_ev$data <- s2dv::Ano(obsL_ev$data, clim_obsL_tr)
        }

        if (!is.null(data$pred) & type == "intlr") {
          clim_hcstP_tr <- MeanDims(hcstP_tr$data, c('syear', 'ensemble'))
          hcstP_tr$data <- s2dv::Ano(hcstP_tr$data, clim_hcstP_tr)
          hcstP_ev$data <- s2dv::Ano(hcstP_ev$data, clim_hcstP_tr)
        }
      }
     
      if (type == "intbc") {
        if (length(int_method) == 0) {
          stop("Please provide one (and only one) interpolation method in the recipe.")
        }

        if (is.null(bc_method)) {
          stop("Please provide one bias-correction method in the recipe. Accepted ",  
                "methods are 'quantile_mapping', 'bias', 'evmos', 'mse_min', 'crps_min' ",
                "'rpc-based', 'qm'. ")
        }

        if (is.null(target_grid)) {
          stop("Please provide the target grid in the recipe.")
        }

        if (!(bc_method %in% BC_METHODS)) {
          stop(paste0(bc_method, " method in the recipe is not available. Accepted methods ",
               "are 'quantile_mapping', 'bias', 'evmos', 'mse_min', 'crps_min' ",
               "'rpc-based', 'qm'."))
        }


        downscal_tr <- CST_Intbc(hcst_tr, obs_tr, 
                                 target_grid = target_grid,
                                 bc_method = bc_method,
                                 int_method = int_method,
                                 points = NULL,
                                 method_point_interp = NULL,
                                 lat_dim = "latitude",
                                 lon_dim = "longitude",
                                 sdate_dim = "syear",
                                 member_dim = "ensemble",
                                 region = NULL,
	                         loocv = FALSE,
                                 ncores = ncores)

        downscal_ev <- CST_Intbc(hcst_tr, obs_tr, hcst_ev, 
                                 target_grid = target_grid, 
                                 bc_method = bc_method, 
                                 int_method = int_method, 
                                 points = NULL,
                                 method_point_interp = NULL, 
                                 lat_dim = "latitude", 
                                 lon_dim = "longitude",
                                 sdate_dim = "syear", 
                                 member_dim = "ensemble", 
                                 region = NULL,
                                 loocv = FALSE, 
                                 ncores = ncores)
  
        DOWNSCAL_MSG <- "##### DOWNSCALING COMPLETE #####"

      } else if (type == "intlr") {
        if (length(int_method) == 0) {
          stop("Please provide one (and only one) interpolation method in the recipe.")
        }

        if (is.null(lr_method)) {
          stop("Please provide one linear regression method in the recipe. Accepted ", 
                "methods are 'basic', 'large-scale', '9nn'.")
        }

        if (is.null(target_grid)) {
          stop("Please provide the target grid in the recipe.")
        }

        if (!(lr_method %in% LR_METHODS)) {
          stop(paste0(lr_method, " method in the recipe is not available. Accepted methods ",
               "are 'basic', 'large-scale', '9nn'."))
        }

        # TO DO: add the possibility to have the element 'pred' in 'data'
        if (lr_method == "large-scale") {
          if (is.null(data$pred$data)) {
            stop("Please provide the large scale predictors in the element 'data$pred$data'.")
          }
        } else {
          hcstP_tr <- NULL
        }

        downscal_tr <- CST_Intlr(hcst_tr, obs_tr,
                                 lr_method = lr_method,
                                 target_grid = target_grid,
                                 points = NULL,
                                 int_method = int_method,
                                 method_point_interp = NULL,
                                 predictors = hcstP_tr,
                                 lat_dim = "latitude",
                                 lon_dim = "longitude",
                                 sdate_dim = "syear",
                                 time_dim = "time",
                                 member_dim = "ensemble",
                                 large_scale_predictor_dimname = 'vars',
                                 region = NULL,
                                 loocv = FALSE,
                                 ncores = ncores)

        downscal_ev <- CST_Intlr(hcst_tr, obs_tr, hcst_ev,
                                 lr_method = lr_method,
                                 target_grid = target_grid,
                                 points = NULL,
                                 int_method = int_method,
                                 method_point_interp = NULL,
                                 predictors = hcstP_tr,
                                 lat_dim = "latitude",
                                 lon_dim = "longitude",
                                 sdate_dim = "syear",
                                 time_dim = "time",
                                 member_dim = "ensemble",
                                 large_scale_predictor_dimname = 'vars',
                                 region = NULL,
                                 loocv = FALSE,
                                 ncores = ncores)
        DOWNSCAL_MSG <- "##### DOWNSCALING COMPLETE #####"

      } else if (type == "analogs") {

        if (is.null(nanalogs)) {
          warning("The number of analogs for searching has not been provided in the ",
                  "recipe. Setting it to 3.")
          nanalogs <- 3
        }

        if (!is.null(size) & (recipe$Analysis$Variables$freq == "monthly_mean" | 
                              recipe$Analysis$Variables$freq == "weekly_mean")) {
          size <- NULL
          warning("Size is set to NULL. ",
                  "It must be NULL for the weekly or monthly input data.")
        }

        if (!is.null(size)) {
          ## keep obs data before the window process to provide it later as the  output
          dum <- obsL_tr$data
            obsL_tr$data <- .generate_window(obsL_tr$data,
                                             sdate_dim = 'syear',
                                             time_dim = 'time',
                                             loocv = FALSE,
                                             size = size,
                                             ncores = ncores)
        }

        ## Priority: hcstL, hcst
        exp_ev <- hcst_ev
        exp_tr <- hcst_tr
        if (!is.null(data$hcstL)) {
          exp_ev <- hcstL_ev
          exp_tr <- hcstL_tr
#          print("Provided large scale variable is used as the predictor.")
        }

        downscal_tr <- CST_Analogs(exp_tr, obs_tr,
                                   grid_exp = grid_exp,
                                   obsL = obsL_tr,
                                   nanalogs = nanalogs,
                                   fun_analog = "wmean",
                                   lat_dim = "latitude",
                                   lon_dim = "longitude",
                                   sdate_dim = "syear",
                                   time_dim = "time",
                                   member_dim = "ensemble",
                                   region = NULL,
                                   return_indices = FALSE,
                                   loocv_window = FALSE,
                                   metric = "dist",
                                   ncores = ncores)

        downscal_ev <- CST_Analogs(exp_ev, obs_tr, 
                                   grid_exp = grid_exp,
                                   obsL = obsL_tr,
                                   nanalogs = nanalogs, 
                                   fun_analog = "wmean", 
                                   lat_dim = "latitude", 
                                   lon_dim = "longitude",
                                   sdate_dim = "syear", 
                                   time_dim = "time", 
                                   member_dim = "ensemble",
                                   region = NULL, 
                                   return_indices = FALSE,
                                   loocv_window = FALSE,
	                           metric = "dist", 
                                   ncores = ncores)

        if (!is.null(size)) {
          obs_tr$data <- Apply(dum, target_dims=c("time", "smonth"),
                               function (x) {x[1:(dim(data$hcst$data)["time"]), 2]},
                               ncores = ncores,
                               output_dims = "time")$output1 ## 2nd month is the target month 
        }

        DOWNSCAL_MSG <- "##### DOWNSCALING COMPLETE #####"
      } else if (type == "logreg") {

        if (length(int_method) == 0) {
          stop("Please provide one (and only one) interpolation method in the recipe.")
        }
 
        if (is.null(log_reg_method)) {
          stop("Please provide one logistic regression method in the recipe. Accepted ", 
                "methods are 'ens_mean', 'ens_mean_sd', 'sorted_members'.")
        }

        if (is.null(target_grid)) {
          stop("Please provide the target grid in the recipe.")
        }

        # Since we are forcing to create three categories, and applying cross-validation,
        # we need at least six years of data for the logistic regression function to not 
        # crash
        if (dim(data$hcst$data)[names(dim(data$hcst$data)) == "syear"] <= 5) {
          stop("The number of start dates is insufficient for the logisitic regression method. ",
               "Please provide six or more.") 
        }
 
        if (!(log_reg_method %in% LOG_REG_METHODS)) {
          stop(paste0(log_reg_method, " method in the recipe is not available. Accepted methods ",
               "are 'ens_mean', 'ens_mean_sd', 'sorted_members'."))
        }

        downscal_ev <- list()
        for (ps in 1:y) { 
          downscal_ev[[ps]] <- CST_LogisticReg(hcst_tr, obs_tr, hcst_ev, 
                                               target_grid = target_grid, 
                                               int_method = int_method, 
                                               log_reg_method = log_reg_method,
                                               probs_cat = categories[[ps]],  
                                               return_most_likely_cat = FALSE, 
                                               points = NULL,
                                               method_point_interp = NULL, 
                                               lat_dim = "latitude", 
                                               lon_dim = "longitude", 
                                               sdate_dim = "syear",
                                               time_dim = "time",
                                               member_dim = "ensemble", 
                                               region = NULL,
                                               loocv = FALSE, 
                                               ncores = ncores)
        }
        DOWNSCAL_MSG <- "##### DOWNSCALING COMPLETE #####"
      }

      # obs limits
      if (type != "logreg") {
        obs_tr_lim <- downscal_ev$obs$data # downscal_ev$obs$data = downscal_tr$obs$data
      } else {
        obs_tr_lim <- downscal_ev[[1]]$obs$data
      }

      if(tolower(recipe$Analysis$Horizon) == "seasonal" &
        (recipe$Analysis$Variables$freq == "daily_mean" |
         recipe$Analysis$Variables$freq == "daily")) {
        obs_tr_lim <- CSIndicators::PeriodMean(obs_tr_lim, time_dim = "time")
      }

      # Not compatible with obs with multiple ensemble members
      if ("ensemble" %in% names(dim(obs_tr_lim))) {
        obs_tr_lim <- ClimProjDiags::Subset(x = obs_tr_lim, along = "ensemble",
                                            indices = 1, drop = 'selected')
      }

      lims_obs_tr_dum <- Apply(obs_tr_lim, target_dims = c('syear'),
                               fun = function(x, prob_lims) {
                                 lapply(prob_lims, function(ps) {
                                   quantile(as.vector(x), ps, na.rm = na.rm)})},
                               output_dims = lapply(categories,
                                                    function(x){'bin'}),
                               prob_lims = categories,
                               ncores = ncores)
      lims_obs_tr_list[[t]] <- lims_obs_tr_dum

      # hcst limits
      if (type != "logreg") {
        hcst_tr_lim <- downscal_tr$exp$data
        # limits must be specified with monthly means for the seasonal case
        # todo modify following if condition wrt subseasonal
        if(tolower(recipe$Analysis$Horizon) == "seasonal" &
          (recipe$Analysis$Variables$freq == "daily_mean" |
           recipe$Analysis$Variables$freq == "daily")) {
          hcst_tr_lim <- CSIndicators::PeriodMean(hcst_tr_lim, time_dim = "time")
        }

        lims_hcst_tr_dum <- Apply(hcst_tr_lim, target_dims = c('syear', 'ensemble'),
                                  fun = function(x, prob_lims) {
                                    lapply(prob_lims, function(ps) {
                                      quantile(as.vector(x), ps, na.rm = na.rm)})},
                                  output_dims = lapply(categories,
                                                    function(x) {'bin'}), 
                                  prob_lims = categories,
                                  ncores = ncores)

        #downscal_tr_list[[t]] <- downscal_tr
        lims_hcst_tr_list[[t]] <- lims_hcst_tr_dum
      } 
      downscal_ev_list[[t]] <- downscal_ev
      suppressWarnings(rm(obs_tr, obs_ev, obsL_tr, obsL_ev, 
                          hcst_tr, hcst_ev, hcstL_tr, hcstL_ev, hcstP_tr, hcstP_ev,
                          clim_obs, clim_obsL, clim_hcst, clim_hcstL, clim_hcstP))
    }
  } 
  ## END OF DOWNSCALING
  print(DOWNSCAL_MSG)

  # Make categories rounded number to use as names:
  categories <- recipe$Analysis$Workflow$Probabilities$percentiles
  categories <- lapply(categories, function (x) {
                       sapply(x, function(y) {
                              round(eval(parse(text = y)),2)})})
#  recipe$Run$output_dir <- paste0(recipe$Run$output_dir,
#                                  "/outputs/Calibration/")

  ## FORECAST DOWNSCALING
  if (!is.null(data$fcst) | !is.null(data$fcstL)) {
    print ("##### STARTING TO DOWNSCALE THE FORECAST DATA #####")

    # ANOMALY CALCULATION
    if (recipe$Analysis$Workflow$Anomalies$compute) {
      data_dum <- data  # to store absolute values
      clim_obs <- MeanDims(data$obs$data, 'syear')
      clim_hcst <- MeanDims(data$hcst$data, c('syear', 'ensemble'))
      data$obs$data <- s2dv::Ano(data$obs$data, clim_obs)
      data$hcst$data <- s2dv::Ano(data$hcst$data, clim_hcst)

      if (!is.null(data$pred) & type == "intlr") {
        clim_hcstP <- MeanDims(data$pred$data, c('syear', 'ensemble'))
        data$pred$data <- s2dv::Ano(data$pred$data, clim_hcstP)
        data$fcst$data <- s2dv::Ano(data$fcst$data, clim_hcstP)
      } else {
        data$fcst$data <- s2dv::Ano(data$fcst$data, clim_hcst)
      }

      if (type == "analogs" & !is.null(data$hcstL)) {
        clim_obsL <- MeanDims(data$obsL$data, 'syear')
        clim_hcstL <- MeanDims(data$hcstL$data, c('syear', 'ensemble'))
        data$obsL$data <- s2dv::Ano(data$obsL$data, clim_obsL)
        data$hcstL$data <- s2dv::Ano(data$hcstL$data, clim_hcstL)
        data$fcstL$data <- s2dv::Ano(data = data$fcstL$data, clim = clim_hcstL)
      }
    }

    if (type == "intbc") {
      fcst <- CST_Intbc(data$hcst, data$obs, data$fcst,
                        target_grid = target_grid,
                        bc_method = bc_method,
                        int_method = int_method,
                        points = NULL,
                        method_point_interp = NULL,
                        lat_dim = "latitude",
                        lon_dim = "longitude",
                        sdate_dim = "syear",
                        member_dim = "ensemble",
                        region = NULL,
                        loocv = FALSE,
                        ncores = ncores)

      hcst_full <- CST_Intbc(data$hcst, data$obs,
                             target_grid = target_grid,
                             bc_method = bc_method,
                             int_method = int_method,
                             points = NULL,
                             method_point_interp = NULL,
                             lat_dim = "latitude",
                             lon_dim = "longitude",
                             sdate_dim = "syear",
                             member_dim = "ensemble",
                             region = NULL,
                             loocv = FALSE,
                             ncores = ncores)$exp
    } else if (type == "intlr") {
      fcst <- CST_Intlr(data$hcst, data$obs, data$fcst,
                        lr_method = lr_method,
                        target_grid = target_grid,
                        points = NULL,
                        int_method = int_method,
                        method_point_interp = NULL,
                        predictors = data$pred$data,
                        lat_dim = "latitude",
                        lon_dim = "longitude",
                        sdate_dim = "syear",
                        time_dim = "time",
                        member_dim = "ensemble",
                        large_scale_predictor_dimname = 'vars',
                        region = NULL,
                        loocv = FALSE,
                        ncores = ncores)

      hcst_full <- CST_Intlr(data$hcst, data$obs,
                             lr_method = lr_method,
                             target_grid = target_grid,
                             points = NULL,
                             int_method = int_method,
                             method_point_interp = NULL,
                             predictors = data$pred$data,
                             lat_dim = "latitude",
                             lon_dim = "longitude",
                             sdate_dim = "syear",
                             time_dim = "time",
                             member_dim = "ensemble",
                             large_scale_predictor_dimname = 'vars',
                             region = NULL,
                             loocv = FALSE,
                             ncores = ncores)$exp
    } else if (type == "analogs") {
      if (!is.null(size)) {
        ## keep obs data before the window process to provide it later as the  output
        data$obsL$data <- .generate_window(data$obsL$data,
                                           sdate_dim = 'syear',
                                           time_dim = 'time',
                                           loocv = FALSE,
                                           size = size,
                                           ncores = ncores)
      }

      ## Priority: fcstL, fcst
      exp <- data$fcst
      expH <- data$hcst
      if (!is.null(data$fcstL)) {
        exp <- data$fcstL
        expH <- data$hcstL
      }

      fcst <- CST_Analogs(exp, data$obs,
                          grid_exp = grid_exp,
                          obsL = data$obsL,
                          nanalogs = nanalogs,
                          fun_analog = "wmean",
                          lat_dim = "latitude",
                          lon_dim = "longitude",
                          sdate_dim = "syear",
                          time_dim = "time",
                          member_dim = "ensemble",
                          region = NULL,
                          return_indices = FALSE,
                          loocv_window = FALSE,
                          metric = "dist",
                          ncores = ncores)

      hcst_full <- CST_Analogs(expH, data$obs,
                               grid_exp = grid_exp,
                               obsL = data$obsL,
                               nanalogs = nanalogs,
                               fun_analog = "wmean",
                               lat_dim = "latitude",
                               lon_dim = "longitude",
                               sdate_dim = "syear",
                               time_dim = "time",
                               member_dim = "ensemble",
                               region = NULL,
                               return_indices = FALSE,
                               loocv_window = FALSE,
                               metric = "dist",
                               ncores = ncores)$exp
      if (!is.null(size)) {
        data$obs$data <- Apply(dum, target_dims=c("time", "smonth"),
                               function (x) {x[1:(dim(data$hcst$data)["time"]), 2]},
                               ncores = ncores,
                               output_dims = "time")$output1 ## 2nd month is the target month 
      }
    } else if (type == "logreg") {
      fcst <- list()
      for (ps in 1:y) {
        fcst[[ps]] <- CST_LogisticReg(data$hcst, data$obs, data$fcst,
                                      target_grid = target_grid,
                                      int_method = int_method,
                                      log_reg_method = log_reg_method,
                                      probs_cat = categories[[ps]],
                                      return_most_likely_cat = FALSE,
                                      points = NULL,
                                      method_point_interp = NULL,
                                      lat_dim = "latitude",
                                      lon_dim = "longitude",
                                      sdate_dim = "syear",
                                      time_dim = "time",
                                      member_dim = "ensemble",
                                      region = NULL,
                                      loocv = FALSE,
                                      ncores = ncores)
      }
    }

    # obs limits
      obs_full_lim <- data$obs$data 

    # limits must be specified with monthly means for the seasonal case
    # todo modify following if condition wrt subseasonal
    if(tolower(recipe$Analysis$Horizon) == "seasonal" &
      (recipe$Analysis$Variables$freq == "daily_mean" |
       recipe$Analysis$Variables$freq == "daily")) {
      obs_full_lim <- CSIndicators::PeriodMean(obs_full_lim, time_dim = "time")
    }

    # Not compatible with obs with multiple ensemble members
    if ("ensemble" %in% names(dim(obs_full_lim))) {
      obs_full_lim <- ClimProjDiags::Subset(x = obs_full_lim, along = "ensemble",
                                            indices = 1, drop = 'selected')
    }

    lims <- Apply(obs_full_lim, target_dims = c('syear'),
                  fun = function(x, prob_lims) {
                    lapply(prob_lims, function(ps) {
                      quantile(as.vector(x), ps, na.rm = na.rm)})},
                  output_dims = lapply(categories, function(x) {'bin'}),
                  prob_lims = categories,
                  ncores = ncores)

    rm (obs_full_lim)
    # fcst lims
    if (type != "logreg") {
      fcst_lim <- hcst_full$data
      # limits must be specified with monthly means for the seasonal case
      # todo modify following if condition wrt subseasonal
      if(tolower(recipe$Analysis$Horizon) == "seasonal" &
        (recipe$Analysis$Variables$freq == "daily_mean" |
         recipe$Analysis$Variables$freq == "daily")) {
        fcst_lim <- CSIndicators::PeriodMean(fcst_lim, time_dim = "time")
      }

      # Terciles limits using the whole hindcast period:
      lims_fcst <- Apply(fcst_lim, target_dims = c('syear', 'ensemble'),
                         fun = function(x, prob_lims) {
                           lapply(prob_lims, function(ps) {
                             quantile(as.vector(x), ps, na.rm = na.rm)})},
                         output_dims = lapply(categories, function(x) {'bin'}),
                         prob_lims = categories,
                         ncores = ncores)
    }

    if (recipe$Analysis$Workflow$Anomalies$compute) {
      data <- data_dum # store absolute values
      suppressWarnings(rm(clim_obs, clim_obsL, clim_hcst, clim_hcstL, clim_hcstP))
    }
    print ("##### FORECAST DOWNSCALING COMPLETE #####")
  } else {
    fcst <- NULL
    fcst_probs <- NULL
  }
  ## End of forecast downscaling

  lims_obs_tr <- lapply(1:y, function(i) {
                   combined <-  abind(lapply(lims_obs_tr_list,
                                        function(x) x[[i]]),
                                      along = length(dim(lims_obs_tr_list[[1]][[i]])) + 1 )
                   names(dim(combined)) <- c(names(dim(lims_obs_tr_list[[1]][[1]])), "syear")
                   return(combined)
                 })
  #rm (lims_obs_tr_list)

  if (type != "logreg") {
    downscal_ev_arr <- abind(lapply(downscal_ev_list, 
                               function(x) x$exp$data),
                             along = which(names(dim(downscal_ev_list[[1]]$exp$data)) == "syear"))
  
    names(dim(downscal_ev_arr)) <- names(dim(downscal_ev_list[[1]]$exp$data))
    
    lims_hcst_tr <- lapply(1:y, function(i) { 
                      combined <-  abind(lapply(lims_hcst_tr_list,
                                           function(x) x[[i]]),
                                         along = length(dim(lims_hcst_tr_list[[1]][[i]])) + 1 )
                      names(dim(combined)) <- c(names(dim(lims_hcst_tr_list[[1]][[1]])), "syear")
                      return(combined)
                    })

    #rm (lims_hcst_tr_list)
  }

  # Compute Probabilities
  hcst_probs_ev <- obs_probs_ev <- hcst_probs_org <- NULL # hcst_probs_org is only for logreg 
  tmp1 <- data$obs$data
  if (type !="logreg") { 
    tmp2 <- downscal_ev_arr
  }
  if (!is.null(data$fcst)) {
    fcst_probs <- list()
    if (type != "logreg") {
      tmp3 <- fcst$exp$data
    } else {
      tmp3 <- list()
      for (ps in 1:y) {
        tmp3[[ps]] <- fcst[[ps]]$exp$data
      }
    }
  }
  # limits must be specified with monthly means for the seasonal case
  # todo modify following if condition wrt subseasonal
  if(tolower(recipe$Analysis$Horizon) == "seasonal" &
    (recipe$Analysis$Variables$freq == "daily_mean" |
     recipe$Analysis$Variables$freq == "daily")) {
    tmp1 <- CSIndicators::PeriodMean(tmp1, time_dim = "time")
    if (type !="logreg") {
      tmp2 <- CSIndicators::PeriodMean(tmp2, time_dim = "time")
    }
    if (!is.null(data$fcst)) {
      if (type !="logreg") {
        tmp3 <- CSIndicators::PeriodMean(tmp3, time_dim = "time")
      } else {
        for (ps in 1:y) {
          tmp3[[ps]] <- CSIndicators::PeriodMean(tmp3[[ps]], time_dim = "time")
        }
      }
    }
  }

  source('modules/Crossval/R/tmp/GetProbs.R')
  for (ps in 1:y) {

    obs_probs_ev[[ps]] <- GetProbs(tmp1, time_dim = 'syear',
                                   prob_thresholds = NULL,
                                   bin_dim_abs = 'bin',
                                   indices_for_quantiles = NULL,
                                   memb_dim = 'ensemble',
                                   abs_thresholds = lims_obs_tr[[ps]],
                                   ncores = ncores)
    if (type !="logreg") {
      hcst_probs_ev[[ps]] <- GetProbs(tmp2, time_dim = 'syear',
                                      prob_thresholds = NULL,
                                      bin_dim_abs = 'bin',
                                      indices_for_quantiles = NULL,
                                      memb_dim = 'ensemble',
                                      abs_thresholds = lims_hcst_tr[[ps]],
                                      ncores = ncores)
      if (!is.null(data$fcst)) {
        fcst_probs[[ps]] <- GetProbs(tmp3, time_dim = 'syear',
                                     prob_thresholds = NULL,
                                     bin_dim_abs = 'bin',
                                     indices_for_quantiles = NULL,
                                     memb_dim = 'ensemble',
                                     abs_thresholds = lims_fcst[[ps]],
                                     ncores = ncores)
      }
    } else {
      hcst_probs_ev[[ps]] <- abind(lapply(1:length(cross), 
                                     function(x) downscal_ev_list[[x]][[ps]]$exp$data), 
                               along = which(names(downscal_ev_list[[1]][[1]]$exp$dims) == "syear"))
      names(dim(hcst_probs_ev[[ps]])) <- names (downscal_ev_list[[1]][[1]]$exp$dims)
      names(dim(hcst_probs_ev[[ps]]))[names(dim(hcst_probs_ev[[ps]])) == "category" ] <- "bin"
      hcst_probs_org[[ps]] <- hcst_probs_ev[[ps]] 
      if(tolower(recipe$Analysis$Horizon) == "seasonal" &
        (recipe$Analysis$Variables$freq == "daily_mean" |
         recipe$Analysis$Variables$freq == "daily")) {
         hcst_probs_ev[[ps]] <- CSIndicators::PeriodMean(hcst_probs_ev[[ps]], time_dim = "time") 
      } 
      if (!is.null(data$fcst)) {
        fcst_probs[[ps]] <- tmp3[[ps]]
        names(dim(fcst_probs[[ps]]))[names(dim(fcst_probs[[ps]])) == "category" ] <- "bin"
      }
    }
  } 

  suppressWarnings(rm(tmp1, tmp2, tmp3))

  # downscaled data -> s2dv_cube
  if (type !="logreg") {
    hcst <- data$hcst
    hcst$data <- downscal_ev_arr
    hcst$dims <- dim(downscal_ev_arr)
    hcst$coords <- downscal_ev_list[[1]]$exp$coords
    hcst$coords$syear[cross] <- cross
    hcst$attrs <- downscal_ev_list[[1]]$exp$attrs
    hcst$Dates <- data$hcst$attrs$Dates
    hcst$attrs$Variable$metadata$time <- data$hcst$attrs$Variable$metadata$time
  } else {
    hcst <- list()
    for (ps in 1:y) {
      hcst[[ps]] <- data$hcst
      hcst[[ps]]$data <- hcst_probs_org[[ps]]
      hcst[[ps]]$dims <- dim(hcst[[ps]]$data)
      hcst[[ps]]$coords <- downscal_ev_list[[1]][[ps]]$exp$coords
      hcst[[ps]]$coords$syear[cross] <- cross
      hcst[[ps]]$attrs <- downscal_ev_list[[1]][[ps]]$exp$attrs
      hcst[[ps]]$Dates <- data$hcst$attrs$Dates
      hcst[[ps]]$attrs$Variable$metadata$time <- data$hcst$attrs$Variable$metadata$time
    }
  }

  if (type != "logreg") {
    fcst_new <- fcst$exp
  } else {
    fcst_new <- list()
    for (ps in 1:y) {
      fcst_new[[ps]] <- fcst[[ps]]$exp
    }
  }

  # Save probability bins
  probs_hcst <- list()
  probs_fcst <- list()
  probs_obs <- list()
  all_names <- NULL

  for (ps in 1:y) {
    for (perc in 1:(length(categories[[ps]]) + 1)) {
      if (perc == 1) {
        name_elem <- paste0("below_", categories[[ps]][perc])
      } else if (perc == length(categories[[ps]]) + 1) {
        name_elem <- paste0("above_", categories[[ps]][perc-1])
      } else {
        name_elem <- paste0("from_", categories[[ps]][perc-1], 
                            "_to_", categories[[ps]][perc])
      }
      probs_hcst <- append(list(Subset(hcst_probs_ev[[ps]],
                                  along = 'bin', indices = perc, drop = 'all')),
                           probs_hcst)
      probs_obs <- append(list(Subset(obs_probs_ev[[ps]],
                                 along = 'bin', indices = perc, drop = 'all')),
                          probs_obs)
      if (!is.null(data$fcst)) {
        probs_fcst <- append(list(Subset(fcst_probs[[ps]],
                                    along = 'bin', indices = perc, drop = 'all')),
                             probs_fcst)
      }
      all_names <- c(all_names, name_elem)
    }
  }

  names(probs_hcst) <- all_names
  if (!('var' %in% names(dim(probs_hcst[[1]])))) {
    probs_hcst <- lapply(probs_hcst, function(x) {
                     dim(x) <- c(var = 1, dim(x))
                     return(x)})
  }
  names(probs_obs) <- all_names
  if (!('var' %in% names(dim(probs_obs[[1]])))) {
    probs_obs <- lapply(probs_obs, function(x) {
                     dim(x) <- c(var = 1, dim(x))
                     return(x)})
  }

  if (!is.null(data$fcst)) {
    names(probs_fcst) <- all_names
    if (!('syear' %in% names(dim(probs_fcst[[1]])))) {
      probs_fcst <- lapply(probs_fcst, function(x) {
                     dim(x) <- c(syear = 1, dim(x))
                     return(x)})
    }
    if (!('var' %in% names(dim(probs_fcst[[1]])))) {
      probs_fcst <- lapply(probs_fcst, function(x) {
                     dim(x) <- c(var = 1, dim(x))
                     return(x)})
    }
  }


  # Saving
#  if (recipe$Analysis$Workflow$Downscaling$save != 'none') {
#    info(recipe$Run$logger, "##### START SAVING DOWNSCALED DATA #####")
#  }
#  recipe$Run$output_dir <- paste0(recipe$Run$output_dir, 
#                                  "/outputs/Downscaling/")
#  if ((recipe$Analysis$Workflow$Downscaling$save %in% 
#       c('all', 'exp_only', 'fcst_only')) && (!is.null(data$fcst))) {
#    save_forecast(recipe = recipe, data_cube = data$fcst, type = 'fcst')
#  }
#  if ((recipe$Analysis$Workflow$Downscaling$save %in%
#       c('all', 'exp_only')) && (!is.null(data$hcst))) {
#    save_forecast(recipe = recipe, data_cube = data$hcst, type = 'hcst')
#  }
#  if (recipe$Analysis$Workflow$Downscaling$save == 'all') {
#    save_observations(recipe = recipe, data_cube = data$obs)
#  }

  return(list(hcst = hcst, obs = data$obs, fcst = fcst_new, 
              hcst.full_val = data$hcst, obs.full_val = data$obs, 
              probs = list(hcst_ev = hcst_probs_ev,
                           obs_ev = obs_probs_ev,
			   fcst = fcst_probs),
              ref_obs_tr = data$obs$data))  # ?
}


