#' Options for \pkg{basilisk}
#'
#' Options controlling run-time behavior of \pkg{basilisk}.
#' Unlike the various environment variables, these options can be turned on or off by users without requiring reinstallation of the \pkg{basilisk} ecosystem.
#'
#' @param value Logical scalar:
#' \itemize{
#' \item For \code{setBasiliskFork}, whether forking should be used when available.
#' \item For \code{setBasiliskShared}, whether the shared Python instance can be set in the R session.
#' \item For \code{setBasiliskCheckVersions}, whether to check for properly versioned package strings in \code{\link{setupBasiliskEnv}}.
#' }
#'
#' @return
#' All functions return a logical scalar indicating whether the specified option is enabled.
#'
#' @section Controlling process creation:
#' By default, \code{\link{basiliskStart}} will attempt to load a shared Python instance into the R session.
#' This avoids the overhead of setting up a new process but will potentially break any \pkg{reticulate}-dependent code outside of \pkg{basilisk}.
#' To guarantee that non-\pkg{basilisk} code can continue to execute, users can set \code{setBasiliskShared(FALSE)}.
#' This will load the Python instance into a self-contained \pkg{basilisk} process.
#'
#' If a new process must be generated by \code{\link{basiliskStart}}, forking is used by default.
#' This is generally more efficient than socket communication when it is available (i.e., not on Windows),
#' but can be less efficient if any garbage collection occurs inside the new process.
#' In such cases, users or developers may wish to turn off forking with \code{setBasiliskFork(FALSE)},
#' e.g., in functions where many R-based memory allocations are performed inside \code{\link{basiliskRun}}.
#'
#' If many \pkg{basilisk}-dependent packages are to be used together on Unix systems, setting \code{setBasiliskShared(FALSE)} may be beneficial.
#' This allows each package to fork to create a new process as no Python has been loaded in the parent R process (see \code{?\link{basiliskStart}}).
#' In contrast, if any package loads Python sharedly, the others are forced to use parallel socket processes.
#' This results in a tragedy of the commons where the efficiency of all other packages is reduced.
#'
#' Developers may wish to set \code{setBasiliskShared(FALSE)} and \code{setBasiliskFork(FALSE)} during unit testing,
#' to ensure that their functions do not make incorrect assumptions about the calling environment used in \code{\link{basiliskRun}}.
#'
#' @section Disabling package version checks:
#' By default, \code{\link{setupBasiliskEnv}} requires versions for all requested Python packages.
#' However, in some cases, the exact version of the packages may not be known beforehand.
#' Developers can set \code{setBasiliskCheckVersions(FALSE)} to disable all version checks, instead allowing \code{pip} to choose appropriate versions for the initial installation.
#' The resulting environment can then be queried using \code{\link{listPackages}} to obtain the explicit versions of all Python packages.
#'
#' Needless to say, this option should only be used during the initial phases of developing a \pkg{basilisk} client.
#' Once a suitable environment is created, Python package versions should be pinned in \code{\link{setupBasiliskEnv}}.
#' This ensures that all users are creating the intended environment for greater reproducibility (and easier debugging).
#' 
#' @author Aaron Lun
#' @examples
#' getBasiliskFork()
#' getBasiliskShared()
#' 
#' @seealso
#' \code{\link{basiliskStart}}, where these options are used.
#'
#' @export
#' @rdname basiliskOptions
getBasiliskFork <- function() {
    globals$get("fork")
}

#' @export
#' @rdname basiliskOptions
setBasiliskFork <- function(value) {
    .check_logical(value)
    globals$set(fork=value)
    value
}

#' @export
#' @rdname basiliskOptions
getBasiliskShared <- function() {
    globals$get("shared")
}

#' @export
#' @rdname basiliskOptions
setBasiliskShared <- function(value) {
    .check_logical(value)
    globals$set(shared=value)
    value
}

.check_logical <- function(value) {
    if (length(value)!=1 || is.na(value)) {
        stop("'value' should be a non-NA logical scalar")
    }
}

#' @export
#' @rdname basiliskOptions
setBasiliskCheckVersions <- function(value) {
    .check_logical(value)
    globals$set(no.version=!value)
    value
}

#' @export
#' @rdname basiliskOptions
getBasiliskCheckVersions <- function() {
    !globals$get("no.version")
}

globals <- (function () {
    current <- list(
        fork=TRUE,
        shared=TRUE,
        force.fallback=FALSE,
        no.version=FALSE
    )

    list(
        set=function(...) {
            replacements <- list(...)
            common <- intersect(names(current), names(replacements))
            current[common] <<- replacements[common]
            invisible(NULL)
        },
        get=function(name) {
            current[[name]]
        }
    )
})()
