## Function to tell if a regexpr() match is a complete match to a specified name
.IsFullMatch <- function(x, name) {
  ifelse(x > 0 && attributes(x)$match.length == nchar(name), TRUE, FALSE)
}

.ConfigReplaceVariablesInString <- function(string, replace_values, allow_undefined_key_vars = FALSE) {
  # This function replaces all the occurrences of a variable in a string by 
  # their corresponding string stored in the replace_values.
  if (length(strsplit(string, "\\$")[[1]]) > 1) {
    parts <- strsplit(string, "\\$")[[1]]
    output <- ""
    i <- 0
    for (part in parts) {
      if (i %% 2 == 0) {
        output <- paste(output, part, sep = "")
      } else {
        if (part %in% names(replace_values)) {
          output <- paste(output, .ConfigReplaceVariablesInString(replace_values[[part]], replace_values, allow_undefined_key_vars), sep = "")
        } else if (allow_undefined_key_vars && (part %in% c("STORE_FREQ", "START_DATE", "MEMBER_NUMBER", "YEAR", "MONTH"))) {
          output <- paste0(output, "$", part, "$")
        } else {
          stop(paste('Error: The variable $', part, '$ was not defined in the configuration file.', sep = ''))
        }
      }
      i <- i + 1
    }
    output
  } else {
    string
  }
}

.LoadDataFile <- function(work_piece, explore_dims = FALSE, silent = FALSE) {
  #suppressPackageStartupMessages({library(ncdf4)})
  #suppressPackageStartupMessages({library(bigmemory)})
  #suppressPackageStartupMessages({library(plyr)})

  arrayIndex2VectorIndex <- function(indices, dims) {
    if (length(indices) > length(dims)) {
      stop("Error: indices do not match dimensions in arrayIndex2VectorIndex.")
    }
    position <- 1
    dims <- rev(dims)
    indices <- rev(indices)
    for (i in 1:length(indices)) {
      position <- position + (indices[i] - 1) * prod(dims[-c(1:i)])
    }
    position
  }

  file_found <- FALSE
  dims <- NULL

  filename <- work_piece[['filename']]
  namevar <- work_piece[['namevar']]
  output <- work_piece[['output']]
  # The names of all data files in the directory of the repository that match 
  # the pattern are obtained.
  if (length(grep("^http", filename)) > 0) {
    is_url <- TRUE
    files <- filename
    ## TODO: Check that the user is not using shell globbing exps.
  } else {
    is_url <- FALSE
    files <- system(paste('ls', filename), intern = TRUE, ignore.stderr = TRUE)
  }

  # If we don't find any, we leave the flag 'file_found' with a false value.
  if (length(files) > 0) {
    # The first file that matches the pattern is chosen and read.
    filename <- files[length(files)]
    file_found <- TRUE

    if (!silent) {
      if (explore_dims) {
        cat(paste("* Exploring dimensions...", filename, '\n'))
      }
      ##} else {
      ##  cat(paste("* Reading & processing data...", filename, '\n'))
      ##}
    }

    # We will fill in 'expected_dims' with the names of the expected dimensions of
    # the data array we'll retrieve from the file.
    expected_dims <- NULL
    if (work_piece[['is_2d_var']]) {
      expected_dims <- c(work_piece[['dimnames']][['longitudes']], 
                         work_piece[['dimnames']][['latitudes']])
      if (!is.null(work_piece[['grid']])) {
        # Here we read the grid type and its number of longitudes and latitudes
        ## TODO: Try to work out the type of grid without running this 
        ##       time-wasting command
        file_info <- system(paste('cdo sinfov', filename, '2> /dev/null'), intern = TRUE)
        namevar_matches <- grep(paste0(' ', namevar, ' '), file_info)
        if (length(namevar_matches) == 0) {
          stop(paste("Error: The variable", namevar, "is not defined in the file", filename))
        }
        var_id <- as.integer(strsplit(file_info[namevar_matches], ' {1, +}')[[1]][2])
        grid_info <- strsplit(file_info[grep('Grid coordinates', a) + var_id], ' {1, +}')[[1]]
        grid_type <- grid_info[grep(':', grid_info)[1] + 1]
        if (!(grid_type %in% c('gaussian', 'lonlat'))) {
          stop(paste("Error: Only 'gaussian' and 'lonlat' grids supported. See e.g: cdo sinfo ", filename))
        }
        grid_lons <- as.integer(grid_info[grep('nx', grid_info)[1] + 2])
        grid_lats <- as.integer(grid_info[grep('ny', grid_info)[1] + 2])
        # Now we calculate the expected grid type and its lons and lats
        if (length(grep('^t\\d{1,+}grid$', work_piece[['grid']])) > 0) {
          expected_grid_type <- 'gaussian'
          expected_grid_res <- strsplit(work_piece[['grid']], '[^0-9]{1,+}')[[1]][2]
          calculate number of lons and lats
        } else if (length(grep('^r\\d{1,+}x\\d{1,+}$', work_piece[['grid']])) > 0) {
          expected_grid_type <- 'lonlat'
          expected_grid_lons <- strsplit(work_piece[['grid']], '[^0-9]{1,+}')[[1]][2]
          expected_grid_lats <- strsplit(work_piece[['grid']], '[^0-9]{1,+}')[[1]][3]
        } else {
          stop("Error: Only supported 'grid' types are t<RES>grid and r<NX>x<NY>")
        }
        if (grid_lons == expected_grid_lons && grid_lats == expected_grid_lats &&
            grid_type == expected_grid_type) {
          remap_needed <- FALSE
        } else {
          remap_needed <- TRUE
        }
      } else {
        remap_needed <- FALSE
      }
      check masks?
      lonmin <- work_piece[['lon_limits']][1]
      lonmax <- work_piece[['lon_limits']][2]
      latmin <- work_piece[['lat_limits']][1]
      latmax <- work_piece[['lat_limits']][2]
      if (lonmin == 0 && lonmax == 360 && latmin == -90 && latmax == 90) {
        lonlat_subsetting_requested <- FALSE
      } else {
        lonlat_subsetting_requested <- TRUE
      }

      if (!lonlat_subsetting_requested && remap_needed) {
        filein <- filename
        transfer file before regrid
        also delete system dependencies in other commands like rm
        update system depends in DESCRIPTION
        filename <- tempfile(pattern = "loadRegridded", fileext = ".nc")
        system(paste0("cdo ", work_piece[['remap']], ",", work_piece[['grid']], 
                      " -selname,", namevar, " ", filein, " ", filename, 
                      " 2>/dev/null", sep = ""))
        workpiece_dimnames longitudes i latitudes <- 'lon', 'lat'
      }
    }
    fnc <- nc_open(filename)
    var_dimnames <- names(fnc$dim)[fnc$var[[namevar]][['dimids']] + 1]
    nmemb <- nltime <- NULL
    if (work_piece[['dimnames']][['members']] %in% var_dimnames) {
      nmemb <- fnc$dim[[work_piece[['dimnames']][['members']]]]$len
      expected_dims <- c(expected_dims, work_piece[['dimnames']][['members']])
    } else {
      nmemb <- 1
    }
    if (length(expected_dims) > 0) {
      dim_matches <- match(expected_dims, var_dimnames)
      if (any(is.na(dim_matches))) {
        stop(paste("Error: the expected dimension(s)", 
                   paste(expected_dims[which(is.na(dim_matches))], collapse = ', '), 
                   "were not found in", filename))
      }
      time_dimname <- var_dimnames[-dim_matches]
    } else {
      time_dimname <- var_dimnames
    }
    if (length(time_dimname) > 0) {
      if (length(time_dimname) == 1) {
        nltime <- fnc$dim[[time_dimname]]$len
        expected_dims <- c(expected_dims, time_dimname)
        dim_matches <- match(expected_dims, var_dimnames)
      } else {
        stop(paste("Error: the variable", namevar, 
                   "is defined over more dimensions than the expected (", 
                   paste(c(expected_dims, 'time'), collapse = ', '), 
                   "). See file", filename))
      }
    } else {
      nltime <- 1
    }

    if (work_piece[['is_2d_var']]) {
      ## We read the longitudes and latitudes from the file.
      lon <- ncvar_get(fnc, work_piece[['dimnames']][['longitudes']])
      lon[which(lon < 0)] <- lon[which(lon < 0)] + 360
      lon_indices <- 1:length(lon)
      lon_indices <- lon_indices[which((lon >= lonmin & lon <= lonmax) | 
                     (lonmax < lonmin & (lon >= 0 | lon <= 360)))]
      lon <- lon[lon_indices]
      lat <- ncvar_get(fnc, work_piece[['dimnames']][['latitudes']])
      lat_indices <- 1:length(lat)
      if (lat[1] < lat[length(lat)]) {
        lat_indices <- length(lat):1
        lat <- lat[lat_indices]
      }
      lat_indices <- lat_indices[which(lat >= latmin & lat <= latmax)]
      lat <- lat[which(lat >= latmin & lat <= latmax)]
    } else {
      lon <- 0
      lat <- 0
    }

    if (explore_dims) {
      # If we're exploring the file we only want one time step, to 
      # regrid it and work out the number of longitudes and latitudes.
      members <- 1
      ltimes_list <- list(c(1))
    } else {
      # The data is arranged in the array 'tmp' with the dimensions in a 
      # common order:
      #   1) Longitudes 
      #   2) Latitudes
      #   3) Members (even if is not a file per member experiment)
      #   4) Lead-times
      if (work_piece[['is_file_per_dataset']]) {
        time_indices <- 1:nltime
        mons <- strsplit(system(paste('cdo showmon ', filein, 
                         ' 2>/dev/null'), intern = TRUE), split = ' ')
        years <- strsplit(system(paste('cdo showyear ', filein, 
                          ' 2>/dev/null'), intern = TRUE), split = ' ')
        mons <- as.integer(mons[[1]][which(mons[[1]] != "")])
        years <- as.integer(years[[1]][which(years[[1]] != "")])
        time_indices <- ts(time_indices, start = c(years[1], mons[1]), 
                           end = c(years[length(years)], mons[length(mons)]),
                           frequency = 12)
        ltimes_list <- list()
        for (sdate in work_piece[['startdates']]) {
          selected_time_indices <- window(time_indices, start = c(as.integer(
                                   substr(sdate, 1, 4)), as.integer(substr(sdate, 5, 6))), 
                                   end = c(3000, 12), frequency = 12, extend = TRUE)
          ltimes_list <- c(ltimes_list, list(selected_time_indices[work_piece[['leadtimes']]]))
        }
      } else {
        ltimes <- work_piece[['leadtimes']]
        #if (work_piece[['dataset_type']] == 'exp') {
          ltimes_list <- list(ltimes[which(ltimes <= nltime)])
        #}
      }
      ## TODO: Put, when reading matrices, this kind of warnings
      #  if (nmember < nmemb) {
      #    cat("Warning:
      members <- 1:work_piece[['nmember']]
      members <- members[which(members <= nmemb)]
    }

    found_disordered_dims <- FALSE
    for (ltimes in ltimes_list) {
      if (work_piece[['is_2d_var']]) {
        start <- c(min(lon_indices), min(lat_indices))
        end <- c(max(lon_indices), max(lat_indices))
        if (remap_needed) {
          subset_indices <- list(min(lon_indices):max(lon_indices) - min(lon_indices) + 1,
                                 lat_indices - min(lat_indices) + 1)
          dim_longitudes <- ncdim_def(work_piece[['dimnames']][['longitudes']], "degrees_east", lon)
          dim_latitudes <- ncdim_def(work_piece[['dimnames']][['latitudes']], "degrees_north", lat)
          ncdf_dims <- list(dim_longitudes, dim_latitudes)
        } else {
          subset_indices <- list(lon_indices - min(lon_indices) + 1,
                                 lat_indices - min(lat_indices) + 1)
        }
        final_dims <- c(length(subset_indices[[1]]), length(subset_indices[[2]]), 1, 1)
      } else {
        start <- end <- c()
        subset_indices <- list()
        ncdf_dims <- list()
        final_dims <- c(1, 1, 1, 1)
      }
        
      if (work_piece[['dimnames']][['members']] %in% expected_dims) {
        start <- c(start, head(members, 1))
        end <- c(end, tail(members, 1))
        subset_indices <- c(subset_indices, list(members - head(members, 1) + 1))
        dim_members <- ncdim_def(work_piece[['dimnames']][['members']], "", members)
        ncdf_dims <- c(ncdf_dims, list(dim_members))
        final_dims[3] <- length(members)
      }
      if ((length(time_dimname) == 1) && (time_dimname %in% expected_dims)) {
        start <- c(start, head(ltimes, 1))
        end <- c(end, tail(ltimes, 1))
        subset_indices <- c(subset_indices, list(ltimes - head(ltimes, 1) + 1))
        dim_time <- ncdim_def(time_dimname, "", 1:length(ltimes), unlim = TRUE)
        ncdf_dims <- c(ncdf_dims, list(dim_time))
        final_dims[4] <- length(ltimes)
      }
      count <- end - start + 1
      start <- start[dim_matches]
      count <- count[dim_matches]
      subset_indices <- subset_indices[dim_matches]
      tmp <- take(ncvar_get(fnc, namevar, start, count, 
                  collapse_degen = FALSE), 
                  1:length(subset_indices), subset_indices)
      if (!all(dim_matches == sort(dim_matches))) {
        if (!found_disordered_dims && rev(work_piece[['indices']])[2] == 1 && rev(work_piece[['indices']])[3] == 1) {
          found_disordered_dims <- TRUE
          cat(paste0("* Warning: the dimensions for the variable ", namevar, " in the files of the experiment with index ", tail(work_piece[['indices']], 1), " are not in the optimal order for loading with Load(). The optimal order would be '", paste(expected_dims, collapse = ', '), "'. One of the files of the experiment is stored in ", filename))
        }
        tmp <- aperm(tmp, dim_matches)
      }
      dim(tmp) <- final_dims

      # The data is regridded if it corresponds to an atmospheric variable. When
      # the chosen output type is 'areave' the data is not regridded to not 
      # waste computing time unless the user specified a common grid.
      if (work_piece[['is_2d_var']]) {
        if (lonlat_subsetting_requested && remap_needed) {
          filein <- tempfile(pattern = "loadRegridded", fileext = ".nc")
          filein2 <- tempfile(pattern = "loadRegridded2", fileext = ".nc")
          nc_close(fnc)
          ncdf_var <- ncvar_def(namevar, "", ncdf_dims)
          fnc <- nc_create(filein2, list(ncdf_var))
          ncvar_put(fnc, ncdf_var, tmp)
          nc_close(fnc)
          transfer before regrid
          system(paste0("cdo -sellonlatbox,", lonmin, ",", lonmax, ",", latmin, ",", latmax, 
                 " -", work_piece[['remap']], ",", work_piece[['grid']], 
                 " -selname,", namevar, " ", filein2, " ", filein, 
                 " 2>/dev/null", sep = ""))
          system(paste("rm -f", filein2))
          fnc <- nc_open(filein)
          ## We read the longitudes and latitudes from the file.
          lon <- ncvar_get(fnc, 'lon')
          lon[which(lon < 0)] <- lon[which(lon < 0)] + 360
          lon_indices <- 1:length(lon)
          lon_indices <- lon_indices[which((lon >= lonmin & lon <= lonmax) | 
                         (lonmax < lonmin & (lon >= lonmin | lon <= lonmax)))]
          lon <- lon[lon_indices]
          lat <- ncvar_get(fnc, 'lat')
          lat_indices <- 1:length(lat)
          if (lat[1] < lat[length(lat)]) {
            lat_indices <- length(lat):1
            lat <- lat[lat_indices]
          }
          lat_indices <- lat_indices[which(lat >= latmin & lat <= latmax)]
          lat <- lat[which(lat >= latmin & lat <= latmax)]
          final_dims[c(1, 2)] <- c(length(lon_indices), length(lat_indices))

          tmp <- take(ncvar_get(fnc, namevar), c(1, 2), list(lon_indices, lat_indices))
          dim(tmp) <- final_dims
        }
        ##############CHECK MASK. Crop mask with lonmin and lonmax (be careful greenwhich) and then check if same number of lons and lats as in subset.
        #lons_mask <- seq(0, 360, length.out = dim(work_piece[['mask']])[1] + 1)
        #lon_indices_mask <- 1:(dim(work_piece[['mask']])[1] + 1)
        #lats_mask <- seq(-90, 90, length.out = dim(work_piece[['mask']])[2] + 1)
        #if (!is.null(work_piece[['mask']]) && !identical(c(full_grid_n_lons, full_grid_n_lats), dim(work_piece[['mask']]))) {
        #if (length(which(lons_mask >= lon[)
        #  stop(paste("Error: the mask of the dataset with index ", tail(work_piece[['indices']], 1), " in '", work_piece[['dataset_type']], "' is wrong. It must be on the common grid if the selected output type is 'lonlat', 'lon' or 'lat', or 'areave' and 'grid' has been specified. It must be on the grid of the corresponding dataset if the selected output type is 'areave' and no 'grid' has been specified. For more information check ?Load and see help on parameters 'grid', 'maskmod' and 'maskobs'.", sep = ""))
        #}
      }

      if (explore_dims) {
        if (work_piece[['is_file_per_member']]) {
          ## TODO: When the exp_full_path contains asterisks and is file_per_member
          ##       members from different datasets may be accounted.
          ##       Also if one file member is missing the accounting will be wrong.
          ##       Should parse the file name and extract number of members.
          if (is_url) {
            nmemb <- NULL
          } else {
            nmemb <- length(files)
          }
        }
        dims <- list(nmemb = nmemb, nltime = nltime, lon = lon, lat = lat)
      } else {
        if (work_piece[['is_2d_var']]) {
          mask <- work_piece[['mask']][lon_indices, lat_indices]
          tmp <- apply(tmp, c(3, 4), function(x) {
            # Disable of large values.
            if (!is.na(work_piece[['var_limits']][2])) {
              x[which(x > work_piece[['var_limits']][2])] <- NA
            }
            if (!is.na(work_piece[['var_limits']][1])) {
              x[which(x < work_piece[['var_limits']][1])] <- NA
            }
            if (!is.null(mask)) {
              x[which(mask < 0.5)] <- NA
            }

            if (output == 'areave' || output == 'lon') {
              weights <- InsertDim(cos(lat * pi / 180), 1, length(lon))
              weights[which(is.na(x))] <- NA
              if (output == 'areave') {
                weights <- weights / mean(weights, na.rm = TRUE)
                mean(x * weights, na.rm = TRUE) 
              } else {
                weights <- weights / InsertDim(Mean1Dim(weights, 2, narm = TRUE), 2, length(lat))
                Mean1Dim(x * weights, 2, narm = TRUE)
              }
            } else if (output == 'lat') {
              Mean1Dim(x, 1, narm = TRUE)
            } else if (output == 'lonlat') {
              signif(x, 5)
            }
          })
          if (output == 'areave') {
            dim(tmp) <- c(1, 1, final_dims[3:4])
          } else if (output == 'lon') {
            dim(tmp) <- c(final_dims[1], 1, final_dims[3:4])
          } else if (output == 'lat') {
            dim(tmp) <- c(1, final_dims[c(2, 3, 4)])
          } else if (output == 'lonlat') {
            dim(tmp) <- final_dims
          }
        }
      
        num_indices <- length(work_piece[['indices']])
        var_data <- attach.big.matrix(work_piece[['out_pointer']])
        if (work_piece[['dims']][['nmember']] > 1 && nmemb > 1 && work_piece[['dims']][['nltime']] > 1 && nltime < work_piece[['dims']][['nltime']]) {
          work_piece[['indices']][num_indices - 2] <- work_piece[['indices']][num_indices - 2] - 1
          for (jmemb in 1:nmemb) {
            work_piece[['indices']][num_indices - 2] <- work_piece[['indices']][num_indices - 2] + 1
            out_position <- arrayIndex2VectorIndex(work_piece[['indices']], work_piece[['dims']])
            out_indices <- out_position:(out_position + length(tmp[, , jmemb, ]) - 1)
            var_data[out_indices] <- as.vector(tmp[, , jmemb, ])
          }
          work_piece[['indices']][num_indices - 2] <- work_piece[['indices']][num_indices - 2] - nmemb + 1
        } else {
          out_position <- arrayIndex2VectorIndex(work_piece[['indices']], work_piece[['dims']])
          out_indices <- out_position:(out_position + length(tmp) - 1)
          var_data[out_indices] <- as.vector(aperm(tmp, c(1, 2, 4, 3)))
        }

        work_piece[['indices']][num_indices - 3] <- work_piece[['indices']][num_indices - 3] + 1
      }
    }

    nc_close(fnc)    
    if (work_piece[['is_2d_var']] && !is.null(work_piece[['grid']])) {
      be aware filein may not be defined
      system(paste("rm -f", filein))
    }
  }

  if (explore_dims) {
    dims
  } else {
    file_found
  }
}

.LoadSampleData <- function(var, exp = NULL, obs = NULL, sdates, 
                            nmember = NULL, nmemberobs = NULL, 
                            nleadtime = NULL, leadtimemin = 1, 
                            leadtimemax = NULL, storefreq = 'monthly', 
                            sampleperiod = 1, lonmin = 0, lonmax = 360, 
                            latmin = -90, latmax = 90, output = 'areave', 
                            method = 'conservative', grid = NULL, 
                            maskmod = vector("list", 15), 
                            maskobs = vector("list", 15), 
                            configfile = NULL, suffixexp = NULL, 
                            suffixobs = NULL, varmin = NULL, varmax = NULL, 
                            silent = FALSE, nprocs = NULL) {
  ## This function loads and selects sample data stored in sampleMap and 
  ## sampleTimeSeries and is used in the examples instead of Load() so as
  ## to avoid nco and cdo system calls and computation time in the stage 
  ## of running examples in the CHECK process on CRAN.
  selected_start_dates <- match(sdates, c('19851101', '19901101', '19951101', 
                                          '20001101', '20051101'))
  start_dates_position <- 3
  lead_times_position <- 4

  if (output == 'lonlat') {
    sampleData <- s2dverification::sampleMap
    if (is.null(leadtimemax)) {
      leadtimemax <- dim(sampleData$mod)[lead_times_position]
    }
    selected_lead_times <- leadtimemin:leadtimemax

    dataOut <- sampleData
    dataOut$mod <- sampleData$mod[, , selected_start_dates, selected_lead_times, , ]
    dataOut$obs <- sampleData$obs[, , selected_start_dates, selected_lead_times, , ]
  }
  else if (output == 'areave') {
    sampleData <- s2dverification::sampleTimeSeries
    if (is.null(leadtimemax)) {
      leadtimemax <- dim(sampleData$mod)[lead_times_position]
    }
    selected_lead_times <- leadtimemin:leadtimemax

    dataOut <- sampleData
    dataOut$mod <- sampleData$mod[, , selected_start_dates, selected_lead_times]
    dataOut$obs <- sampleData$obs[, , selected_start_dates, selected_lead_times]
  }

  dims_out <- dim(sampleData$mod)
  dims_out[start_dates_position] <- length(selected_start_dates)
  dims_out[lead_times_position] <- length(selected_lead_times)
  dim(dataOut$mod) <- dims_out

  dims_out <- dim(sampleData$obs)
  dims_out[start_dates_position] <- length(selected_start_dates)
  dims_out[lead_times_position] <- length(selected_lead_times)
  dim(dataOut$obs) <- dims_out

  invisible(list(mod = dataOut$mod, obs = dataOut$obs, 
                 lat = dataOut$lat, lon = dataOut$lon))
}

.ConfigGetDatasetInfo <- function(matching_entries) {
  # This function obtains the information of a dataset and variable pair,
  # applying all the entries that match in the configuration file.
  experiment_defaults <- c('$DEFAULT_EXP_MAIN_PATH$', '$DEFAULT_EXP_FILE_PATH$', '$DEFAULT_GRID$', '$DEFAULT_NC_VAR_NAME$', '$DEFAULT_SUFFIX$', '$DEFAULT_VAR_MIN$', '$DEFAULT_VAR_MAX$')
  observation_defaults <- c('$DEFAULT_OBS_MAIN_PATH$', '$DEFAULT_OBS_FILE_PATH$', '$DEFAULT_NC_VAR_NAME$', '$DEFAULT_SUFFIX$', '$DEFAULT_VAR_MIN$', '$DEFAULT_VAR_MAX$')
  info <- NULL

  for (entry in matching_entries) {
    if (is.null(info)) {
      info <- entry[-1:-2]
      if (length(info) == 7) {
        info[which(info == '*')] <- experiment_defaults[which(info == '*')]
      } else {
        info[which(info == '*')] <- observation_defaults[which(info == '*')]
      }
    } else {
      info[which(entry[-1:-2] != '*')] <- entry[-1:-2][which(entry[-1:-2] != '*')]
    }
  }
  
  info
}
